确定Ruby数组/字符串中最大匹配序列的有效方法(在Ruby中)?

时间:2019-03-13 16:24:32

标签: arrays ruby algorithm match

比方说,我有两个单词数组:

array1 = ["hello", "world", "i", "am", "in", "the", "world"]
array2 = ["This", "is", "the", "hello", "world", "message"]

可以很容易地用两个字符串表示:

string1 = "hello world i am in the world"
string2 = "This is the hello world message"

让我们假设我现在要使用数组。

我想找到array2中最大的子数组,该数组以相同的顺序出现

因此,如果您打算以可想象的最慢的方式进行操作,那么您会说:

  • 从array2中获取所有6个单词的子数组(其中有一个)。
    • 它是否按此顺序出现在array1中?否
  • 从array2中获取所有5字子数组(其中有两个)。
    • 它们中的任何一个都按此顺序出现在array1中吗?否
  • 从array2中获取所有4字子数组。
    • 它们中的任何一个都没有按此顺序出现在array1中吗?否
  • 等等,直到我们到达
  • 从array2中获取所有2字子数组。
    • 它们中的任何一个都没有按此顺序出现在array1中吗?是:[“ hello”,“ world”]可以。停止。

但是,这感觉效率很低。谁能看到更好的方法?我正在使用Ruby,但是我对通用算法以及如何使用该特定语言感兴趣。

请注意,这不仅是数组的交集,因为(至少在ruby中)不关心元素的顺序,而我确实关心。

谢谢!

5 个答案:

答案 0 :(得分:2)

直到我了解到“最长的公共子字符串/序列问题”(请参阅​​@Dustin的答案),我认为没有一种方法比您在问题中概述的方法更好:从最大的子数组开始({ {1}}本身),然后顺序将子数组的大小减小一个,直到找到匹配项为止(或者确定两个数组不包含公共元素)。尽管我现在看到了一种更有效的方法,但是您的想法肯定不是一个坏主意,尤其是在子字符串不太大的情况下,并且比Dustin引用的动态编程解决方案更容易实现。我已经在下面实现了您的想法。

我选择使用正则表达式来标识匹配项,因此我需要将array2转换为字符串。

array1

计算如下。

str1 = array1.join(' ')
  #=> "hello world i am in the world" 
如果[array1.size, array2.size].min.downto(1).each do |n| a = array2.each_cons(n).find { |a| str1.match?(/\b#{a.join(' ')}\b/) } break a unless a.nil? end #=> ["hello", "world"] nil没有公共元素,则返回

array1。如果需要,可以先测试array2

这是对我上面的内容的可能改进。这个想法是尝试减少(array1 & array2).empty?中的m

m.downto(1)

这在这里无济于事,但是如果数组h1 = array1.each_with_object(Hash.new(0)) { |word, h| h[word] += 1 } #=> {"hello"=>1, "world"=>2, "i"=>1, "am"=>1, "in"=>1, "the"=>1} h2 = array1.each_with_object(Hash.new(0)) { |word, h| h[word] += 1 } #=> {"hello"=>1, "world"=>2, "i"=>1, "am"=>1, "in"=>1, "the"=>1} m = [array1.size, array2.size, h2.sum { |k,v| [v, h2[k]].min }].min #=> [7, 6, 7].min #=> 6 array1不同则可能有用。

答案 1 :(得分:2)

这似乎是解决“最长公共子字符串”问题的方法,但是使用单词代替字符串中的字符。

此Wiki(https://en.wikipedia.org/wiki/Longest_common_substring_problem)概述了动态编程方法,用于在伪代码中定位最大的公共匹配项,并且可以将其移植到通过两个数组作为参数的红宝石上。

function LCSubstr(S[1..r], T[1..n])
L := array(1..r, 1..n)
z := 0
ret := {}
for i := 1..r
    for j := 1..n
        if S[i] == T[j]
            if i == 1 or j == 1
                L[i,j] := 1
            else
                L[i,j] := L[i-1,j-1] + 1
            if L[i,j] > z
                z := L[i,j]
                ret := {S[i-z+1..i]}
            else
            if L[i,j] == z
                ret := ret ∪ {S[i-z+1..i]}
        else
            L[i,j] := 0
return ret

答案 2 :(得分:2)

直接进行“六个字”测试 然后我遍历第二个数组中的每个单词,并测试它是否在第一个中。 如果是,则寻找它,然后寻找一个,如果两者都寻找,则寻找下一个。

即,一旦发现第一个数组中不存在“ This”,您还将丢弃其他五个以此开头的潜在候选对象。

答案 3 :(得分:1)

这是一个快速工作的解决方案,将比较结果缩减为仅适用于数组中共有的那些元素:

array1 = ["hello", "world", "i", "am", "in", "the", "world"]
array2 = ["This", "is", "the", "hello", "world", "message"]

common_words = array1 & array2

stringified_array1 = array1.join(' ')
stringified_array2 = array2.join(' ')

(common_words.length - 1).downto(0).map do |n|
  stringified_combo = array1[0..n].join(' ')

  if stringified_array1.include?(stringified_combo) && stringified_array2.include?(stringified_combo)
    stringified_combo.split($,)
  end 
end.compact.max

这将使两个数组具有相同的词,并从大到小对它们进行测试。您检查它们在第一个数组中的顺序是否正确,然后在第二个数组中是否存在。

我很高兴能收到任何评论和反馈,但这种做法确实有效,并且有效

答案 4 :(得分:1)

这是Ruby中PHP实现的like_text的一部分。使用字符串:

def substrings(str)
  (0...str.size).flat_map do |i|
    (i...str.size).map { |j| str[i..j] }
  end
end

def lcs(str1, str2)
  (substrings(str1) & substrings(str2)).max_by(&:size)
end

puts "'#{lcs("hello world i am in the world", "This is the hello world message")}'"

=> 'hello world '

对子字符串的蛮力可能会成为Rust FFI调用的候选对象?我们并没有进行太大的比较,所以它对我们有用。