Como você adiciona uma matriz a outra matriz no Ruby e não acaba com um resultado multidimensional?


474
somearray = ["some", "thing"]

anotherarray = ["another", "thing"]

somearray.push(anotherarray.flatten!)

eu esperava

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

6
Vale a pena dizer (para não lhe causar tristeza, mas porque isso o morderá repetidamente) que sua expectativa é o problema aqui. Matrizes Ruby (diferentemente das matrizes no Perl) não são niveladas automaticamente em contextos como este. Isso não é um bug: é um recurso.
Telêmaco

3
ri Array@flatten!Por que essa pergunta está recebendo tantos votos? O documento é explícito Array#flatten! Achatar-se. Retorna nulo se nenhuma modificação foi feita (ou seja, a matriz não contém sub-matrizes).
yeyo 31/01

7
As perguntas são aprovadas se forem úteis para os usuários. As perguntas mais simples recebem mais votos por serem úteis para a maioria das pessoas.
Ziggy

@ yeyo, você não acha que a operação de nivelamento é gratuita?
Konstantin

A @ Konstantin op não está procurando alternativas ou falando sobre problemas de desempenho, estava esperando um resultado que ele ou ela não obteve porque flatten!não funciona assim. Finalmente, a pergunta reflete um problema lógico e não um problema de otimização. Veja a resposta de pilcrow abaixo para obter mais.
yeyo

Respostas:


713

Você tem uma idéia viável, mas o #flatten!é no lugar errado - ele achata o seu receptor, para que você possa usá-lo para transformar [1, 2, ['foo', 'bar']]em [1,2,'foo','bar'].

Sem dúvida, estou esquecendo algumas abordagens, mas você pode concatenar :

a1.concat a2
a1 + a2              # creates a new array, as does a1 += a2

ou acrescentar / acrescentar :

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

ou emenda :

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

ou acrescentar e achatar :

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

17
bem feito por ser o único (de 5 que posso ver) que realmente apontou o que havia de errado com o código apresentado. 1
Mike Woodhouse

53
Usar push em vez de concat evita a criação de uma terceira matriz; portanto, isso é preferível para matrizes grandes.
phatmann

8
Eu amo o impulso com o asterisco. Muito elegante.
orourkedd

14
@phatmann concatenação com Array#concatnão alocar uma nova matriz, concatenação com Array#+faz
cbliard

5
A única coisa que falta nessa resposta são comparações de benchmark de cada abordagem. +1!
Terra Ashley

206

Você pode apenas usar o +operador!

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]

Você pode ler tudo sobre a classe array aqui: http://ruby-doc.org/core/classes/Array.html


15
O pôster queria saber como concaturar com uma matriz existente, não criar uma nova matriz que fosse a união de duas matrizes.
phatmann

1
Nota: a+= bcria uma nova matriz:c = a = [1,2] ; b = [3,4] ; a += b ; puts c #=> [1,2]
kbrock 08/04

1
@kbrock Correct. Se você estiver lidando com matrizes grandes , consulte o pushmétodo descrito por @pilcrow.
Joshua Pinter

2
lembre-se que +=cria um novo objeto. nesse exemplo, a [1, 2].each_with_object([]) { |number, object| object+=number }matriz vazia []será retornada
Filip Bartuzi 06/11/2015

1
O item adicionado deve ser uma matriz
RousseauAlexandre

66

A abordagem mais limpa é usar o método Arrat # concat ; ele não criará uma nova matriz (diferente da Matriz # +, que fará a mesma coisa, mas criará uma nova matriz).

Diretamente dos documentos ( http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-concat ):

concat (other_ary)

Anexa os elementos de other_ary a self.

assim

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

A matriz # concat não achatará uma matriz multidimensional se for passada como argumento. Você precisará lidar com isso separadamente:

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

