如何在Ruby中有效地连接多个数组?

时间:2016-11-09 15:30:58

标签: arrays ruby concatenation

我只想在Ruby中连接多个数组,并且找不到令人满意的方法。

示例输入:

foo = [1, 2, 3]
bar = [4, 5, 6]
baz = [7, 8, 9]

预期结果(不修改现有数组)

[1, 2, 3, 4, 5, 6, 7, 8, 9]

我的实际数组很多更大,所以我对一个有效的解决方案感兴趣。可能还有三个以上的数组,因此首选短语法。

到目前为止我尝试了什么

  • foo + bar + baz是显而易见的,它简明扼要。但它被评估为(foo + bar) + baz。换句话说:它创建了一个中间数组[1, 2, 3, 4, 5, 6],它在整个操作后被抛弃。如documentation中所述:

      

    在数组上重复使用+=可能效率很低

  • [*foo, *bar, *baz]基本上内联了对大型数组效率不高的元素。它看起来更像是对我的黑客攻击。

  • [foo, bar, baz].flatten(1)似乎比上述情况更糟糕,表现明智。

  • [].concat(foo).concat(bar).concat(baz)是最快的,但它看起来很麻烦,需要多次方法调用。

这种基本任务不应该有简单的类方法吗?类似的东西:

Array.concat(foo, bar, baz)

我错过了一些明显的东西吗?

3 个答案:

答案 0 :(得分:16)

如果您已经确定多个连接是最快的方法,则可以使用reduce编写更好的方法:

[foo, bar, baz].reduce([], :concat)

答案 1 :(得分:7)

我创建了另一个基准测试,比较+concat和自定义C扩展名,其中包含可变数量的数组。

结果

  • C扩展总是最快,大约比concat
  • 快2-3倍 如果连接多个数组
  • plus变得非常慢

结论

虽然“2-3x”听起来像是一个巨大的改进,但绝对数字只有几毫秒。我不希望调整阵列大小,因此我期待更大的差异,但这显然不是一个重要因素。

IMO,concat是一个不错的表演者,我认为没有迫切需要C扩展。

我的测试数组包含nil个值。其他元素似乎没有产生不同的结果(相对而言)。

我没有包含flat_map,因为它等同于concat

Concatenating 3 arrays of size 100 (10000 times)
                 user     system      total        real
plus         0.020000   0.000000   0.020000 (  0.027927)
concat       0.020000   0.010000   0.030000 (  0.033204)
c_extension  0.010000   0.010000   0.020000 (  0.010727)

Concatenating 10 arrays of size 100 (10000 times)
                 user     system      total        real
plus         0.110000   0.070000   0.180000 (  0.180417)
concat       0.050000   0.020000   0.070000 (  0.065299)
c_extension  0.010000   0.010000   0.020000 (  0.025475)

Concatenating 10 arrays of size 1000 (10000 times)
                 user     system      total        real
plus         0.690000   0.560000   1.250000 (  1.252319)
concat       0.180000   0.130000   0.310000 (  0.303365)
c_extension  0.120000   0.120000   0.240000 (  0.248589)

plus被排除在以下结果

之外
Concatenating 10 arrays of size 100000 (100 times)
                 user     system      total        real
concat       0.220000   0.340000   0.560000 (  0.568730)
c_extension  0.130000   0.150000   0.280000 (  0.281354)

Concatenating 100 arrays of size 10000 (100 times)
                 user     system      total        real
concat       0.210000   0.320000   0.530000 (  0.519030)
c_extension  0.160000   0.140000   0.300000 (  0.304751)

Concatenating 1000 arrays of size 1000 (100 times)
                 user     system      total        real
concat       0.240000   0.330000   0.570000 (  0.563511)
c_extension  0.150000   0.120000   0.270000 (  0.283546)

Concatenating 10000 arrays of size 100 (100 times)
                 user     system      total        real
concat       0.330000   0.310000   0.640000 (  0.643987)
c_extension  0.170000   0.120000   0.290000 (  0.286489)

Concatenating 100000 arrays of size 10 (100 times)
                 user     system      total        real
concat       1.300000   0.340000   1.640000 (  1.648687)
c_extension  0.310000   0.150000   0.460000 (  0.458214)

测试代码:

require 'benchmark'

values = [
  # small
  { count: 3,      size: 100,    n: 10000 },
  { count: 10,     size: 100,    n: 10000 },
  { count: 10,     size: 1000,   n: 10000 },
  # large
  { count: 10,      size: 100000, n: 100 },
  { count: 100,     size: 10000,  n: 100 },
  { count: 1000,    size: 1000,   n: 100 },
  { count: 10000,   size: 100,    n: 100 },
  { count: 100000,  size: 10,     n: 100 }
]

