Comment ajoutez-vous un tableau à un autre tableau dans Ruby et ne vous retrouvez-vous pas avec un résultat multidimensionnel?

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

anotherarray = ["another", "thing"]

somearray.push(anotherarray.flatten!)

j'esperais

["some","thing","another","thing"]
ncvncvn
la source
6
Cela vaut la peine de dire (non pas pour vous causer du chagrin, mais parce qu'il vous mordra encore et encore) que votre attente est le problème ici. Les tableaux Ruby (contrairement aux tableaux en Perl par exemple) ne s'aplatissent pas automatiquement dans des contextes comme celui-ci. Ce n'est pas un bug: c'est une fonctionnalité.
Télémaque
3
ri Array@flatten!Pourquoi cette question obtient-elle autant de votes? Le doc est explicite Array#flatten! Flattens self in place. Renvoie nil si aucune modification n'a été apportée (c'est-à-dire que le tableau ne contient aucun sous-tableau.)
yeyo
7
Les questions suscitent des votes positifs si elles sont utiles aux utilisateurs. Les questions les plus simples obtiennent le plus de votes car elles sont utiles au plus grand nombre.
Ziggy
@yeyo, ne pensez-vous pas simplement que l'opération d'aplatissement est gratuite?
Konstantin
@Konstantin op ne cherche pas d'alternatives ou ne parle pas de problèmes de performance, op s'attendait à un résultat qu'il ou elle n'a pas obtenu parce que flatten!cela ne fonctionne pas comme ça. Enfin, la question reflète un problème de logique plutôt qu'un problème d'optimisation. Voir la réponse de pilcrow ci-dessous pour en savoir plus.
yeyo

Réponses:

714

Vous avez une idée réalisable, mais #flatten!est au mauvais endroit - il aplatit son récepteur, de sorte que vous pouvez l' utiliser pour transformer [1, 2, ['foo', 'bar']]en [1,2,'foo','bar'].

J'oublie sans doute certaines approches, mais vous pouvez concaténer :

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

ou ajouter / ajouter :

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

ou épissure :

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

ou ajouter et aplatir :

(a1 << a2).flatten!  # a call to #flatten instead would return a new array
pilcrow
la source
17
bravo pour être le seul (sur 5 je peux voir) qui a fait remarquer ce qui n'allait pas avec le code présenté. +1
Mike Woodhouse
53
L'utilisation de push au lieu de concat évite la création d'un troisième tableau, c'est donc préférable pour les grands tableaux.
phatmann
8
J'adore la poussée avec l'astérisque. Très élégant.
orourkedd
14
@phatmann Concaténation avec Array#concatn'alloue pas de nouveau tableau, Concaténation avec le Array#+fait
cbliard
5
La seule chose qui manque à cette réponse est des comparaisons de référence de chaque approche. +1!
Terra Ashley
206

Vous pouvez simplement utiliser l' +opérateur!

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]

Vous pouvez tout lire sur la classe array ici: http://ruby-doc.org/core/classes/Array.html

micmoo
la source
15
L'affiche voulait savoir comment concaténer un tableau existant, pas créer un nouveau tableau qui était l'union de deux tableaux.
phatmann
1
Remarque: a+= bcrée un nouveau tableau:c = a = [1,2] ; b = [3,4] ; a += b ; puts c #=> [1,2]
kbrock
1
@kbrock Correct. Si vous traitez de grands tableaux, vous voudrez regarder la pushméthode décrite par @pilcrow.
Joshua Pinter
2
rappelez-vous que +=crée un nouvel objet. dans un tel exemple, un [1, 2].each_with_object([]) { |number, object| object+=number }tableau vide []sera retourné
Filip Bartuzi
1
L'élément ajouté doit être un tableau
RousseauAlexandre
66

L'approche la plus propre consiste à utiliser la méthode Array # concat ; il ne créera pas de nouveau tableau (contrairement à Array # + qui fera la même chose mais créera un nouveau tableau).

Directement à partir des documents ( http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-concat ):

