【问题标题】:Check if a string is a substring of another string检查一个字符串是否是另一个字符串的子字符串
【发布时间】:2019-05-11 08:09:27
【问题描述】:

我阅读了一篇关于检查字符串是否是另一个字符串的子字符串的很好练习的文章。

练习的内容是:

编写一个从命令行获取 2 个字符串参数的程序。 程序必须验证第二个字符串是否是第一个字符串的子字符串 字符串(您不能使用 substr、substring 或任何其他标准 函数包括正则表达式库)。

第二个子字符串中每次出现 * 表示它可以是 匹配第一个字符串的零个或多个字符。

考虑这个例子:输入字符串 1:abcd 输入字符串 2:a*c 程序 应该评估字符串 2 是字符串 1 的子字符串。

另外,星号 (*) 可能被视为常规字符,如果 它前面有一个反斜杠 (\)。反斜杠 (\) 被视为 除星号 (*) 之前的所有情况下的常规字符。

我写了一个简单的应用程序,它首先检查第二个字符串不比第一个长(但有一个问题,当在 ("ab", "a*b") 测试时这是正确的测试,但方法失败) :

public static boolean checkCharactersCount(String firstString, String secondString) {
    return (firstString.length() > 0 && secondString.length() > 0) &&
            (firstString.length() > secondString.length());

...下一个验证是一个子串:

public static boolean checkSubstring(String firstString, String secondString) {
    int correctCharCounter = 0;
    int lastCorrectCharAtIndex = -1;

    for (int i = 0; i < secondString.length(); i++) {
        for (int j = 0; j < firstString.length(); j++) {
            if (j > lastCorrectCharAtIndex) {

                if ((secondString.charAt(i) == firstString.charAt(j)) || secondString.charAt(i) == '*') {
                    correctCharCounter++;
                    lastCorrectCharAtIndex = j;
                }

                if (correctCharCounter >= secondString.length())
                    return true;
            }
        }
    }

    return false;
}

但是有两个问题:

  1. 我上面的代码不支持字符连续性(例如 test: checkSubstring("abacd", "bcd") 返回 true,但它是错误的!-应该返回 false)
  2. 知道如何将特殊符号验证为“\*”吗?测试样本 (checkSubstring("abc", "\b")

您对解决方案的看法如何? :)

【问题讨论】:

  • 旁注:转义规则有些奇怪,它们不允许指定反斜杠后跟通配符。
  • @Henry 是的,很难写;P 我们需要使用双反斜杠(“\\”),它定义我们的第二个反斜杠作为真正的反斜杠或其他符号:P
  • 不,不可能。 \\* 的含义是一个反斜杠(第一个),后跟一个字面星号。
  • @Henry 你是对的 :)
  • 请注意,您的长度检查将排除带有“a*b”的“ab”,即使这应该有效,因为星号可以代表零个字符。

标签: java substring


【解决方案1】:

我会分几个阶段来做。

让我们称潜在的子字符串 p 和我们正在测试的包含子字符串 s 的字符串。

将“包含”部分分解为“p是否从s的第N个位置开始匹配?”的一系列问题;显然,您从第一个位置开始遍历 s,以查看 p 是否在 s 的任何位置匹配。

在匹配中,我们有可能遇到“*”;在这种情况下,我们想知道 p 在 * 之后的部分是否是 s 在匹配 p 的部分直到 * 之后的部分的子字符串。这暗示了一个递归例程,获取要匹配的部分和要匹配的字符串并返回真/假。当你遇到 * 时,形成两个新字符串并调用你自己。

如果您遇到 \,那么您只需继续与下一个字符进行常规匹配,而不是进行递归调用。鉴于您需要这样做,我想如果您从原始 p 构建 pPrime 可能是最简单的,这样您就可以在遇到反斜杠时删除它们,类似于从通配符中删除星号匹配。

我实际上没有写任何代码,你只是要求方法。

【讨论】:

  • 解决方案的美好愿景!没有反斜杠的临时字符串听起来不错;)我很好奇你将如何在代码中解决它
