Поиск всех повторяющихся подстрок в строке и частота их появления

Проблема:

Мне нужны все последовательности символов, которые соответствуют следующему:

  1. Последовательность символов должна присутствовать более одного раза (поэтому (LE, 1) недопустимо).
  2. Последовательность символов должна быть длиннее одного символа ((M, 2) недопустимо).
  3. Последовательность символов не должна быть частью более длинной существующей последовательности, которая присутствует такое же количество раз (таким образом, (LI, 2) недопустимо, если присутствует (LIO, 2)).

Таким образом, если бы входная строка была: KAKAMNENENELIOLELIONEM$, на выходе было бы:

(KA, 2)
(NE, 4)
(LIO, 2)

Он также должен быть быстрым, он должен иметь возможность решить строку длиной 1000 символов за разумное время.

Что я пробовал:

Получение количества ветвей из суффиксного дерева:

Редактирование этого суффиксного дерева -создание библиотеки (Python-Suffix -Tree), я сделал программу, которая дает несколько ошибочные результаты.

Я добавил эту функцию в класс SuffixTree в suffix_tree.py:

def get_repeated_substrings(self):
    curr_index = self.N
    values = self.edges.values()
    values = sorted(values, key=lambda x: x.dest_node_index)
    data = []  # index = edge.dest_node_index - 1
    for edge in values:
        if edge.source_node_index == -1:
            continue
        top = min(curr_index, edge.last_char_index)
        data.append([edge.source_node_index,
                self.string[edge.first_char_index:top+1]])
    repeated_substrings = {}
    source_node_indexes = [i[0] for i in data]
    nodes_pointed_to = set(source_node_indexes)
    nodes_pointed_to.remove(0)
    for node_pointed_to in nodes_pointed_to:
        presence = source_node_indexes.count(node_pointed_to)
        key = data[node_pointed_to-1][1]
        if key not in repeated_substrings:
            repeated_substrings[key] = 0
        repeated_substrings[key] += presence
    for key in repeated_substrings:
        if len(key) > 1:
            print(key, repeated_substrings[key])

А затем использовал его и остальную часть библиотеки следующим образом:

from lib.suffix_tree import SuffixTree

st = SuffixTree("KAKANENENELIOLELIONE$")
print(st.get_repeated_substrings())

Выход:

KA 2
NE 7
LIO 2
IO 4

get_repeated_substrings() в основном проходит через все соединения между узлами (называемые ребрами в этой библиотеке) и сохраняет, сколько еще соединений имеет узел, на который он указывает (он сохраняет его в Repeat_substrings), а затем печатает сохраненные значения, которые содержат более одного символа. длинная.

Он добавляет количество подключений к количеству, которое уже было для этой последовательности, что работает большую часть времени, но, как вы можете видеть в приведенном выше выводе, это привело к неправильному значению для «NE» (7, должно было быть 4). После решения этой проблемы я понял, что этот метод не будет обнаруживать паттерны, составленные из одного и того же символа (AA, BB), и другие неисправности. Мой вывод: либо с суффиксными деревьями никак не решить, либо я что-то очень неправильно делаю.

Другие методы:

Я также пробовал несколько более простых способов, состоящих из перебора вещей, но это тоже не увенчалось успехом:

import copy

string = 'kakabaliosie'

for main_char in set(string):
    indices = []
    for char_i, char in enumerate(string):
        if main_char == char:
            indices.append(char_i)
    relative = 1
    while True
        for index in indices:
            other_indices = copy.deepcopy(indices)
            other_indices.remove(index)
            for other_index in other_indices:

(не успеваю закончить)

Вопрос:

Как я могу сделать программу, которая делает то, что я хочу?


person alvitawa    schedule 28.05.2016    source источник
comment
@BenHare Написание конструктивного ответа на мой вопрос действительно требует некоторой «работы», но я не думаю, что мой вопрос квалифицируется как «просить кого-то сделать вашу работу за вас». В конце концов, я уже несколько дней пытаюсь решить эту проблему разными способами (наиболее известные из которых я указал в вопросе).   -  person alvitawa    schedule 28.05.2016
comment
Взяв твой пример. Если у нас есть (LIO, 2), нужно ли также игнорировать (IO, 2)? Кроме того, вывод неверен: подстрока ELIO может быть найдена дважды, но не указана в результатах. То же самое для ENE, у которых есть два перекрывающихся совпадения.   -  person Rerito    schedule 23.06.2016
comment
@Rerito Спасибо, ты прав. И да, если у нас есть (LIO, 2), (IO, 2) следует игнорировать.   -  person alvitawa    schedule 20.06.2018


Ответы (1)


Ваш подход к дереву суффиксов - правильный путь.

Получение набора совпадений и их количества вхождений

По сути, вам нужно пройтись по дереву в стиле BFS. Начиная с дочерних элементов корня, вы рекурсивно подсчитываете количество листьев, достижимых каждым узлом. Это приведет к методу для Node, который вы вызовете в корне. Вот возможная реализация:

