Нахождение шаблона в наборе - PullRequest
3 голосов
/ 07 ноября 2008

Какие алгоритмы можно использовать для определения общих символов в наборе строк?

Чтобы сделать пример простым, я забочусь только о 2+ символах в строке и о том, встречается ли он в 2 или более образца. Например:

  1. 0000abcde0000
  2. 0000abcd00000
  3. 000abc0000000
  4. 00abc000de000

Я хотел бы знать:

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
до н.э. использовался в 1,2,3,4
bcd использовался в 1,2
CD был использован в 1,2
де был использован в 1,4

Ответы [ 7 ]

3 голосов
/ 07 ноября 2008

Я предполагаю, что это не домашняя работа. (Если это так, вы один из ваших собственных плагиата!; -)

Ниже приведено быстрое и грязное решение. Сложность по времени составляет O(m**2 * n), где m - средняя длина строки, а n - размер массива строк.

Экземпляр Occurrence содержит набор индексов, которые содержат данную строку. Подпрограмма commonOccurrences сканирует строковый массив, вызывая captureOccurrences для каждой ненулевой строки. Подпрограмма captureOccurrences помещает текущий индекс в Occurrence для каждой возможной подстроки строки, которая ему дана. Наконец, commonOccurrences формирует набор результатов, выбирая только те Occurrences, которые имеют как минимум два индекса.

Обратите внимание, что в данных вашего примера гораздо больше общих подстрок, чем вы указали в вопросе. Например, "00ab" встречается в каждой из входных строк. Дополнительный фильтр для выбора интересных строк на основе содержимого (например, все цифры, все буквы и т. Д.), Как говорится, оставлен в качестве упражнения для читателя. ; -)

ИСТОЧНИК БЫСТРОГО И Грязного Ява:

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);
        }
    }

}

ОБРАЗЕЦ ВЫБОРА: (обратите внимание, что на строку в действительности приходился только один Вихрь; похоже, я не могу помешать разметке цитат из слияния строк)

"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 «00а»: 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
«0а»: 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 «до н.э.»: 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 "кд": 0,1
"де": 0,3 "de0": 0,3 "de00": 0,3
"e0": 0,3 «е00»: 0,3

2 голосов
/ 07 ноября 2008

Построить дерево, где путь через дерево представляет собой последовательность букв. Пусть каждый узел содержит «набор», к которому ссылки на строки добавляются попутно (или просто сохраняют счет). Затем отследите N мест в слове, где N - самая длинная последовательность, о которой вы заботитесь (например, начинайте новый дескриптор на каждом символе, обходя все дескрипторы на каждом шаге и прерывайте каждый дескриптор после N шагов)

Это будет лучше работать с маленьким, конечным и плотным алфавитом (ДНК была первым местом, где я подумал об этом).

Редактировать: Если вы заранее знаете шаблон, который вам небезразличен, то вышеперечисленное можно изменить на работу, построив дерево заранее, а затем только проверяя, находитесь ли вы на дереве, а не расширяя его.

пример

вход * +1011 *

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
2 голосов
/ 07 ноября 2008

Скорее всего, это трудная проблема NP. Это похоже на множественное выравнивание последовательностей , которое есть. По сути, вы можете адаптировать многомерный Smith-Waterman (= локальное выравнивание последовательностей) для своих нужд. Хотя может быть более эффективный алгоритм.

1 голос
/ 07 ноября 2008

Посмотрите "Суффикс деревья" в Интернете. Или возьмите «Алгоритмы на строках, деревьях и последовательностях» Дэна Гасфилда. У меня нет книги для проверки, но на странице википедии о деревьях суффиксов сказано, что страница 205 содержит решение вашей проблемы: «найти самые длинные подстроки, общие по крайней мере для k строк в наборе» ».

1 голос
/ 07 ноября 2008

Знаете ли вы «ценности», которые нужно искать заранее? Или вам нужен код для разбора строк и выдачи вам статистики, как вы опубликовали?

Использование алгоритма Бойера-Мура - очень быстрый способ определить, существуют ли подстроки (и даже найти их), если вы заранее знаете, что ищете.

0 голосов
/ 07 ноября 2008

Вы можете найти массив суффиксов проще и эффективнее, чем дерево суффиксов, в зависимости от того, как часто в ваших данных встречаются общие подстроки - если они достаточно общие, вам понадобится более сложный алгоритм построения суффиксного массива. (Наивный метод состоит в том, чтобы просто использовать библиотечную функцию сортировки.)

0 голосов
/ 07 ноября 2008

Вы можете использовать анализ матрицы расстояний. Любое диагональное движение (без изменения стоимости) является точным совпадением.

...