【问题标题】:Find all repeating non-overlapping substrings and cycles查找所有重复的非重叠子串和循环
【发布时间】:2017-03-11 03:54:15
【问题描述】:

我手头有一个复杂的字符串操作问题。 我有一个字符串,其中我将有循环,以及我需要识别和列出的重复。

'abcabcabcabcabcdkkabclilabcoabcdieabcdowabcdppabzabx'

以下是可能的模式 ->

未使用的实际索引

abc -> 0,3,6,9,12,15,17, .....(重复字符串的出现索引), 0,3,6,9(重复字符串的唯一出现索引,12、15、17 取消资格,因为 abc 是较长重复子字符串的一部分)

abcd -> 12, 15, 17(重复字符串的出现索引), 12, 15, 17 (重复字符串的唯一出现索引)

bcda -> 13, 16, 18..(重复字符串的出现索引),(重复字符串的唯一出现索引),因为它是重叠的 字符串 abcd 因此它不是必需的 ab -> 0,3,6,9,12,15,17, 25, 27 ...(重复字符串的出现索引), 25、27(重复字符串的唯一出现索引)。 .....

我想找到所有unique recurring occurences/recurrences,即所有唯一的、不重叠的重复字符串值。正如刚才提到的。并且输入字符串可能包含,

所有循环模式(abcabcabcdefdefdeflkjlkjlkj => abcdeflkj 是循环中的重复,但 bcabbcab 不是预期的,因为它们是误报的结果) 或

单独重复的模式(abcxabcdabcm => abc 是重复但不是循环,即它们不相邻) 或者

两者的混合(abcabcabcabcabclkabcdokabcdhuabcd => abc 是循环重复,abcd 是非循环重复,我们需要找到两者 -> 只有abcdabc 是重复的,而不是bcabbcda 等)

有人可以为此问题陈述提出解决方案吗?我正在尝试使用 suffix_arrays 也没有找到重叠的结果。

【问题讨论】:

  • 你有什么问题?
  • @Stefan 请看一下,如果现在听起来不错?
  • “全部”或“发生”是什么意思? “aaaaaaa”的正确答案是什么?我可以争辩说,这是两个“aaa”循环,还有一些东西。我认为这个问题根本没有明确定义。
  • @matt 更新问题,我们实际上是在寻找重复出现,无论是循环还是单独出现(循环 -> 'abcabcabc',单独 -> 'abcokabcdeabcll'),混合 -> ('abcabcabcabcabcabklasdoqwepzxcpasdpabc ')。回答 'aaaaaa' -> 'a',因为它变成了 'a' 的循环。我已经更新了问题说明,请看一下。
  • (如果没有(编程)语言要检测,请尝试在代码块之前使用<!-- language: lang-none --> 进行语言检测:您可以摆脱“装饰”文字和for。(一个块无论如何,quote 可能更适合:它被包装了 - 你有一些 非常长 行。))(我,一方面,确实发现给出不对应于任何示例的索引令人恼火。索引相差 3 不可能对应于 4 个不同字符的模式的重复......)

标签: ruby string algorithm substring suffix-tree


【解决方案1】:

构造一个哈希,其键由给定字符串的所有唯一子字符串组成,这些子字符串在字符串中至少出现两次(不重叠),并且对于每个键,值是字符串中所有偏移量的数组,其中值键(子字符串)的开头。

代码

def recurring_substrings(str)
  arr = str.chars
  (1..str.size/2).each_with_object({}) do |n,h|
    arr.each_cons(n).map { |b| b.join }.uniq.each do |s|
      str.scan(Regexp.new(s)) { (h[s] ||= []) << Regexp.last_match.begin(0) }
    end
  end.reject { |_,v| v.size == 1 }
end

示例

recurring_substrings 'abjkabrjkab'
  #=> {"a"=>[0, 4, 9], "b"=>[1, 5, 10], "j"=>[2, 7], "k"=>[3, 8], "ab"=>[0, 4, 9],
  #    "jk"=>[2, 7], "ka"=>[3, 8], "jka"=>[2, 7], "kab"=>[3, 8], "jkab"=>[2, 7]}

recurring_substrings "abcabcabcabcabcdkkabclilabcoabcdieabcdowabcdppabzabx"
  #=> {"a"=>[0, 3, 6, 9, 12, 18, 24, 28, 34, 40, 46, 49],
  #    "b"=>[1, 4, 7, 10, 13, 19, 25, 29, 35, 41, 47, 50],
  #    "c"=>[2, 5, 8, 11, 14, 20, 26, 30, 36, 42], "d"=>[15, 31, 37, 43],
  #    "k"=>[16, 17], "l"=>[21, 23], "i"=>[22, 32], "o"=>[27, 38], "p"=>[44, 45],
  #    "ab"=>[0, 3, 6, 9, 12, 18, 24, 28, 34, 40, 46, 49],
  #    "bc"=>[1, 4, 7, 10, 13, 19, 25, 29, 35, 41], "ca"=>[2, 5, 8, 11],
  #    "cd"=>[14, 30, 36, 42],
  #    "abc"=>[0, 3, 6, 9, 12, 18, 24, 28, 34, 40], "bca"=>[1, 4, 7, 10],
  #    "cab"=>[2, 5, 8, 11], "bcd"=>[13, 29, 35, 41],
  #    "abca"=>[0, 6], "bcab"=>[1, 7], "cabc"=>[2, 8], "abcd"=>[12, 28, 34, 40],
  #    "abcab"=>[0, 6], "bcabc"=>[1, 7], "cabca"=>[2, 8],
  #    "abcabc"=>[0, 6], "bcabca"=>[1, 7], "cabcab"=>[2, 8]} 

