在集合中查找模式

时间:2008-11-06 22:18:37

标签: algorithm design-patterns data-mining

我可以使用哪些算法来确定一组字符串中的常见字符?

为了使示例简单,我只关心连续2个以上的字符,如果它出现在2个或更多的样本中。例如:

  1. 0000abcde0000
  2. 0000abcd00000
  3. 000abc0000000
  4. 00abc000de000
  5. 我想知道:

    00用于1,2,3,4中 000用于1,2,3,4中 0000用于1,2,3
    00000用于2,3
    ab用于1,2,3,4中 abc用于1,2,3,4中 abcd用于1,2
    bc用于1,2,3,4中 bcd用于1,2
    cd用于1,2
    de用于1,4

7 个答案:

答案 0 :(得分:3)

我假设这不是作业。 (如果是的话,你就是一个自己的抄袭!; - )

以下是一个快速而肮脏的解决方案。时间复杂度为O(m**2 * n),其中m是字符串的平均长度,n是字符串数组的大小。

Occurrence的实例保留包含给定字符串的索引集。 commonOccurrences例程扫描字符串数组,为每个非空字符串调用captureOccurrencescaptureOccurrences例程将当前索引放入Occurrence,以获取给定字符串的每个可能子字符串。最后,commonOccurrences只选择那些至少有两个索引的Occurrences来形成结果集。

请注意,您的示例数据有比您在问题中确定的更常见的子字符串。例如,"00ab"出现在每个输入字符串中。根据内容(例如所有数字,所有字母等)选择有趣字符串的附加过滤器 - 正如他们所说 - 留给读者的练习。 ; - )

快速而肮脏的JAVA来源:

package com.stackoverflow.answers;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

public class CommonSubstringFinder {

    public static final int MINIMUM_SUBSTRING_LENGTH = 2;

    public static class Occurrence implements Comparable<Occurrence> {
        private final String value;
        private final Set<Integer> indices;
        public Occurrence(String value) {
            this.value = value == null ? "" : value;
            indices = new TreeSet<Integer>();
        }
        public String getValue() {
            return value;
        }
        public Set<Integer> getIndices() {
            return Collections.unmodifiableSet(indices);
        }
        public void occur(int index) {
            indices.add(index);
        }
        public String toString() {
            StringBuilder result = new StringBuilder();
            result.append('"').append(value).append('"');
            String separator = ": ";
            for (Integer i : indices) {
                result.append(separator).append(i);
                separator = ",";
            }
            return result.toString();
        }
        public int compareTo(Occurrence that) {
            return this.value.compareTo(that.value);
        }
    }

    public static Set<Occurrence> commonOccurrences(String[] strings) {
        Map<String,Occurrence> work = new HashMap<String,Occurrence>();
        if (strings != null) {
            int index = 0;
            for (String string : strings) {
                if (string != null) {
                    captureOccurrences(index, work, string);
                }
                ++index;
            }
        }
        Set<Occurrence> result = new TreeSet<Occurrence>();
        for (Occurrence occurrence : work.values()) {
            if (occurrence.indices.size() > 1) {
                result.add(occurrence);
            }
        }
        return result;
    }

    private static void captureOccurrences(int index, Map<String,Occurrence> work, String string) {
        final int maxLength = string.length();
        for (int i = 0; i < maxLength; ++i) {
            for (int j = i + MINIMUM_SUBSTRING_LENGTH; j < maxLength; ++j) {
                String partial = string.substring(i, j);
                Occurrence current = work.get(partial);
                if (current == null) {
                    current = new Occurrence(partial);
                    work.put(partial, current);
                }
                current.occur(index);
            }
        }
    }

    private static final String[] TEST_DATA = {
        "0000abcde0000",
        "0000abcd00000",
        "000abc0000000",
        "00abc000de000",
    };
    public static void main(String[] args) {
        Set<Occurrence> found = commonOccurrences(TEST_DATA);
        for (Occurrence occurrence : found) {
            System.out.println(occurrence);
        }
    }

}

SAMPLE OUTPUT :(请注意,每行实际上只有一次出现;我似乎无法阻止块引用标记合并行)

  

“00”:0,1,2,3   “000”:0,1,2,3
  “0000”:0,1,2   “0000a”:0,1
  “0000ab”:0,1   “0000abc”:0,1
  “0000abcd”:0,1   “000a”:0,1,2
  “000ab”:0,1,2   “000abc”:0,1,2
  “000abcd”:0,1   “00a”:0,1,2,3
  “00ab”:0,1,2,3   “00abc”:0,1,2,3
  “00abc0”:2,3   “00abc00”:2,3
  “00abc000”:2,3   “00abcd”:0,1
  “0a”:0,1,2,3   “0ab”:0,1,2,3
  “0abc”:0,1,2,3   “0abc0”:2,3
  “0abc00”:2,3   “0abc000”:2,3
  “0abcd”:0,1   “ab”:0,1,2,3   “abc”:0,1,2,3   “abc0”:2,3   “abc00”:2,3
  “abc000”:2,3   “abcd”:0,1   “bc”:0,1,2,3   “bc0”:2,3   “bc00”:2,3
  “bc000”:2,3   “bcd”:0,1   “c0”:2,3   “c00”:2,3   “c000”:2,3   “cd”:0,1
  “de”:0,3   “de0”:0,3   “de00”:0,3
  “e0”:0,3   “e00”:0,3

答案 1 :(得分:2)

这很可能是NP难问题。它看起来类似于multiple sequence alignment。基本上,您可以根据需要调整多维Smith-Waterman(=局部序列比对)。但是,可能有更高效的算法。

答案 2 :(得分:2)

构建一棵树,其中树的路径是字母序列。让每个节点包含一个“set”,字符串引用在传递中添加(或只是保持计数)。然后跟踪单词中的N个位置,其中N是您关心的最长序列(例如,在每个步骤处开始一个新句柄,在每个步骤中向下移动所有句柄并在N个步骤之后中止每个句柄)

这可以更好地用一个小的,有限的和密集的字母表(DNA是我认为使用它的第一个地方)。

编辑:如果您事先知道您关心的模式,可以通过提前构建树然后只检查您是否在树上来改变上述工作而不是扩展它。

一个例子

输入

abc
abd
abde
acc
bde

a : 4
  b : 3
    c : 1
    d : 2
      e : 1
  c : 1
    c : 1
b : 4
  d : 3
    e : 2
  c : 1
c : 3
  c : 1
d : 3
  e : 2

答案 3 :(得分:1)

您是否知道提前搜索所需的“价值”?或者你需要代码来解析字符串,并给你像你发布的统计数据?

使用Boyer-Moore算法是一种非常快速的方法,可以判断子串是否存在(甚至可以找到它们),如果您事先知道要查找的内容。

答案 4 :(得分:1)

在网络上查找“后缀树”。或者选择Dan Gusfield的“字符串,树和序列算法”。我没有这本书来验证,但是wikipedia page on suffix trees说第205页包含了一个解决问题的方法:“找到一组中至少k个字符串共有的最长子串”。

答案 5 :(得分:0)

你可以使用距离矩阵的分析。任何对角线移动(无成本变化)都是完全匹配。

答案 6 :(得分:0)

您可能会发现suffix array比后缀树更简单,更高效,具体取决于数据中常见子串的频率 - 如果它们足够常见,则需要更复杂的后缀数组构造算法。 (天真的方法是只使用你的库排序功能。)