比较相等长度的字符串并注意差异发生的位置

时间:2011-04-29 07:22:07

标签: ruby algorithm string

给出两个长度相等的字符串

s1 = "ACCT"
s2 = "ATCT"

我想找出字符串不同的位置。所以我做到了这一点。 (请建议一个更好的方法。我打赌应该有)

z= seq1.chars.zip(seq2.chars).each_with_index.map{|(s1,s2),index| index+1 if s1!=s2}.compact

z是两个字符串不同的位置数组。在这种情况下,z返回2

想象一下,我添加了一个新字符串

s3 = "AGCT"

我希望将其与其他人进行比较,看看3个字符串的不同之处。我们可以采用与上述相同的方法,但这一次

s1.chars.zip(s2.chars,s3.chars)

返回一个数组数组。给定两个字符串我只是比较两个字符的相等性,但随着我添加更多的字符串,它开始变得势不可挡,随着字符串变得更长。

#=> [["A", "A", "A"], ["C", "T", "G"], ["C", "C", "C"], ["T", "T", "T"]]

正在运行

s1.chars.zip(s2.chars,s3.chars).each_with_index.map{|item| item.uniq}

 #=> [["A"], ["C", "T", "G"], ["C"], ["T"]] 

可以帮助减少冗余并返回完全相同的位置(大小为1的非空子阵列)。然后我可以打印出尺寸大于>的子阵列的索引和内容。 1.

s1.chars.zip(s2.chars,s3.chars,s4.chars).each_with_index.map{|item| item.uniq}.each_with_index.map{|a,index| [index+1,a] unless a.size== 1}.compact.map{|h| Hash[*h]}
#=> [{2=>["C", "T", "G"]}]

我觉得随着字符串数量的增加和字符串长度变长,这会停止或变慢。有什么方法可以最佳地做到这一点? 谢谢。

3 个答案:

答案 0 :(得分:2)

这是我开始的地方。我故意使用不同的字符串来更容易地看到差异:

str1 = 'jackdaws love my giant sphinx of quartz'
str2 = 'jackdaws l0ve my gi4nt sphinx 0f qu4rtz'

获取第一个字符串的字符:

str1.chars.with_index.to_a - str2.chars.with_index.to_a
=> [["o", 10], ["a", 19], ["o", 30], ["a", 35]]

获取第二个字符串的字符:

str2.chars.with_index.to_a - str1.chars.with_index.to_a
=> [["0", 10], ["4", 19], ["0", 30], ["4", 35]]

随着琴弦越来越大,会有一点慢,但这并不坏。


编辑:添加了更多信息。

如果您有任意数量的字符串,并且需要将它们全部进行比较,请使用Array#combination

str1 = 'ACCT'
str2 = 'ATCT'
str3 = 'AGCT'

require 'pp'

pp [str1, str2, str3].combination(2).to_a
>> [["ACCT", "ATCT"], ["ACCT", "AGCT"], ["ATCT", "AGCT"]]

在上面的输出中,您可以看到combination在数组中循环,返回数组元素的各种n大小的组合。

pp [str1, str2, str3].combination(2).map{ |a,b| a.chars.with_index.to_a - b.chars.with_index.to_a }
>> [[["C", 1]], [["C", 1]], [["T", 1]]]

使用组合输出,您可以循环遍历数组,将所有元素相互比较。因此,在上面返回的数组中,在“ACCT”和“ATCT”对中,'C'是两者之间的差异,位于字符串中的位置1。同样,在“ACCT”和“AGCT”中,差异为“C”,位置为1.最后,对于“ATCT”和“AGCT”,它在位置1处为“T”。

因为我们已经在较长的字符串示例中看到代码将返回多个已更改的字符,所以这应该会让您非常接近。

答案 1 :(得分:2)

解决方案1 ​​

strings = %w[ACCT ATCT AGCT]

首先,加入字符串,并为每个字符创建所有位置的哈希值。

joined = strings.join
positions = (0...joined.length).group_by{|i| joined[i]}
# => {"A"=>[0, 4, 8], "C"=>[1, 2, 6, 10], "T"=>[3, 5, 7, 11], "G"=>[9]}

然后,将索引按每个字符串中的相应位置分组,删除那些重复次数与字符串数一样多的索引。此部分是an algorithm that Jorg suggests的变体。

length = strings.first.length
n = strings.length
diff = Hash[*positions.map{|k, v| 
  [k, v.group_by{|i| i % length}.reject{|i, is| is.length == n}.keys]
}]

这将提供类似的内容:

diff
# => {"A"=>[], "C"=>[1], "T"=>[1], "G"=>[1]}

表示“A”出现在所有字符串的相同位置,“C”,“T”和“G”在字符串的位置1(从0开始计数)不同。

如果您只是想知道字符串不同的位置,请执行

diff["G"] + diff["A"] + diff["C"] + diff["T"]
# or diff["G"] + diff["A"] + diff["C"]
# => [1]

解决方案2

请注意,通过维护成对比较失败的索引数组,并继续向其添加索引,将s1与其余部分进行比较(s2s3 ,. ..)就足够了。

length = s1.length
diff = []
[s2, s3, ...].each{|s| diff += (0...length).reject{|i| s1[i] == s[i]}}

更详细的解释

假设

s1 = 'GGGGGGGGG'
s2 = 'GGGCGGCGG'
s3 = 'GGGAGGCGG'

比较s1s2之后,我们会有一组索引[3, 6]来表示它们的不同之处。现在,当我们添加s3时,我们是将它与s1还是s2进行比较并不重要,因为s1[i]s2[i]是不同的,然后i已经包含在集合[3, 6]中,因此它们中的任何一个是否与s3[i]不同并且要添加i并没有区别集合。另一方面,如果s1[i]s2[i]相同,那么我们与s3[i]中的哪一个相比也没有区别。因此,s1s2s3,......的成对比较就足够了。

答案 2 :(得分:-1)

您几乎肯定不希望使用自己的代码进行此分析。相反,您希望将其移交给现有的multiple sequence alignment工具,例如Clustal

我意识到这不是你问题的答案,但我希望这是你问题的解决方案!