说明

对于上面的第一个例子,步骤如下。

str = 'abjkabrjkab'
arr = str.chars
  #=> ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"] 
q = str.size/2 # max size for string to repeat at least once
  #=> 5 
b = (1..q).each_with_object({})
  #=> #<Enumerator: 1..5:each_with_object({})>

我们可以通过将枚举器转换为数组来查看该枚举器将生成​​哪些元素。 (下面我会多做几次。)

b.to_a
  #=> [[1, {}], [2, {}], [3, {}], [4, {}], [5, {}]]

空的哈希值将随着计算的进行而建立。

接下来将第一个元素传递给块,并使用并行赋值(有时称为多重赋值)为其设置块变量。

n,h = b.next
  #=> [1, {}] 
n #=> 1 
h #=> {} 

c = arr.each_cons(n)
  #=> #<Enumerator: ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"]:each_cons(1)>

c 是一个包含所有长度为 1 的子字符串的数组。在下一次迭代中,它将是一个包含所有长度为 2 的子字符串的数组,依此类推。见Emumerable#each_cons

c.to_a # Let's see which elements will be generated.
  #=> [["a"], ["b"], ["j"], ["k"], ["a"], ["b"], ["r"], ["j"], ["k"], ["a"], ["b"]] 
d = c.map { |b| b.join }
  #=> ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"] 
e = d.uniq
  #=> ["a", "b", "j", "k", "r"] 

在下一次迭代中,这将是

r = arr.each_cons(2)
  #=> #<Enumerator: ["a", "b", "j", "k", "a", "b", "r", "j", "k", "a", "b"]:
  #    each_cons(2)>
r.to_a
  #=> [["a", "b"], ["b", "j"], ["j", "k"], ["k", "a"], ["a", "b"],
  #    ["b", "r"], ["r", "j"], ["j", "k"], ["k", "a"], ["a", "b"]]  
s = r.map { |b| b.join }
  #=> ["ab", "bj", "jk", "ka", "ab", "br", "rj", "jk", "ka", "ab"] 
s.uniq
  #=> ["ab", "bj", "jk", "ka", "br", "rj"] 

继续,

f = e.each
  #=> #<Enumerator: ["a", "b", "j", "k", "r"]:each> 
f.to_a # Let's see which elements will be generated.
  #=> ["a", "b", "j", "k", "r"] 

s = f.next
  #=> "a" 
r = (Regexp.new(s))
  #=> /a/ 
str.scan(r) { (h[s] ||= []) << Regexp.last_match.begin(0) }

如果h 还没有密钥sh[s] #=&gt; nil。扩展为h[s] = h[s] || []h[s] ||= [] 在执行h[s] &lt;&lt; Regexp.last_match.begin(0) 之前将h[s] 转换为空数组。即h[s] = h[s] || [] #=&gt; nil || [] #=&gt; []

在块内,MatchData 对象使用类方法Regexp::last_match 检索。 (或者,可以将全局变量$~ 替换为Regexp.last_match。有关详细信息,请在Regexp 上搜索“特殊全局变量”。)MatchData#begin 返回当前匹配开始处str 的索引。

现在

h #=> {"a"=>[0, 4, 9]} 

其余的计算类似,将键值对添加到h,直到构造了示例中给出的。

【讨论】:

  • 我认为,对于abcabcabc -> abc,预期为 0,3,6,它给出了 -> abc、ab、bc 以及它们各自的索引。
  • 不错。 offset(0)[0] 可以替换成 begin(0)
  • 谢谢,@EricDuminil。我不知道MatchData#begin。我已修改我的答案以纳入您的建议。
【解决方案2】:

在@CarySwoveland 的出色回答之后进行进一步处理:

def ignore_smaller_substrings(hash)
  found_indices = []
  new_hash = {}
  hash.sort_by{|s,_| [-s.size,s]}.each{|s,indices|
    indices -= found_indices
    found_indices |= indices
    new_hash[s]=indices unless indices.empty?
  }
  new_hash
end

pp ignore_smaller_substrings(recurring_substrings('abcabcabcabcabcdkkabclilabcoabcdieabcdowabcdppabzabx'))

哈希按照字符串长度递减排序(然后按字母顺序),索引只允许出现一次。

输出

{"abcabc"=>[0, 6],
 "bcabca"=>[1, 7],
 "cabcab"=>[2, 8],
 "abcd"=>[12, 28, 34, 40],
 "abc"=>[3, 9, 18, 24],
 "bca"=>[4, 10],
 "bcd"=>[13, 29, 35, 41],
 "cab"=>[5, 11],
 "ab"=>[46, 49],
 "bc"=>[19, 25],
 "cd"=>[14, 30, 36, 42],
 "b"=>[47, 50],
 "c"=>[20, 26],
 "d"=>[15, 31, 37, 43],
 "i"=>[22, 32],
 "k"=>[16, 17],
 "l"=>[21, 23],
 "o"=>[27, 38],
 "p"=>[44, 45]}

它并没有准确回答这个问题,但它更接近了一点。

【讨论】:

    猜你喜欢
    • 2013-07-03
    • 2016-09-26
    • 1970-01-01
    • 2017-08-05
    • 2013-09-26
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多