python – 查找字符串中所有重复的子字符串以及它们出现的频率

问题:

我需要符合以下条件的所有字符序列:

>字符序列必须多次出现((LE,1)因此无效).
>字符序列必须长于一个字符((M,2)因此无效).
>字符序列不能是存在相同次数的较长现有序列的一部分(如果(LIO,2)存在,则(LI,2)无效).

因此,如果输入字符串是:KAKAMNENENELIOLELIONEM $
输出将是:

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

它还需要快速,它应该能够在合理的时间内解决1000个字符长的字符串.

我尝试过的:

从后缀树获取分支数量:

编辑this后缀树 – 创建librabry(Python-Suffix-Tree),我制作了一个程序,给出了一些错误的结果.

我将此函数添加到suffix_tree.py中的SuffixTree类:

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()基本上遍历节点之间的所有连接(在此库中称为边)并保存它指向的节点有多少连接(它将它保存到repeated_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:

(无法完成它)

题:

我怎样才能制作出我想要的程序?

最佳答案 您的后缀树方法是正确的方法.

获取匹配集及其出现次数

基本上你需要做的是以BFS方式遍历树.从root的子节点开始,您将递归计算每个节点可到达的叶子数.这将导致您在根上调用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次a.k.a N匹配(其中N是固定整数),我们如何过滤掉“冗余”?

我们首先从通过减少长度排序的N匹配集合中建立优先级队列.然后,我们将迭代地构建这些匹配的通用后缀树(GST)以识别冗余匹配.为此,算法如下:

>对于堆中的每个元素(在顶部),测试此元素是否是已在GST中注册的元素之一的子字符串

>如果不是:将其插入GST并将其附加到“良好匹配”列表中.
>其他:跳过它,因为已经注册了另一个更大的匹配…并尝试使用下一个元素

>一旦堆为空,良好匹配列表包含所有非冗余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实现,你可以随时调整你的实现.请参阅this question以获取有关如何执行此操作的提示.

点赞