Por fim, você pode usar a nossa corelib gem ( https://github.com/corlewsolutions/corelib ), que adiciona ajudantes úteis às classes principais do Ruby. Em particular, temos um método Array # add_all que achatará automaticamente as matrizes multidimensionais antes de executar a concat.


1
Você geralmente deseja imutabilidade, portanto, criar uma nova matriz é uma idéia melhor.
vasilakisfil

5
"Você geralmente quer imutabilidade" não é preciso. Em mais de 20 anos de desenvolvimento de software em tempo integral, trabalhei com todos os tipos de matrizes e coleções diariamente. Às vezes, você modifica uma matriz existente no local. Às vezes você precisa trabalhar com uma nova instância.
Corlew Solutions

35

Método fácil que funciona com a versão Ruby> = 2.0, mas não com versões mais antigas:

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]

2
@Ikuty Essa é de longe a solução mais elegante que encontrei, você pode explicar o que está acontecendo *aqui?
Abhinay 13/08/16

@ O operador plat explode a matriz em elementos, criando assim uma matriz de dimensão única na última linha.
Omar Ali

[*a, *b]falha nas versões mais antigas do ruby, ou seja, 1.8.7. E, por mais que o Ruby queira lhe dizer que está fora da vida, o RHEL6 ainda é mantido, tornando o Ruby 1.8 uma versão de destino muito significativa.
Otheus 24/03

1
Eu não acho que justifique o -1 que esta resposta recebe. Nenhuma versão ruby ​​mencionada pelo OP, a versão ruby ​​mencionada explicitamente na resposta, então ... você quer ser compatível com a versão pré alpha 0.0.0.0.1? Esta é uma das boas soluções, dependendo da versão do rubi
Ludovic Kuty

1
Apenas para apontar que esta resposta é muito 'semelhante' ao JavaScript muito idiomático ES6 no qual você poderia fazer [...array1, ...array2], apenas lembrando que o splatoperador em ruby ​​seria em *vez de .... Isso facilita a lembrança
sandre89

34

Tente isso, ele combinará suas matrizes removendo duplicatas

array1 = ["foo", "bar"]
array2 = ["foo1", "bar1"]

array3 = array1|array2

http://www.ruby-doc.org/core/classes/Array.html

Mais documentação veja "Set Union"


Este é um ou, ele retorna uma matriz sem elementos duplicados. Aqui está um exemplo de como provavelmente não faz o que ele está pedindo, os dois "baz" na primeira matriz são transformados em um e a "barra" na segunda matriz não é adicionada. array1 = ["foo", "bar", "baz", "baz"] array2 = ["foo1", "bar1", "bar"] array3 = array1 | array2 array3 # => ["foo", "bar ", "baz", "foo1", "bar1"]
Joshua Cheek

Ou melhor:array1 |= [ "foo1", "bar1" ] #=> [ "foo", "bar", "foo1", "bar1" ]
Joshua Pinter

33

Aqui estão duas maneiras, observe, neste caso, que a primeira maneira atribui uma nova matriz (traduz-se em 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"]

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

Para anexar ba ae armazenar o resultado em a:

a.push(*b)

ou

a += b

Em qualquer um dos casos, atorna-se:

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

mas no primeiro caso, os elementos de bsão anexados à amatriz existente e, no último caso, as duas matrizes são concatenadas juntas e o resultado é armazenado em a.


2
Observe que a.push(*b)não é exatamente o mesmo que a += b. O primeiro adiciona os novos elementos à matriz existente; o último cria uma nova matriz com todos os elementos e a atribui a. Você pode ver a diferença se fizer algo como aa = asalvar o ref aantes de qualquer método de adição e depois examinar aadepois. No primeiro caso, ele muda com o novo valor de ae, no segundo, permanece inalterado.
Dave Hartnoll

20

(array1 + array2).uniq

Dessa forma, você obtém os elementos do array1 primeiro. Você não receberá duplicatas.


9

Ao elaborar a resposta do @ Pilcrow, a única resposta adequada para grandes matrizes é concat( +), pois é rápida e não aloca um novo objeto a ser coletado como lixo ao operar dentro de um loop.

Aqui está a referência:

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

Resultados:

       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)