【解决方案2】:

试试这个:(为解释添加评论)

// only for non empty Strings
public boolean isSubString(String string1,String string2)
{
    // step 1: split by *, but not by \*
    List<String>list1 = new ArrayList<String>();
    char[]cs = string2.toCharArray();
    int lastIndex = 0 ;
    char lastChar = 0 ;
    int i = 0 ;
    for(; i < cs.length ; ++i)
    {
        if(cs[i]=='*' && lastChar!='\\')
        {
            list1.add(new String(cs,lastIndex,i-lastIndex).replace("\\*", "*"));
            //earlier buggy line:
            //list1.add(new String(cs,lastIndex,i-lastIndex));
            lastIndex = i + 1 ;
        }
        lastChar = cs[i];
    }
    if(lastIndex < i )
    {
        list1.add(new String(cs,lastIndex,i-lastIndex).replace("\\*", "*"));
    }
    // step 2: check indices of each string in the list
    // Note: all indices should be in proper order.
    lastIndex = 0;
    for(String str : list1)
    {
        int newIndex = string1.indexOf(str,lastIndex);
        if(newIndex < 0)
        {
            return false;
        }
        lastIndex = newIndex+str.length();
    }
    return true;
}

如果您不允许使用String.indexOf(),则编写一个函数public int indexOf(String string1,String string2, int index2) 并替换此语句

int newIndex = string1.indexOf(str,lastInxdex);

用这个声明:

int newIndex = indexOf(string1, str,lastInxdex);

================================================ =========

附录A:我测试的代码:

package jdk.conf;

import java.util.ArrayList;
import java.util.List;

public class Test01 {
    public static void main(String[] args)
    {
        Test01 test01 = new Test01();
        System.out.println(test01.isSubString("abcd", "a*c"));
        System.out.println(test01.isSubString("abcd", "bcd"));
        System.out.println(test01.isSubString("abcd", "*b"));
        System.out.println(test01.isSubString("abcd", "ac"));
        System.out.println(test01.isSubString("abcd", "bd"));
        System.out.println(test01.isSubString("abcd", "b*d"));
        System.out.println(test01.isSubString("abcd", "b\\*d"));
        System.out.println(test01.isSubString("abcd", "\\*d"));
        System.out.println(test01.isSubString("abcd", "b\\*"));

        System.out.println(test01.isSubString("a*cd", "\\*b"));
        System.out.println(test01.isSubString("", "b\\*"));
        System.out.println(test01.isSubString("abcd", ""));

        System.out.println(test01.isSubString("a*bd", "\\*b"));
    }
    // only for non empty Strings
    public boolean isSubString(String string1,String string2)
    {
        // step 1: split by *, but not by \*
        List<String>list1 = new ArrayList<String>();
        char[]cs = string2.toCharArray();
        int lastIndex = 0 ;
        char lastChar = 0 ;
        int i = 0 ;
        for(; i < cs.length ; ++i)
        {
            if(cs[i]=='*' && lastChar!='\\')
            {
                list1.add(new String(cs,lastIndex,i-lastIndex).replace("\\*", "*"));
                lastIndex = i + 1 ;
            }
            lastChar = cs[i];
        }
        if(lastIndex < i )
        {
            list1.add(new String(cs,lastIndex,i-lastIndex).replace("\\*", "*"));
        }
        // step 2: check indices of each string in the list
        // Note: all indices should be in proper order.
        lastIndex = 0;
        for(String str : list1)
        {
            int newIndex = string1.indexOf(str,lastIndex);
            if(newIndex < 0)
            {
                return false;
            }
            lastIndex = newIndex+str.length();
        }
        return true;
    }
}

输出:

true
true
true
false
false
true
false
false
false
false
false
true
true

【讨论】:

  • 检查输入字符串之一是否为空“”,它会失败
  • 怎么样 ("a*bc", "\*b") ?有问题,返回false
  • @ACz ("a*bc", "*b") 根本没有尝试过。再看一遍。它的 ("a*cd", "\*b")
  • @ACz 更正了代码。现在它适用于 ("a*bc", "\*b")