def count_leaves(self, stree):
    leaves_count = 0
    for child in [stree.nodes[x.dest_node_index] for x in self.edges.values()]:
        child_leaves_count = child.count_leaves(stree)
        if 0 == child_leaves_count:
            # The child node is a leaf...
            leaves_count = leaves_count + 1
        else:
            # The child node is an internal node, we add up the number of leaves it can reach
            leaves_count = leaves_count + child_leaves_count
    self.leaves_count = leaves_count
    return leaves_count

Теперь каждый узел помечен количеством листьев, которых он может достичь.

Затем интересные свойства дерева суффиксов помогут вам автоматически отфильтровывать подстроки, которые не соответствуют некоторым вашим требованиям:

  • Если строка присутствует только один раз, то вы обязательно окажетесь в переходе к конечному узлу (указанный переход имеет как минимум два символа, потому что мы не учитываем конечный токен $). Это означает, что это не явное состояние, и поэтому мы даже не рассматриваем его.
  • Если у нас есть (LI, 2) и (LIO, 2), то (LI, 2) является неявным состоянием суффиксного дерева, что означает, что оно находится в середине ребра. Поскольку мы рассматриваем только подстроки с явным состоянием (т. е. заканчивающиеся в узле), мы никогда не найдем (LI, 2) с самого начала.
  • Внутренние узлы имеют как минимум 2 дочерних элемента, иначе они были бы листом и не имели ни одного.

Теперь, перебирая внутренние узлы, вы получите как список подстрок, так и их количество вхождений во входной строке (вам нужно отфильтровать узлы, представляющие подстроку из 1 символа).

Ниже вы найдете строковое представление дерева суффиксов для вашей входной строки. Это поможет вам визуализировать, какие подстроки совпадают.

- O - N E M $ - ##  
    - L E L I O N E M $ - ##  
- I O - N E M $ - ##  
      - L E L I O N E M $ - ##  
- $ - ##  
- E - M $ - ##  
      L I O - N E M $ - ##
              L E L I O N E M $ - ##
      N E - L I O L E L I O N E M $ - ##
            N E L I O L E L I O N E M $ - ##
- K A - M N E N E N E L I O L E L I O N E M $ - ##
        K A M N E N E N E L I O L E L I O N E M $ - ##
- L - E L I O N E M $ - ##
      I O - N E M $ - ##
            L E L I O N E M $ - ##
- A - M N E N E N E L I O L E L I O N E M $ - ##
      K A M N E N E N E L I O L E L I O N E M $ - ##
- M - $ - ##
      N E N E N E L I O L E L I O N E M $ - ##
- N E - M $ - ##
        L I O L E L I O N E M $ - ##
        N E - L I O L E L I O N E M $ - ##
              N E L I O L E L I O N E M $ - ##

Это приводит к следующему выводу:

(IO, 2)
(ELIO, 2)
(ENE, 2)
(KA, 2)
(LIO, 2)
(NE, 4)
(NENE , 2)

Исключение избыточных совпадений для фиксированного числа вхождений (N)

Теперь предположим, например, что LIO и IO должны быть отфильтрованы, потому что, как и ELIO, они имеют два совпадения. Такие подстроки большего совпадения будут называться "избыточными совпадениями". Следующая загадка остается нерешенной: учитывая набор всех совпадений, которые происходят ровно N раз, то есть N-совпадений (где N — фиксированное целое число), как мы можем отфильтровать «лишние» совпадения?

Начнем с создания приоритетной очереди из набора N совпадений, упорядоченных по убыванию длины. Затем мы итеративно построим обобщенное дерево суффиксов (GST) из этих совпадений, чтобы выявить избыточные совпадения. Для этого алгоритм следующий:

  1. For each element in the heap (taken at the top), test if this element is a substring of one of the elements already registered in the GST
    • If not: insert it into the GST and append it to the list of "good matches".
    • Остальное: пропустите, так как уже зарегистрировано другое большее совпадение... И попробуйте со следующим элементом
  2. Когда куча пуста, список хороших совпадений содержит все неизбыточные N-совпадения.

Это приводит к следующему псевдо-коду Python:

match_heap = heapify(set_of_matches)
good_matches = []
match_gst = generalized_suffix_tree()
while (not match_heap.empty()):
    top_match = match_heap.top()
    if (not match_gst.is_substring(top_match.string)):
        gst_match.insert(top_match.string)
        good_matches.append(top_match)
    else:
        # The given match is a substring of an already registered, bigger match
        # We skip it
return good_matches

Фильтрация всех избыточных совпадений

Теперь, когда мы можем отфильтровать избыточные совпадения для N-соответствий, можно легко отфильтровать их все из нашего глобального набора совпадений. Мы собираем совпадения в корзинах, используя их количество вхождений, затем применяем алгоритм из предыдущего раздела к каждой корзине.

Заметки

Для реализации описанного выше алгоритма вам потребуется реализация обобщенного дерева суффиксов, которое немного отличается от обычного дерева суффиксов. Если вы не можете найти реализацию Python, вы всегда можете адаптировать ту, что у вас есть. См. этот вопрос, чтобы получить подсказки о том, как это сделать.

person Rerito    schedule 23.06.2016