concat (other_ary)

Ajoute les éléments de other_ary à soi.

Donc

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

Array # concat n'aplatira pas un tableau multidimensionnel s'il est passé en argument. Vous devrez gérer cela séparément:

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

Enfin, vous pouvez utiliser notre gem corelib ( https://github.com/corlewsolutions/corelib ) qui ajoute des aides utiles aux classes de base Ruby. En particulier, nous avons une méthode Array # add_all qui aplatira automatiquement les tableaux multidimensionnels avant d'exécuter le concat.

Corlew Solutions
la source
1
Vous voulez généralement l'immuabilité, donc la création d'un nouveau tableau est une meilleure idée.
vasilakisfil
5
"Vous voulez généralement l'immuabilité" n'est pas exact. En plus de 20 ans de développement de logiciels à plein temps, j'ai travaillé quotidiennement avec toutes sortes de matrices et de collections. Parfois, vous modifiez un tableau existant sur place. Parfois, vous devez travailler avec une nouvelle instance.
Corlew Solutions du
35

Méthode simple qui fonctionne avec la version Ruby> = 2.0 mais pas avec les versions plus anciennes:

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]
Ludovic Kuty
la source
2
@Ikuty C'est de loin la solution la plus élégante que j'ai trouvée, pouvez-vous expliquer ce qui se passe *ici?
Abhinay
@Abhinay, l'opérateur plat explose le tableau en éléments, créant ainsi un tableau à une dimension dans la dernière ligne.
Omar Ali
[*a, *b]échoue pour les anciennes versions de ruby, c'est-à-dire 1.8.7. Et autant que Ruby veut vous dire qu'il est hors de vie, RHEL6 est toujours maintenu, ce qui fait de Ruby 1.8 une version cible importante.
Otheus
1
Je ne pense pas que cela justifie le -1 obtenu par cette réponse. Pas de version rubis mentionnée par OP, version rubis explicitement mentionnée dans la réponse, donc ... vous voulez être rétrocompatible avec la version pré alpha 0.0.0.0.1? C'est l'une des bonnes solutions, selon la version rubis
Ludovic Kuty
1
Juste pour souligner que cette réponse est très «similaire» au JavaScript ES6 très idiomatique dans lequel vous pourriez le faire [...array1, ...array2], en vous rappelant simplement que l' splatopérateur dans ruby ​​serait à la *place de .... Il est plus facile de se souvenir
sandre89
34

Essayez ceci, il combinera vos tableaux en supprimant les doublons

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

array3 = array1|array2

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

Pour plus de documentation, regardez "Set Union"

g00se0ne
la source
Ceci est un ou, il renvoie un tableau sans éléments en double, voici un exemple de la façon dont il ne fait probablement pas ce qu'il demande, les deux "baz" du premier tableau sont transformés en un seul, et la "barre" dans le second tableau n'est pas ajouté. array1 = ["foo", "bar", "baz", "baz"] array2 = ["foo1", "bar1", "bar"] array3 = array1 | array2 array3 # => ["foo", "bar "," baz "," foo1 "," bar1 "]
Joshua Cheek
Ou encore mieux:array1 |= [ "foo1", "bar1" ] #=> [ "foo", "bar", "foo1", "bar1" ]
Joshua Pinter le
33