【解决方案3】:

我的解决方案看起来像这样,我评论了所有内容,希望您能理解这一点。

public static void main(String [] args) throws Exception {
        System.err.println(contains("bruderMusssLos".toCharArray(),"Mu*L*".toCharArray()));
}

public static boolean contains(char [] a, char [] b) {

    int counterB = 0; // correct characters
    char lastChar = '-'; //last Character encountered in B

    for(int i = 0; i < a.length; i++) {

        //if last character * it can be 0 to infinite characters
        if(lastChar == '*') {

            //if next characters in a is next in b reset last char
            // this will be true as long the next a is not the next b
            if(a[i] == b[counterB]) {
                lastChar = b[counterB];
                counterB++;

            }else {
                counterB++;
            }

        }else {

            //if next char is * and lastchar is not \ count infinite to next hit
            //otherwise * is normal character
            if(b[counterB] == '*' && lastChar != '\\') {
                lastChar = '*';
                counterB++;
            }else {
                //if next a is next b count
                if(a[i] == b[counterB]) {
                    lastChar = b[counterB];
                    counterB++;
                }else {
                    //otherwise set counter to 0
                    counterB = 0;
                }                   
            }

        }

        //if counterB == length a contains b
        if(counterB == b.length)
            return true;

    }


    return false;
}

例如,当前测试返回 true :)

【讨论】:

  • 好吧,我意识到它不适用于 * = 0 个字符,我的错。所以会有一个额外的检查,但除此之外,这适用于所有 * > 0 个字符和 arrays.length > 0。
  • 让我查一下,我会带回答案
【解决方案4】:

我发现这是一个很好的挑战。这个练习真的迫使我们在一般的语言和算法的非常低的层次上思考。没有 lambda,没有流,没有正则表达式,没有查找,没有子字符串,什么都没有。只是旧的 CharAt,一些 fors 和什么不是。本质上,我创建了一个查找方法,它查找要找到的字符串的第一个字符,然后另一个查找从那时起考虑您的规则。如果失败,它会返回找到的第一个索引,添加一个并执行必要的迭代次数,直到字符串结束。如果没有找到匹配项,它应该返回 false。如果只找到一个,则足以将其视为子字符串。最重要的极端情况在微积分开始时被考虑,因此如果检测到错误,则确定它不会更进一步。因此,'*' 单独表示任何字符匹配,我们可以使用 \ 对其进行转义。我试图包括大多数极端情况,这确实是一个挑战。我不完全确定我的代码是否涵盖了您的所有情况,但它应该涵盖相当多的情况。我真的很想帮助你,所以这是我的方法,这是我的代码:

package com.jesperancinha.string;

public class StringExercise {

    private static final char ASTERISK = '*';
    private static final char BACKSLASH = '\\';

    public boolean checkIsSubString(String mainString, String checkString) {
        int nextIndex = getNextIndex(0, checkString.charAt(0), mainString);
        if (nextIndex == -1) {
            return false;
        }
        boolean result = checkFromIndex(nextIndex, mainString, checkString);
        while (nextIndex < mainString.length() - 1 && nextIndex > -1) {
            if (!result) {
                nextIndex = getNextIndex(nextIndex + 1, checkString.charAt(0), mainString);
                if (nextIndex > -1) {
                    result = checkFromIndex(nextIndex, mainString, checkString);
                }
            } else {
                return result;
            }
        }
        return result;
    }

    private int getNextIndex(int start, char charAt, String mainString) {
        if (charAt == ASTERISK || charAt == BACKSLASH) {
            return start;
        }
        for (int i = start; i < mainString.length(); i++) {
            if (mainString.charAt(i) == charAt) {
                return i;
            }
        }
        return -1;
    }

