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

More concise way of setting a counter, incrementing, and returning?

我正在研究一个问题,我想逐个字符地比较两个长度相等的字符串。对于字符不同的每个索引,我需要将计数器加 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 但似乎无法弄清楚我将如何在这种情况下应用它。

inject 确实可以通过一种方式用一条线来做到这一点,所以你在正确的轨道上:

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

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

另外,请注意这里调用 each_with_index 时没有阻塞。当这样调用时,它 return 是一个枚举器对象。这让我们可以使用其他枚举器方法之一(在本例中为 inject),值和索引对 return 由 each_with_index 编辑,允许 each_with_indexinject待合并。


编辑:看着它,user12341234's 解决方案是一个很好的方法。好好利用zip!我可能会同意。

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

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

这里有一些单行:

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)

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

我 运行 在本机 3.13.0-24-generic #47-Ubuntu SMP x64 上,12 核 i7-3930K 上有足够的内存。

这是一个刚刚扩展的评论,所以请不要赞成票(反对票也可以)。

先生们:发动你们的引擎!

测试题

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)

[编辑:我添加了 'user12341234' 的 lazy 版本。与 lazy 版本的枚举器的情况一样,在使用的内存量和执行时间之间存在权衡。我跑了几次。这里我报告两个典型的结果。 'mikej' 和 'user12341234' 的差异很小,lazy user 的差异更大,Nik 的差异很大。]