Voici deux façons, notez dans ce cas que la première façon assigne un nouveau tableau (se traduit par 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"]
Joshua Cheek
la source
25
a = ["some", "thing"]
b = ["another", "thing"]

Pour ajouter bà aet stocker le résultat dans a:

a.push(*b)

ou

a += b

Dans les deux cas, adevient:

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

mais dans le premier cas, les éléments de bsont ajoutés au atableau existant , et dans le second cas, les deux tableaux sont concaténés ensemble et le résultat est stocké dans a.

snibbets
la source
2
Notez que ce a.push(*b)n'est pas exactement le même que a += b. Le premier ajoute les nouveaux éléments au tableau existant; ce dernier crée un nouveau tableau avec tous les éléments et l'assigne à a. Vous pouvez voir la différence si vous faites quelque chose comme aa = aenregistrer la référence aavant l'une ou l'autre méthode d'ajout, puis l'examiner aaensuite. Dans le premier cas, il change avec la nouvelle valeur de a, et dans le second, il reste inchangé.
Dave Hartnoll
20

(array1 + array2).uniq

De cette façon, vous obtenez d'abord les éléments array1. Vous n'aurez aucun doublon.

slindsey3000
la source
9

En élaborant la réponse de @ Pilcrow, la seule réponse appropriée pour les énormes tableaux est concat( +) car elle est rapide et n'alloue pas un nouvel objet à récupérer les ordures lors de son fonctionnement dans une boucle.

Voici la référence:

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

Résultats:

       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)

Comme vous pouvez le voir en utilisant pushjette une ERREUR : stack level too deep (SystemStackError)lorsque les tableaux sont assez grands.

juliangonzalez
la source
8

La question, essentiellement, est "comment concaténer des tableaux en Ruby". Naturellement, la réponse est d'utiliser concatou +comme mentionné dans presque toutes les réponses.

Une extension naturelle de la question serait "comment effectuer la concaténation ligne par ligne des tableaux 2D dans Ruby". Lorsque j'ai googlé "matrices de concaténation rubis", cette question SO était le meilleur résultat, alors j'ai pensé laisser ma réponse à cette question (non demandée mais liée) ici pour la postérité.


Dans certaines applications, vous souhaiterez peut-être "concaténer" deux tableaux 2D en ligne. Quelque chose comme,

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

C'est quelque chose comme "augmenter" une matrice. Par exemple, j'ai utilisé cette technique pour créer une seule matrice d'adjacence pour représenter un graphique à partir d'un tas de matrices plus petites. Sans cette technique, j'aurais dû parcourir les composants d'une manière qui aurait pu être source d'erreurs ou frustrante. J'aurais peut-être dû faire un each_with_index, par exemple. Au lieu de cela, j'ai combiné zip et aplatir comme suit,

# 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]]
Ziggy
la source
8

Juste une autre façon de le faire.

[somearray, anotherarray].flatten
=> ["some", "thing", "another", "thing"]
Datt
la source
flattenaplatit tout autant que possible, récursivement. Même des tableaux imbriqués. Par conséquent, si somearrayou anotherarraycontient des tableaux imbriqués, ils s'aplatissent également. Il s'agit d'un effet secondaire qui n'est généralement pas prévu.
hagello
5

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

samg
la source
Je ne connais pas l'efficacité, mais cela fonctionne pour Ruby 1.8. En général, les [*a] + [*b]travaux
Otheus
Je ne pense pas que cela "another" + "thing"fonctionnera comme prévu.
Alexis Wilke
5

Si les nouvelles données peuvent être un tableau ou un scalaire et que vous souhaitez empêcher l'imbrication des nouvelles données s'il s'agissait d'un tableau, l'opérateur splat est génial! Il retourne un scalaire pour un scalaire et une liste décompactée d'arguments pour un tableau.

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 
Sandip Bhattacharya
la source
4

Je suis surpris que personne ne l'ait mentionné reduce, ce qui fonctionne bien lorsque vous disposez d'un tableau de tableaux:

lists = [["a", "b"], ["c", "d"]]
flatlist = lists.reduce(:+)  # ["a", "b", "c", "d"]
ScottJ
la source
4
a = ['a', 'b']
b = ['c', 'd']
arr = [a, b].flatten

Cela ne supprimera pas les doublons, mais

a|b

supprime les doublons.

AustintheCleric
la source
Remarque: Cela aplatit récursivement tous les tableaux internes également.
Mirodinho
2

Je trouve plus facile de pousser ou d'ajouter des tableaux, puis de les aplatir en place, comme suit:

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"]
nas
la source
2

somearray = ["certains", "chose"]

anotherarray = ["autre", "chose"]

un tableau + un autre tableau

Lyle Dickie
la source