    private boolean checkFromIndex(int nextIndex, String mainString, String checkString) {
        for (int i = 0, j = 0; i < checkString.length(); i++, j++) {
            if (i < (checkString.length() - 2) && checkString.charAt(i) == BACKSLASH
                    && checkString.charAt(i + 1) == ASTERISK) {
                i++;
                if (mainString.charAt(j + nextIndex) == BACKSLASH) {
                    j++;
                }
                if (checkString.charAt(i) != mainString.charAt(j + nextIndex)) {
                    return false;
                }
            }
            if (i > 0 && checkString.charAt(i - 1) != BACKSLASH
                    && checkString.charAt(i) == ASTERISK) {
                if (i < checkString.length() - 1 && (j + nextIndex) < (mainString.length() - 1)
                        && checkString.charAt(i + 1) !=
                        mainString.charAt(j + nextIndex + 1)) {
                    i--;
                } else {
                    if (j + nextIndex == mainString.length() - 1
                            && checkString.charAt(checkString.length() - 1) != ASTERISK
                            && checkString.charAt(checkString.length() - 2) != BACKSLASH) {
                        return false;
                    }
                }
            } else {
                if ((j + nextIndex) < (mainString.length() - 2) &&
                        mainString.charAt(j + nextIndex)
                                != checkString.charAt(i)) {
                    return false;
                }
            }
        }
        return true;
    }

}

我已经进行了一组单元测试,但是如果我将整个课程都放在这里会太长,我唯一想向您展示的是我在单元测试中实现的测试用例。这是我针对这种情况的单元测试的精简版:

package com.jesperancinha.string;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;

class StringExerciseMegaTest {

    @Test
    void checkIsSubString() {
        StringExercise stringExercise = new StringExercise();
        boolean test = stringExercise.checkIsSubString("abcd", "a*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("abcd", "a\\*c");
        assertThat(test).isFalse();
        test = stringExercise.checkIsSubString("a*c", "a\\*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdsadasa*c", "a\\*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdsadasa*csdfdsfdsfdsf", "a\\*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdsadasa**csdfdsfdsfdsf", "a\\*c");
        assertThat(test).isFalse();
        test = stringExercise.checkIsSubString("aasdsadasa**csdfdsfdsfdsf", "a*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdsadasa*csdfdsfdsfdsf", "a*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdweriouiauoisdf9977675tyhfgh", "a*c");
        assertThat(test).isFalse();
        test = stringExercise.checkIsSubString("aasdweriouiauoisdf9977675tyhfgh", "dwer");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdweriouiauoisdf9977675tyhfgh", "75tyhfgh");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdweriou\\iauoisdf9977675tyhfgh", "riou\\iauois");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdweriou\\*iauoisdf9977675tyhfgh", "riou\\\\*iauois");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdweriou\\*iauoisdf9\\*977675tyhfgh", "\\\\*977675tyhfgh");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("aasdweriou\\*iauoisdf9\\*977675tyhfgh", "\\*977675tyhfgh");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("\\*aasdweriou\\*iauoisdf9\\*977675tyhfgh", "\\*aasdwer");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("*aasdweriou\\*iauoisdf9\\*977675tyhfgh", "*aasdwer");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("abcd", "bc");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("abcd", "zbc");
        assertThat(test).isFalse();
        test = stringExercise.checkIsSubString("abcd", "*bc*");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("*bcd", "\\*bc*");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("abcd", "a*c");
        assertThat(test).isTrue();
        test = stringExercise.checkIsSubString("abcd", "az*bc");
        assertThat(test).isFalse();
    }
}

【讨论】:

  • 很棒的答案!是的,这是一个很好的挑战,有很多情况需要检查为空字符串、无符号、字符数、特殊字符等......这个练习看起来很简单,但如果我们开始编码,我们可以看到有很多我们需要检查的案例和 if 语句;D 给我一点时间来检查这段代码,它看起来很复杂
  • @ACz,谢谢!很高兴为您提供帮助!
猜你喜欢
  • 1970-01-01
  • 2011-02-07
  • 2013-05-12
  • 1970-01-01
  • 2022-07-01
  • 2011-12-16
  • 2013-03-13
  • 1970-01-01
相关资源
最近更新 更多