¿Cómo agrega una matriz a otra matriz en Ruby y no termina con un resultado multidimensional?


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

anotherarray = ["another", "thing"]

somearray.push(anotherarray.flatten!)

Esperaba

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

66
Vale la pena decir (no para darte pena, sino porque te morderá una y otra vez) que tu expectativa es el problema aquí. Las matrices de rubíes (a diferencia de las matrices en Perl) no se aplanan automáticamente en contextos como este. Esto no es un error: es una característica.
Telémaco el

3
ri Array@flatten!¿Por qué esta pregunta está obteniendo tantos votos? El documento es explícito Se Array#flatten! aplana en su lugar. Devuelve nulo si no se realizaron modificaciones (es decir, la matriz no contiene
submatrices

77
Las preguntas obtienen votos positivos si son útiles para los usuarios. Las preguntas más simples obtienen la mayor cantidad de votos porque son útiles para la mayoría de las personas.
Ziggy

@yeyo, ¿no crees que la operación de aplanar es gratis?
Konstantin

@Konstantin op no está buscando alternativas o está hablando de problemas de rendimiento, estaba esperando un resultado que no obtuvo porque flatten!no funciona así. Finalmente, la pregunta refleja un problema lógico en lugar de un problema de optimización. Vea la respuesta de Pilcrow a continuación para obtener más información.
yeyo

Respuestas:


714

Tienes una idea viable, pero #flatten!está en el lugar equivocado: aplana su receptor, por lo que puedes usarlo para convertirlo [1, 2, ['foo', 'bar']]en[1,2,'foo','bar'] .

Sin duda, estoy olvidando algunos enfoques, pero puedes concatenar :

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

o anteponer / agregar :

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

o empalme :

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

o anexar y aplanar :

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

17
bien hecho por ser el único (de 5 que puedo ver) que realmente señaló lo que estaba mal con el código presentado. +1
Mike Woodhouse

53
Usar push en lugar de concat evita la creación de una tercera matriz, por lo que se prefiere para matrices grandes.
phatmann

8
Me encanta el empujón con el asterisco. Muy elegante.
orourkedd

14
@phatmann Concatenación con Array#concatno asigna una nueva matriz, Concatenación con Array#+
cbliard

55
Lo único que falta esta respuesta son las comparaciones de referencia de cada enfoque. +1!
Terra Ashley

206

¡Solo puedes usar el +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]

Puede leer todo sobre la clase de matriz aquí: http://ruby-doc.org/core/classes/Array.html


15
El póster quería saber cómo concatenar a una matriz existente, no crear una nueva matriz que fuera la unión de dos matrices.
phatmann

1
Nota: a+= bcrea una nueva matriz:c = a = [1,2] ; b = [3,4] ; a += b ; puts c #=> [1,2]
kbrock

1
@kbrock Correcto. Si se trata de matrices grandes , querrá ver el pushmétodo descrito por @pilcrow.
Joshua Pinter

2
recuerda que +=crea un nuevo objeto. en tal ejemplo , se devolverá una [1, 2].each_with_object([]) { |number, object| object+=number }matriz vacía[]
Filip Bartuzi, el

1
El artículo agregado debe ser una matriz
RousseauAlexandre

66

El enfoque más limpio es utilizar el método Array # concat ; no creará una nueva matriz (a diferencia de Array # + que hará lo mismo pero creará una nueva matriz).

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

concat (otro_ary)

Agrega los elementos de other_ary a self.

Entonces

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

La matriz # concat no aplanará una matriz multidimensional si se pasa como argumento. Tendrá que manejar eso por separado:

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

