Як додати масив до іншого масиву в Ruby і не закінчитися багатовимірним результатом?


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

anotherarray = ["another", "thing"]

somearray.push(anotherarray.flatten!)

я очікував

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

6
Варто сказати (не для того, щоб доставити вам горе, а тому, що воно буде кусати вас знову і знову), що ваше очікування - тут проблема. Ruby масиви (на відміну від масивів, які говорять у Perl) не вирівнюються автоматично в таких контекстах. Це не помилка: це особливість.
Телемах

3
ri Array@flatten!Чому за це питання набирається стільки голосів? Документ явно Array#flatten! згладжує себе на місці. Повертає нуль, якщо не було внесено жодних модифікацій (тобто масив не містить підмагістралей.)
yeyo

7
Питання отримують нагороди, якщо вони корисні для користувачів. Найпростіші запитання отримують найбільше відгуків, оскільки вони корисні більшості людей.
Зіггі

@yeyo, ти не вважаєш, що операція згладжування безкоштовна?
Костянтин

@Konstantin op не шукає альтернатив або не розмовляє з питаннями ефективності, op очікував результату, якого він чи вона не отримає, тому flatten!що не працює так. Нарешті, питання відображає не логічну проблему, а проблему оптимізації. Детальну інформацію див. У відповіді прокладок нижче.
yeyo

Відповіді:


713

У вас є працездатна ідея, але #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

або зрощення :

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

17
молодший за те, що був єдиним (з 5 я бачу), який насправді зазначив, що не так у представленому коді. +1
Майк Вудхаус

53
Використання push замість concat дозволяє уникнути створення третього масиву, тому цей варіант є кращим для великих масивів.
phatmann

8
Я люблю поштовх зірочкою. Дуже елегантний.
orourkedd

14
@phatmann Concatenation with Array#concatне виділяє новий масив, Concatenation with Array#+does
cbliard

5
Єдине, чого не вистачає цієї відповіді, - порівняльні порівняння кожного підходу. +1!
Терра Ешлі

205

Ви можете просто скористатися +оператором!

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


15
Плакат хотів знати, як присвоїти існуючий масив, а не створити новий масив, який був об'єднанням двох масивів.
phatmann

1
Примітка: a+= bстворює новий масив:c = a = [1,2] ; b = [3,4] ; a += b ; puts c #=> [1,2]
kbrock

1
@kbrock Правильно. Якщо ви маєте справу з великими масивами, ви хочете переглянути pushметод, як описано у @pilcrow.
Джошуа Пінтер

2
пам’ятайте, що +=створює новий об’єкт. у такому прикладі повернеться [1, 2].each_with_object([]) { |number, object| object+=number }порожній масив[]
Філіп Бартузі

1
Доданий елемент повинен бути масивом
RousseauAlexandre

66

Найчистіший підхід - використовувати метод Array # concat ; він не створить новий масив (на відміну від Array # +, який зробить те саме, але створить новий масив).

Прямо з документів ( http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-concat ):

concat (other_ary)

Додає елементи other_ary до себе.

Тому

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

Масив # concat не буде вирівнювати багатовимірний масив, якщо він передається як аргумент. Вам потрібно буде опрацювати це окремо:

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

Нарешті, ви можете скористатися нашою драгоценною каменем corelib ( https://github.com/corlewsolutions/corelib ), яка додає корисних помічників до основних класів Ruby. Зокрема, у нас є метод Array # add_all, який автоматично розгладжує багатовимірні масиви перед виконанням concat.


1
Зазвичай ви хочете незмінність, тому краще створити новий масив.
vasilakisfil

5
"Ви зазвичай хочете незмінність" не є точним. За 20+ років повної розробки програмного забезпечення я щодня працював з усілякими масивами та колекціями. Іноді ви змінюєте наявний масив на місці. Іноді потрібно працювати з новим екземпляром.
Corlew Solutions

35

Простий метод, який працює з версією 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]

2
@Ikuty Це, безумовно, найелегантніше рішення, яке я знайшов, чи можете ви пояснити, що *тут відбувається ?
Абхінай

@Abhinay оператор plat вибудовує масив в елементи, створюючи одномірний масив в останньому рядку.
Омар Алі

[*a, *b]виходить з ладу для старих версій ruby, тобто 1.8.7. І наскільки Рубі хоче розповісти вам про своє життя, RHEL6 все ще підтримується, що робить Ruby 1.8 дуже важливою цільовою версією.
Отей

