如何确定字符串是否是另一个字符串的子序列,而不管其间的字符是什么?

时间:2017-10-13 00:10:31

标签: java string

我正在尝试编写一个代码,告诉我一个字符串是否是另一个字符串的子字符串。问题在于,如果中间有字符并且唯一重要的字符是'A''T''G''C',则无关紧要。例如:

"TxxAA" is     a subsequence of "CTyyGCACA"
"pln"   is     a subsequence of "oiu"
"TAA"   is NOT a subsequence of "TCCCA" 

目前我在做

private boolean subSequence(DNASequence other) {

    other.fix();
    boolean valid = false;
    String t = other.toString();
    data = dataFix(data);
    int index = 0;

    for (int i = 0; i < data.length(); i++) {
        for (int j = 0; j < t.length(); j++) {
            if(data.charAt(i) == t.charAt(j)) {                        
                if( j >= index) {
                    valid = true;
                    index = j;
                    t = t.replace(t.charAt(j), '_');
                } else {
                    valid = false;
                }
            }
        }

    }

    if (data == "" || t == "" ) {
        valid = true;
    }
    return valid;
}

private String dataFix(String data) {
    for (int i = 0; i < data.length(); i += 1) {
        char ch = data.charAt(i);
        if (("ATGC".indexOf(ch) < 0))
            data = data.replace(data.charAt(i), ' ');        
    }
    data = data.replaceAll(" ", "").trim();
    return data;
}

fix()dataFix()方法会删除"ATGC"以外的所有字符。随着代码的迭代,它将t中与data.charAt(i)匹配的字符替换为_,以便它不会重新匹配相同的字母(我遇到了这个问题)。

目前,正在发生的事情是,replace函数正在替换字符串中的每个字符,而不仅仅是特定索引处的char(这是它应该做的)。有什么更好的方法来解决这个问题?我哪里错了?谢谢。

3 个答案:

答案 0 :(得分:4)

要回答第一个问题&#39;什么是解决此问题的更好方法?&#39;,我建议使用正则表达式(或正则表达式)。正则表达式是一种表达文本模式的方式。

对于您有搜索字词的示例:

T.*A.*A

用于描述您正在寻找的模式的正则表达式可以是:

public class DnaMatcher {

    static boolean isSearchChar(char c) {
        return 'A' == c || 'T' == c || 'G' == c || 'C' == c;
    }

    static Pattern preparePattern(String searchSequence) {
        StringBuilder pattern = new StringBuilder();
        boolean first = false;
        for (char c : searchSequence.toCharArray()) {
            if (isSearchChar(c)) {
                if (first) {
                    first = false;
                } else {
                    pattern.append(".*");
                }
                pattern.append(c);
            }
        }
        return Pattern.compile(pattern.toString());
    }

    static boolean contains(String sequence, String searchSequence) {
        Pattern pattern = preparePattern(searchSequence);
        Matcher matcher = pattern.matcher(sequence);
        return matcher.find();
    }

    public static void main(String...none) throws Exception {
        System.out.println(contains("CTyyGCACA", "TxxAA")); // true
        System.out.println(contains("TCCCA", "TAA")); // false
    }
}

没有详细说明术语。*是任何数字(零个或多个)任何字符的表达式。所以这个正则表达式描述了一个模式:T;那么任何人物;一个;那么任何人物;然后是A。

您的原始问题变为&#34;序列是否具有模式为T. * A. * A?&#34;的子序列。 Java内置了一个正则表达式库,您可以使用Pattern和Matcher对象来回答这个问题。

一些示例代码作为演示:

add_filter( 'job_manager_indeed_get_jobs_args', 'custom_job_manager_indeed_get_jobs_args' );

function custom_job_manager_indeed_get_jobs_args( $args ) {
    $args['q'] = $search_keywords ? $search_keywords . "designer" : "designer";

    return $args;
}

您可以看到preparePattern匹配准备正如所讨论的正则表达式。

答案 1 :(得分:4)

了解字符串可能很长,正则表达式检查可能需要一些时间。

static String fix(String s) {
    return s.replaceAll("[^ACGT]+", "");
}

static boolean isSubSequence(String sought, String chain) {
    sought = fix(sought);
    chain = fix(chain);
    char[] soughtChars = sought.toCharArray();
    char[] chainChars = chain.toCharArray();
    int si = 0;
    for (int ci = 0; si < soughtChars.length && ci < chainChars.length; ++ci) {
        if (chainChars[ci] == soughtChars[si]) {
            ++si;
        }
    }
    return si >= soughtChars.length;
}

或者

static boolean isSubSequence(String sought, String chain) {
    sought = fix(sought);
    chain = fix(chain);
    int ci = 0;
    for (char ch : sought.toCharArray()) {
        ci = chain.indexOf(ch, ci);
        if (ci < 0) {
            return false;
        }
        ++ci;
    }
    return true;
}

问题似乎更像这种结果。

与正则表达式比较:

我做了一个比较:

    StringBuilder sb = new StringBuilder(10_000);
    Random random = new Random(42);

    for (int i = 0; i < 10_1000 - 6; ++i) {
        sb.append("ACGT".charAt(random.nextInt(3)));
    }
    sb.append("TTAGTA");
    String s = sb.toString();
    String t = "TAGAAG";
    {
        long t0 = System.nanoTime();
        boolean found = contains(s, t);
        long t1 = System.nanoTime();
        System.out.printf("Found: %s in %d ms%n", found, (t1 - t0) / 1000_000L);
    }
    {
        long t0 = System.nanoTime();
        boolean found = isSubSequence(t, s);
        long t1 = System.nanoTime();
        System.out.printf("Found: %s in %d ms%n", found, (t1 - t0) / 1000_000L);
    }

结果

Found: false in 31829 ms --> Regex
Found: false in 5 ms     --> indexOf

但是:这个案子非常人为:短串上失败。

答案 2 :(得分:1)

可以使用(相对)简单的递归来完成:

 /**
 * Returns true is s1 is a subsequence of s2, false otherwise
 */
private static boolean isSubSeq(String s1, String s2) {
    if ("".equals(s1)) {
        return true;
    }
    String first = s1.substring(0, 1);
    s1 = s1.substring(1);
    int index = s2.indexOf(first);
    if (index == -1) {
        return false;
    }
    s2 = s2.substring(index+1);
    return isSubSeq(s1, s2);

}

算法:在s2中查找s1的第一个字符的第一个索引,如果没有这样的索引 - 答案是假的,如果有,我们可以继续寻找(递归)从位置索引+ 1开始的下一个字母

修改
您似乎需要清理输入以仅包含字符:'A','T','G','C'

这很容易做到(在Java 9上运行之后,但很容易修改为Java的低版本):

private static String sanitize(String s) {
    String result = "";
    List<Character> valid = List.of( 'A', 'T', 'G', 'C');
    for (char c : s.toCharArray()) {
        if (valid.contains(c)) {
            result += c;
        }
    }
    return result;
}

然后使用如下(示例):

    public static void main(String[] args) {
        String s1 = "TxxAA";
        String s2 = "CTyyGCACA";
        s1 = sanitize(s1); // you need to sanitize only s1, can you see why?
        System.out.println(isSubSeq(s1, s2));
    }