Por último, puede usar nuestra gema corelib ( https://github.com/corlewsolutions/corelib ) que agrega ayudantes útiles a las clases principales de Ruby. En particular, tenemos un método Array # add_all que aplanará automáticamente las matrices multidimensionales antes de ejecutar el concat.


1
Por lo general, desea la inmutabilidad, por lo que crear una nueva matriz es una mejor idea.
vasilakisfil

55
"Usualmente quieres inmutabilidad" no es exacto. En más de 20 años de desarrollo de software a tiempo completo, he trabajado con todo tipo de matrices y colecciones a diario. A veces modifica una matriz existente en su lugar. A veces necesitas trabajar con una nueva instancia.
Corlew Solutions

35

Método fácil que funciona con la versión Ruby> = 2.0 pero no con versiones anteriores:

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 Esta es, con mucho, la solución más elegante que encontré, ¿podría explicar qué sucede *aquí?
Abhinay

@Abhinay, el operador de plataforma explota la matriz en elementos creando así una matriz unidimensional en la última línea.
Omar Ali

[*a, *b]falla para versiones anteriores de ruby, es decir, 1.8.7. Y por mucho que Ruby quiera decirte que está fuera de la vida, RHEL6 aún se mantiene, lo que hace que Ruby 1.8 sea una versión objetivo significativa.
Oteo

1
No creo que eso justifique el -1 que obtiene esta respuesta. Ninguna versión de ruby ​​mencionada por OP, la versión de ruby ​​mencionada explícitamente en la respuesta, entonces ... ¿Desea ser compatible con versiones anteriores a alfa 0.0.0.0.1? Esta es una de las buenas soluciones, dependiendo de la versión rubí
Ludovic Kuty el

1
Solo para señalar que esta respuesta es muy 'similar' a la muy idiomática JavaScript ES6 en la que podría hacerlo [...array1, ...array2], solo recordando que el splatoperador en ruby ​​sería en *lugar de .... Hace que sea más fácil de recordar
sandre89

34

Prueba esto, combinará tus matrices eliminando duplicados

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

array3 = array1|array2

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

Más documentación mira en "Set Union"


Este es un o, devuelve una matriz sin elementos duplicados, aquí hay un ejemplo de cómo probablemente no hace lo que está pidiendo, los dos "baz" en la primera matriz se convierten en uno y la "barra" en la segunda matriz no se agrega. array1 = ["foo", "bar", "baz", "baz"] array2 = ["foo1", "bar1", "bar"] array3 = array1 | array2 array3 # => ["foo", "bar "," baz "," foo1 "," bar1 "]
Joshua Cheek

O incluso mejor:array1 |= [ "foo1", "bar1" ] #=> [ "foo", "bar", "foo1", "bar1" ]
Joshua Pinter el

33

Aquí hay dos formas, observe en este caso que la primera forma asigna una nueva matriz (se traduce en 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 añadir ba ay almacenar el resultado en a:

a.push(*b)

o

a += b

En cualquier caso, se aconvierte en:

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

pero en el primer caso, los elementos de bse añaden a la amatriz existente , y en el último caso las dos matrices se concatenan juntas y el resultado se almacena en a.


2
Tenga en cuenta que a.push(*b)no es exactamente lo mismo que a += b. El primero agrega los nuevos elementos a la matriz existente; este último crea una nueva matriz con todos los elementos y se los asigna a. Puede ver la diferencia si hace algo como aa = aguardar la referencia aantes de cualquiera de los métodos de agregar y luego examinar aadespués. En el primer caso, cambia con el nuevo valor de a, y en el segundo permanece sin cambios.
Dave Hartnoll el

20

(array1 + array2).uniq

De esta manera obtienes primero los elementos array1. No obtendrás duplicados.


9

Al elaborar la respuesta de @ Pilcrow, la única respuesta adecuada para grandes matrices es concat(+ ) ya que es rápida y no asigna un nuevo objeto para ser recolectado como basura cuando se opera dentro de un bucle.

Aquí está el punto de referencia:

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 puede ver, el uso pusharroja un ERROR : stack level too deep (SystemStackError)cuando las matrices son lo suficientemente grandes.


8

La pregunta, esencialmente, es "cómo concatenar matrices en Ruby". Naturalmente, la respuesta es usar concato+ como se menciona en casi todas las respuestas.

Una extensión natural de la pregunta sería "cómo realizar una concatenación de filas 2D de matrices 2D en Ruby". Cuando busqué en Google "matrices de concatenación de rubíes", esta pregunta SO fue el resultado principal, así que pensé en dejar mi respuesta a esa pregunta (no formulada pero relacionada) aquí para la posteridad.


En algunas aplicaciones, es posible que desee "concatenar" dos matrices 2D en fila. Algo como,

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

Esto es algo así como "aumentar" una matriz. Por ejemplo, utilicé esta técnica para crear una matriz de adyacencia única para representar un gráfico de un conjunto de matrices más pequeñas. Sin esta técnica, habría tenido que iterar sobre los componentes de una manera que podría haber sido propensa a errores o frustrante. Podría haber tenido que hacer un each_with_index, por ejemplo. En cambio, combiné zip y flatten de la siguiente manera,

# 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

Solo otra forma de hacerlo.

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

flattenaplana todo lo más posible, recursivamente. Incluso matrices anidadas. En consecuencia, si somearrayo anotherarraycontiene matrices anidadas, también se aplanan. Este es un efecto secundario que generalmente no está previsto.
hagello

5

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


No sé sobre eficiencia, pero esto funciona para Ruby 1.8. En general, [*a] + [*b]funciona
Oteo

No creo que "another" + "thing"vaya a funcionar como se esperaba.
Alexis Wilke

5

Si los nuevos datos pueden ser una matriz o un escalar, y desea evitar que los nuevos datos se aniden si se tratara de una matriz, ¡el operador splat es increíble! Devuelve un escalar para un escalar y una lista desempaquetada de argumentos para una 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

Me sorprende que nadie haya mencionado reduce, lo que funciona bien cuando tienes una variedad de matrices:

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

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

Esto no eliminará dups, pero

a|b

elimina dups.


Nota: Esto también aplana recursivamente todos los arreglos internos.
Mirodinho

2

Me resulta más fácil empujar o agregar matrices y luego aplanarlas en su lugar, así:

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 = ["some", "thing"]

anotherarray = ["otro", "cosa"]

somearray + anotherarray

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.