Como você pode ver, usando pushthrows um ERRO : stack level too deep (SystemStackError)quando as matrizes são grandes o suficiente.


8

A questão, essencialmente, é "como concatenar matrizes em Ruby". Naturalmente, a resposta é usar concatou +como mencionado em quase todas as respostas.

Uma extensão natural para a pergunta seria "como executar concatenação em linha de matrizes 2D em Ruby". Quando pesquisei "matrizes de concatenação de rubi" no Google, essa pergunta do SO foi o principal resultado, então pensei em deixar minha resposta para essa pergunta (não solicitada, mas relacionada) aqui para a posteridade.


Em alguns aplicativos, convém concatenar duas matrizes 2D em linhas. Algo como,

[[a, b], | [[x],    [[a, b, x],
 [c, d]] |  [y]] =>  [c, d, y]]

Isso é algo como "aumentar" uma matriz. Por exemplo, usei essa técnica para criar uma única matriz de adjacência para representar um gráfico de várias matrizes menores. Sem essa técnica, eu teria que percorrer os componentes de uma maneira que poderia ter sido propensa a erros ou frustrante de se pensar. Eu poderia ter que fazer um each_with_index, por exemplo. Em vez disso, combinei o zíper e o achatamento da seguinte maneira:

# given two multi-dimensional arrays that you want to concatenate row-wise
m1 = [[:a, :b], [:c, :d]]
m2 = [[:x], [:y]]

m1m2 = m1.zip(m2).map(&:flatten)
# => [[:a, :b, :x], [:c, :d, :y]]

8

Apenas outra maneira de fazer isso.

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

flattennivela tudo o mais longe possível, recursivamente. Até matrizes aninhadas. Conseqüentemente, se somearrayou anotherarraycontém matrizes aninhadas, elas também são achatadas. Este é um efeito colateral que geralmente não se destina.
hagello

5

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


Eu não sei sobre eficiência, mas isso funciona para o Ruby 1.8. Geralmente, [*a] + [*b]funciona
Otheus 24/03

Eu não acho que "another" + "thing"isso funcionará como esperado.
Alexis Wilke

5

Se os novos dados puderem ser uma matriz ou escalar, e você desejar impedir que os novos dados sejam aninhados, se for uma matriz, o operador splat é incrível! Ele retorna um escalar para um escalar e uma lista descompactada de argumentos para uma matriz.

1.9.3-p551 :020 > a = [1, 2]
 => [1, 2] 
1.9.3-p551 :021 > b = [3, 4]
 => [3, 4] 
1.9.3-p551 :022 > c = 5
 => 5 
1.9.3-p551 :023 > a.object_id
 => 6617020 
1.9.3-p551 :024 > a.push *b
 => [1, 2, 3, 4] 
1.9.3-p551 :025 > a.object_id
 => 6617020 
1.9.3-p551 :026 > a.push *c
 => [1, 2, 3, 4, 5] 
1.9.3-p551 :027 > a.object_id
 => 6617020 

4

Estou surpreso que ninguém tenha mencionado reduce, o que funciona bem quando você tem uma matriz de matrizes:

lists = [["a", "b"], ["c", "d"]]
flatlist = lists.reduce(:+)  # ["a", "b", "c", "d"]

4
a = ['a', 'b']
b = ['c', 'd']
arr = [a, b].flatten

Isso não remove dups, mas

a|b

remove dups.


Nota: Isso recursivamente aplana todas as matrizes internas também.
Mirodinho

2

Acho mais fácil empurrar ou anexar matrizes e achatá-las no lugar, da seguinte maneira:

somearray = ["some", "thing"]
anotherarray = ["another", "thing"]
somearray.push anotherarray # => ["some", "thing", ["another", "thing"]]
#or
somearray << anotherarray # => ["some", "thing", ["another", "thing"]]
somearray.flatten!  # => ["some", "thing", "another", "thing"]
somearray # => ["some", "thing", "another", "thing"]

2

somearray = ["alguns", "coisa"]

anotherarray = ["outro", "coisa"]

somearray + anotherarray

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.