在Ruby中如何将一个数组添加到另一个数组而不产生多维结果?

570

我尝试了:

somearray = ["some", "thing"]
anotherarray = ["another", "thing"]
somearray.push(anotherarray.flatten!)

我原本期望

["some", "thing", "another", "thing"]

但实际得到的是

["some", "thing", nil]

6
值得一提的是(不是为了责备你,而是因为这会反复困扰你),你的期望值是问题所在。Ruby数组(与Perl中的数组不同)在这种情况下不会自动展开。这不是一个bug,而是一个特性。 - Telemachus
4
为什么这个问题得到了这么多投票?文档已经明确说明Array#flatten!是“原地”将数组压平。如果没有进行修改(即数组不包含子数组),则返回nil。 - yeyo
14
如果问题对用户有用,它们会得到赞同。最简单的问题获得最多的赞同,因为它们对大多数人都有帮助。 - Ziggy
@yeyo,你不觉得展平操作是免费的吗? - Konstantin
@Konstantin op并不是在寻找替代方案或讨论性能问题,op期望得到一个结果,但由于flatten!的工作方式不同,所以没有得到。最后,这个问题反映了一个逻辑问题而不是一个优化问题。请参见下面pilcrow的答案获取更多信息。 - yeyo
@yeyo,实际问题标题与问题标题并不完全相关。我认为OP实际上想要将一个数组添加到另一个数组中,因此我认为值得向他和所有新手澄清dos和donts。你知道,SO问题标题在SE中有很大的权重;) - Konstantin
18个回答

824
你有一个可行的想法,但是#flatten! 的位置不正确--它会使其接收者扁平化,所以你可以使用它将[1, 2, ['foo', 'bar']]转换为[1,2,'foo','bar']
我肯定忘记了一些方法,但是你可以连接
a1.concat a2
a1 + a2              # creates a new array, as does a1 += a2

前置/后置添加:

a1.push(*a2)         # note the asterisk
a2.unshift(*a1)      # note the asterisk, and that a2 is the receiver

或者 splice

a1[a1.length, 0] = a2
a1[a1.length..0] = a2
a1.insert(a1.length, *a2)

或者追加和展平

(a1 << a2).flatten!  # a call to #flatten instead would return a new array

20
恭喜你作为五个人中唯一一个指出所呈现的代码有问题的人,做得很好。+1 - Mike Woodhouse
59
使用 push 而不是 concat 可以避免创建第三个数组,因此对于大型数组来说这是更好的选择。 - phatmann
9
我很喜欢带星号的推送,非常优雅。 - orourkedd
18
使用Array#concat进行数组连接操作时不会创建新的数组,而使用Array#+进行连接操作则会创建一个新的数组。 - cbliard
7
这个回答唯一缺少的是每种方法的基准比较。+1! - Terra Ashley
显示剩余11条评论

229
你可以直接使用+操作符!
irb(main):001:0> a = [1,2]
=> [1, 2]
irb(main):002:0> b = [3,4]
=> [3, 4]
irb(main):003:0> a + b
=> [1, 2, 3, 4]

您可以在这里阅读有关数组类的所有信息: http://ruby-doc.org/core/classes/Array.html


22
这位发帖者想知道如何将内容连接到现有数组中,而不是创建一个由两个数组组成的新数组。 - phatmann
1
注:a+= b将创建一个新数组:c = a = [1,2] ; b = [3,4] ; a += b ; puts c #=> [1,2] - kbrock
1
@kbrock 正确。如果处理的是 大型 数组,您需要查看由 @pilcrow 描述的 push 方法。 - Joshua Pinter
2
请记住,+= 会创建一个新的对象。在这个例子中 [1, 2].each_with_object([]) { |number, object| object+=number },将返回空数组 [] - Filip Bartuzi
1
添加的项必须是一个数组。 - alexandre-rousseau
显示剩余2条评论

86

最干净的方法是使用Array#concat方法;它不会创建一个新的数组(与Array#+不同,后者会做同样的事情但会创建一个新的数组)。

来自文档的原话(http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-concat):

concat(other_ary)

将other_ary的元素附加到self上。

因此:

[1,2].concat([3,4])  #=> [1,2,3,4]  

Array#concat如果作为参数传入多维数组,它将不会扁平化该数组。您需要单独处理这个问题:

arr= [3,[4,5]]
arr= arr.flatten   #=> [3,4,5]
[1,2].concat(arr)  #=> [1,2,3,4,5]
最后,您可以使用我们的corelib gem (https://github.com/corlewsolutions/corelib),它为Ruby核心类添加了有用的辅助函数。特别是我们有一个Array#add_all方法,它会在执行连接之前自动展平多维数组。

1
通常情况下,您希望使用不可变性,因此创建一个新的数组是更好的选择。 - vasilakisfil
10
“通常你想要不可变性”这句话并不准确。在我从事全职软件开发的20多年中,我每天都与各种数组和集合打交道。有时需要就地修改现有数组,有时需要使用新实例进行操作。 - WebDev

46
a = ["some", "thing"]
b = ["another", "thing"]

ab相加并将结果存储在a中:
a.push(*b)

或者

a += b

无论哪种情况,a都会变成:
["some", "thing", "another", "thing"]

但在前一种情况下,b 的元素会被追加到现有的 a 数组中,在后一种情况下,这两个数组会被连接在一起,并将结果存储在 a 中。


4
请注意,a.push(*b)a += b并不完全相同。前者将新元素添加到现有数组中,后者创建一个包含所有元素的新数组,并将其分配给a。如果您在任一附加方法之前执行aa = a以保存对a的引用,然后检查aa,您可以看到差异。在前一种情况下,它随着a的新值而更改,而在后一种情况下,它保持不变。 - Dave Hartnoll
2
请注意,@DaveHartnoll指出的对于each_with_object使用等非常重要。使用each_with_object([]) { |thing, result| result += [thing] }是行不通的,而使用push方法则可行。 - ragurney

44

这是一种简单的方法,适用于Ruby版本>= 2.0,但不适用于旧版本:

irb(main):001:0> a=[1,2]
=> [1, 2]
irb(main):003:0> b=[3,4]
=> [3, 4]
irb(main):002:0> c=[5,6]
=> [5, 6]
irb(main):004:0> [*a,*b,*c]
=> [1, 2, 3, 4, 5, 6]

3
这是我找到的迄今为止最优雅的解决方案,你能否解释一下这里的 * 是什么意思? 这里的 * 是一个指针符号,用于解引用指针。它可以访问指针所指向的值。 - Abhinay
@Abhinay,平台运算符将数组展开为元素,从而在最后一行创建一个单维数组。 - Omar Ali
[*a, *b] 在旧版本的 Ruby(即 1.8.7)中会失败。尽管 Ruby 想告诉你它已经过时了,但 RHEL6 仍在维护中,使得 Ruby 1.8 仍然是一个重要的目标版本。 - Otheus
4
我认为这并不能证明答案应该被评为-1分。原问题中并没有提到Ruby的版本,而回答中明确提到了Ruby的版本,所以......你想要向后兼容早于alpha 0.0.0.0.1版本的吗?这是一个好的解决方案,具体取决于Ruby的版本。 - Ludovic Kuty
4
只是想指出这个答案与非常惯用的JavaScript ES6非常“相似”,你可以使用[...array1, ...array2]的方式,只需记住在Ruby中的splat运算符是*而不是...。这样更容易记住。 - sandre89

41

这里有两种方式,注意在这种情况下第一种方式会分配一个新数组(翻译为 somearray = somearray + anotherarray)。

somearray = ["some", "thing"]

anotherarray = ["another", "thing"]

somearray += anotherarray # => ["some", "thing", "another", "thing"]

somearray = ["some", "thing"]
somearray.concat anotherarray # => ["some", "thing", "another", "thing"]

38

1
这是一个或运算,它返回一个没有重复元素的数组。以下是一个示例,说明它可能不会执行他所要求的操作:第一个数组中的两个“baz”被合并成了一个,“bar”在第二个数组中没有被添加。array1 = ["foo", "bar" , "baz" , "baz" ] array2 = ["foo1", "bar1" , "bar" ]array3 = array1|array2 array3 # => ["foo", "bar", "baz", "foo1", "bar1"] - Joshua Cheek
1
甚至更好的是:array1 |= [ "foo1", "bar1" ] #=> [ "foo", "bar", "foo1", "bar1" ] - Joshua Pinter

22
(array1 + array2).uniq

这样,您将首先获取array1的元素。您将不会获得任何重复项。


13

在 @Pilcrow 的回答中进一步阐述,对于大型数组唯一合适的答案是使用 concat (+),因为它快速且在循环内操作时不会分配需要进行垃圾回收的新对象。

以下是基准测试:

require 'benchmark'

huge_ary_1 = Array.new(1_000_000) { rand(5_000_000..30_000_00) }

huge_ary_2 = Array.new(1_000_000) { rand(35_000_000..55_000_00) }

Benchmark.bm do |bm|
  p '-------------------CONCAT ----------------'
  bm.report { huge_ary_1.concat(huge_ary_2) }

  p '------------------- PUSH ----------------'
  bm.report { huge_ary_1.push(*huge_ary_2)  }
end

结果:

       user     system      total        real
"-------------------CONCAT ----------------"
  0.000000   0.000000   0.000000 (  0.009388)
"------------------- PUSH ----------------"
  example/array_concat_vs_push.rb:13:in `block (2 levels) in <main>': stack level too deep (SystemStackError)

从下面的代码中可以看到,使用push操作在数组足够大时会引发一个错误stack level too deep (SystemStackError)


13
["some", "thing"] + ["another", "thing"]

我不知道效率如何,但这适用于Ruby 1.8。一般来说,[*a] + [*b]是可行的。 - Otheus
3
我觉得将"another" + "thing"相加不会按预期工作。 - Alexis Wilke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接