1
Я не думаю, що це виправдовує -1, яку отримує ця відповідь. Жодна версія рубіну не згадується ОП, версія рубіну прямо вказана у відповіді, так що ... ви хочете бути зворотною сумісною з версією до альфа 0.0.0.0.1? Це одне з хороших рішень, залежно від рубінованої версії
Людовик Куті

1
Тільки щоб зазначити, що ця відповідь дуже «схожа» на той самий ідіоматичний JavaScript ES6, в якому ви могли б зробити [...array1, ...array2], просто пам’ятаючи, що замість цього splatбув би оператор в рубіні . Це полегшує запам’ятовування*...
sandre89

34

Спробуйте це, він поєднає ваші масиви, видаляючи дублікати

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

array3 = array1|array2

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

Подальша документація дивіться у "Установці"


Це або, він повертає масив без повторюваних елементів, ось приклад того, як він, ймовірно, не робить того, що він просить, два "baz" у першому масиві перетворюються на один, а "bar" у другому масиві не додається. array1 = ["foo", "bar", "baz", "baz"] array2 = ["foo1", "bar1", "bar"] array3 = array1 | array2 array3 # => ["foo", "bar "," baz "," foo1 "," bar1 "]
Джошуа Щока

Або ще краще:array1 |= [ "foo1", "bar1" ] #=> [ "foo", "bar", "foo1", "bar1" ]
Джошуа Пінтер

33

Ось два способи, зауважте в цьому випадку, що перший спосіб призначає новий масив (перекладається на 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"]

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

Для того, щоб додати bдо aі зберегти результат в a:

a.push(*b)

або

a += b

У будь-якому випадку aстає:

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

але в першому випадку елементи bдолучаються до існуючого aмасиву, а в другому випадку два масиви об'єднуються разом і результат зберігається в a.


2
Зауважте, що a.push(*b)це не точно так само, як a += b. Перший додає нові елементи до існуючого масиву; останній створює новий масив з усіма елементами і присвоює йому a. Ви можете побачити різницю, якщо зробите щось на зразок, aa = aщоб зберегти посилання до aбудь-якого методу додавання, а потім перевірити aa. У першому випадку воно змінюється з новим значенням a, а в другому воно залишається незмінним.
Дейв

20

(array1 + array2).uniq

Таким чином ви спочатку отримуєте елементи array1. Ви не отримаєте жодних дублікатів.


9

Робота над відповіддю @ 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)коли масиви досить великі.


8

По суті, питання полягає в тому, як "об'єднати масиви в Ruby". Природно, що відповідь полягає у використанні concatабо +як згадується майже у кожній відповіді.

Природним продовженням питання було б "як виконувати доречне з'єднання 2D-масивів у Ruby". Коли я гугла "рубінові матриці", це питання було найкращим результатом, тому я подумав, що залишу свою відповідь на це (не задане, але пов'язане) питання для нащадків.


У деяких додатках ви можете "об'єднати" два двовимірні масиви в рядку. Щось на зразок,

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

Це щось на кшталт "збільшення" матриці. Наприклад, я використав цю методику для створення єдиної матриці суміжності, щоб представити графік із купки менших матриць. Без цієї методики мені довелося б переглядати компоненти таким чином, що це могло б бути схильним до помилок або неприємно думати. Я, можливо, мав би зробити щось each_with_index, наприклад. Натомість я комбінував блискавку і вирівняти так,

# 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

Просто інший спосіб зробити це.

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

flattenзгладжує все, наскільки це можливо, рекурсивно. Навіть вкладені масиви. Отже, якщо somearrayабо anotherarrayмістять вкладені масиви, вони також сплющуються. Це побічний ефект, який зазвичай не призначений.
хагелло

5

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


Я не знаю про ефективність, але це працює для Ruby 1.8. Загалом, [*a] + [*b]працює
Отей

Я не думаю, що "another" + "thing"це буде працювати так, як очікувалося.
Alexis Wilke

5

Якщо нові дані можуть бути масивом або скаляром, і ви хочете запобігти вкладенню нових даних, якщо це був масив, оператор splat є приголомшливим! Він повертає скаляр для скаляра та розпакований список аргументів для масиву.

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

Я здивований, що ніхто не згадав reduce, що працює добре, коли у вас є масив масивів:

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

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

Це не видалить дупи, але

a|b

видаляє дупи.


Примітка. Це рекурсивно згладжує і всі внутрішні масиви.
Миродіньо

2

Мені простіше натиснути або додати масиви, а потім розрівняти їх на місці:

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"]

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.