values.each_with_index do |h, i|
  count, size, n = h.values_at(:count, :size, :n)
  arrays = Array.new(count) { Array.new(size) }

  puts
  puts "Concatenating #{count} arrays of size #{size} (#{n} times)"
  Benchmark.bm(10) do |r|
    r.report('plus')        { n.times { arrays.reduce(:+) } } if i < 3
    r.report('concat')      { n.times { arrays.reduce([], :concat) } }
    r.report('c_extension') { n.times { Array.concat(*arrays) } }
  end
end

C扩展:(实际上是一个补丁,我已将其添加到Ruby的array.c

VALUE
rb_ary_s_concat(int argc, VALUE *argv, VALUE klass)
{
  VALUE ary;
  long len = 0, i;
  for (i=0; i<argc; i++) {
    argv[i] = to_ary(argv[i]);
    len += RARRAY_LEN(argv[i]);
  }
  ary = rb_ary_new2(len);
  long beg = 0;
  for (i=0; i<argc; i++) {
    ary_memcpy(ary, beg, RARRAY_LEN(argv[i]), RARRAY_CONST_PTR(argv[i]));
    beg += RARRAY_LEN(argv[i]);
  }
  ARY_SET_LEN(ary, len);
  return ary;
}

您必须在Init_Array中通过以下方式注册此方法:

rb_define_singleton_method(rb_cArray, "concat", rb_ary_s_concat, -1);

答案 2 :(得分:3)

一些基准测试和简单+是最有效的。 所以我建议忽略数组的中间创建。

你可以像这样添加一个新的方法concat_all到Array,但你也必须考虑混合和多维数组。

class Array
  def concat_all 
    self.reduce([], :+)
  end
end
[a, b, c].concat_all # a huge array
[a, b, c].concat_all.length #300000

这是我的基准

require 'Benchmark'
N = 1000

class Array
  def concat_all 
    self.reduce([], :+)
  end
  def concat_all2
    # just a quick test with fixed numbers for the fill method Stephan proposes but in Ruby itself
    d = Array.new(300_000)
    d[0..99999] = self[0]
    d[100_000..199999] = self[1]
    d[200_000..299999] = self[2]
    d
  end
  def concat_all3
    self.flatten
  end
end

# small arrays
a = (1..10).to_a
b = (11..20).to_a
c = (21..30).to_a

Benchmark.bm do |r|
  r.report('plus       ')  { N.times { a + b + c }}
  r.report('concat     ') { N.times { [].concat(a).concat(b).concat(c) }}
  r.report('push       ') { N.times { [].push(*a).push(*b).push(*c) }}
  r.report('<<         ') { N.times { ([] << a << b << c).flatten}}
  r.report('splash     ') { N.times {[*a, *b, *c]} }
  r.report('concat_all ')  { N.times { [a, b, c].concat_all }}
  r.report('concat_all3')  { N.times { [a, b, c].concat_all3 }}
  r.report('flat_map   ') { N.times {[a, b, c].flat_map(&:itself)} }
end

#large arrays
a = (1..100_000).to_a
b = (100_001..200_000).to_a
c = (200_001..300_000).to_a

Benchmark.bm do |r|
  r.report('plus       ')  { N.times { a + b + c }}
  r.report('concat     ') { N.times { [].concat(a).concat(b).concat(c) }}
  r.report('push       ') { N.times { [].push(*a).push(*b).push(*c) }}
  r.report('<<         ') { N.times { ([] << a << b << c).flatten}}
  r.report('splash     ') { N.times {[*a, *b, *c]} }
  r.report('concat_all ')  { N.times { [a, b, c].concat_all }}
  r.report('concat_all2')  { N.times { [a, b, c].concat_all2 }}
  r.report('concat_all3')  { N.times { [a, b, c].concat_all3 }}
  r.report('flat_map   ') { N.times {[a, b, c].flat_map(&:itself)} }
end

结果

# results for small arrays
       user     system      total        real
plus         0.000000   0.000000   0.000000 (  0.000416)
concat       0.000000   0.000000   0.000000 (  0.000592)
push         0.000000   0.000000   0.000000 (  0.000441)
<<           0.000000   0.000000   0.000000 (  0.003387)
splash       0.000000   0.000000   0.000000 (  0.000789)
concat_all   0.000000   0.000000   0.000000 (  0.001480)
concat_all3  0.016000   0.000000   0.016000 (  0.003496)
flat_map     0.000000   0.000000   0.000000 (  0.001036)

# results for big arrays
       user     system      total        real
plus         0.686000   0.671000   1.357000 (  1.351171)
concat       0.890000   0.733000   1.623000 (  1.630155)
push         1.466000   0.624000   2.090000 (  2.092684)
<<          23.837000   1.045000  24.882000 ( 24.885238)
splash       1.029000   1.264000   2.293000 (  2.332560)
concat_all   0.687000   0.967000   1.654000 (  1.709321)
concat_all2  0.936000   0.780000   1.716000 (  1.730428)
concat_all3 24.242000   0.998000  25.240000 ( 25.278264)
flat_map     0.780000   0.765000   1.545000 (  1.551654)