更简洁的设置计数器,递增和返回的方法?

时间:2015-06-12 17:36:44

标签: ruby string-comparison

我正在处理一个问题,我想要比较两个相等长度的字符串,char by char。对于字符不同的每个索引,我需要将计数器递增1.现在我有这个:

def compute(strand1, strand2)
    raise ArgumentError, "Sequences are different lengths" unless strand1.length == strand2.length

    mutations = 0
    strand1.chars.each_with_index { |nucleotide, index| mutations += 1 if nucleotide != strand2[index] }
    mutations

    end
end

我是这种语言的新手,但这对我来说非常非Ruby。是否有一个单行程可以巩固设置计数器,递增计数器然后返回计数器的过程?

我正在考虑选择所有不匹配的字符然后获得结果数组的大小。但是,据我所知,没有select_with_index方法。我也在研究inject,但似乎无法弄清楚如何在这种情况下应用它。

4 个答案:

答案 0 :(得分:5)

可能最简单的答案就是计算差异:

strand1.chars.zip(strand2.chars).count{|a, b| a != b}

答案 1 :(得分:2)

以下是一些单行:

strand1.size.times.select { |index| strand1[index] != strand2[index] }.size

这不是最好的,因为它会生成一个大小为O(n)的中间数组。

strand1.size.times.inject(0) { |sum, index| sum += 1 if strand1[index] != strand2[index]; sum }

不占用额外的内存,但有点难以阅读。

strand1.chars.each_with_index.count { |x, index| x != strand2[index] }

我顺其自然。感谢user12341234提及count,这是建立在此基础上的。

<强>更新

我的机器上的基准测试与@CarySwoveland获得的结果不同:

                   user     system      total        real
mikej          4.080000   0.320000   4.400000 (  4.408245)
user12341234   3.790000   0.210000   4.000000 (  4.003349)
Nik            2.830000   0.170000   3.000000 (  3.008533)


                   user     system      total        real
mikej          3.590000   0.020000   3.610000 (  3.618018)
user12341234   4.040000   0.140000   4.180000 (  4.183357)
lazy user      4.250000   0.240000   4.490000 (  4.497161)
Nik            2.790000   0.010000   2.800000 (  2.808378)

这并不是要指出我的代码具有更好的性能,而是提到环境在选择任何特定的实现方法中起着重要作用。

我在具有足够内存的12-Core i7-3930K上使用本机3.13.0-24-generic#47-Ubuntu SMP x64运行此功能。

答案 2 :(得分:2)

这是一个刚刚延伸的评论,所以请不要赞成(downvotes还可以)。

先生们:启动引擎!

测试问题

def random_string(n, selection)
  n.times.each_with_object('') { |_,s| s << selection.sample }
end

n = 5_000_000
a = ('a'..'e').to_a
s1 = random_string(n,a)
s2 = random_string(n,a)

基准代码

require 'benchmark'

Benchmark.bm(12) do |x|

  x.report('mikej') do
    s1.chars.each_with_index.inject(0) {|c,(n,i)| n==s2[i] ? c : c+1}
  end

  x.report('user12341234') do
    s1.chars.zip(s2.chars).count{|a,b| a != b }
  end

  x.report('lazy user') do
    s1.chars.zip(s2.chars).lazy.count{|a,b| a != b }
  end

  x.report('Nik') do
    s1.chars.each_with_index.count { |x,i| x != s2[i] }
  end
end

<强>结果

mikej          6.220000   0.430000   6.650000 (  6.651575)
user12341234   6.600000   0.900000   7.500000 (  7.504499)
lazy user      7.460000   7.800000  15.260000 ( 15.255265)
Nik            6.140000   3.080000   9.220000 (  9.225023)

                  user     system      total        real
mikej          6.190000   0.470000   6.660000 (  6.662569)
user12341234   6.720000   0.500000   7.220000 (  7.223716)
lazy user      7.250000   7.110000  14.360000 ( 14.356845)
Nik            5.690000   0.920000   6.610000 (  6.621889)

[编辑:我添加了'{user}个版本''user12341234'。与lazy版本的枚举器一样,在使用的内存量和执行时间之间存在权衡。我做了好几次。在这里,我报告两个典型的结果。 'mikej'和'user12341234'的变化很小,lazy的变化较大,lazy user的变化较大。]

答案 3 :(得分:1)

inject确实可以用一个单行程来做到这一点,所以你走在正确的轨道上:

strand1.chars.each_with_index.inject(0) { |count, (nucleotide, index)|
  nucleotide == strand2[index] ? count : count + 1 }

我们从初始值0开始,然后如果2个字母相同,我们只返回累加器值的当前值,如果字母不同,我们加1。

此外,请注意,each_with_index在没有阻止的情况下被调用。当像这样调用时,它返回一个枚举器对象。这允许我们使用其他枚举器方法之一(在本例中为inject)与each_with_index返回的值和索引对,允许each_with_indexinject的功能要合并。

编辑:看着它,user12341234's解决方案是一种很好的方式。善用zip!我可能会这样做。