Как добавить массив в другой массив в 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! Сглаживает себя на месте. Возвращает nil, если не было внесено никаких изменений (т.
Е.

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

@yeyo, разве ты не думаешь, что операция сглаживания бесплатна?
Константин

@Konstantin op не ищет альтернативы и не говорит о проблемах с производительностью, op ожидал результата, которого он или она не получил, потому flatten!что так не работает. Наконец, вопрос отражает логическую проблему, а не проблему оптимизации. Смотрите ответ Пилкроу ниже для получения дополнительной информации.
августа

Ответы:


714

У вас есть подходящая идея, но она #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 Конкатенация с Array#concatне выделяет новый массив, Конкатенация с Array#+делает
cbliard

5
Единственное, чего не хватает в этом ответе - это сравнительные сравнения каждого подхода. +1!
Терра Эшли

206

Вы можете просто использовать +оператор!

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. И хотя Ruby хочет сказать вам, что он из жизни, RHEL6 все еще поддерживается, что делает Ruby 1.8 очень важной целевой версией.
Отей

1
Я не думаю, что это оправдывает -1 этот ответ. OP версия не упоминает, версия ruby ​​явно упоминается в ответе, так что ... вы хотите иметь обратную совместимость с версией pre alpha 0.0.0.0.1? Это одно из хороших решений, в зависимости от рубиновой версии
Людовик Куты

1
Просто чтобы указать, что этот ответ очень «похож» на очень идиоматический JavaScript ES6, в котором вы могли бы сделать [...array1, ...array2], просто помня, что splatоператор в ruby ​​будет *вместо .... Это легче запомнить
sandre89

34

Попробуйте, это объединит ваши массивы, удалив дубликаты

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

array3 = array1|array2

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

Дальнейшую документацию смотрите на "Set Union"


Это или, он возвращает массив без дублирующих элементов, вот пример того, как он, вероятно, не выполняет то, о чем он просит, два «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"]

25
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)

Как вы можете видеть, использование pushthrows ERROR : stack level too deep (SystemStackError)когда массивы достаточно велики.


8

По сути, вопрос заключается в том, «как объединить массивы в Ruby». Естественно, ответ заключается в использовании concatили+ как упоминалось почти в каждом ответе.

Естественным продолжением вопроса будет «как выполнить построчную конкатенацию двумерных массивов в Ruby». Когда я гуглил «матрицы конкатенации рубина», этот SO вопрос был главным результатом, поэтому я решил оставить свой ответ на этот (не заданный, но связанный) вопрос здесь для потомков.


В некоторых приложениях вам может потребоваться «объединить» два 2D-массива по строкам. Что-то вроде,

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

Это что-то вроде «увеличения» матрицы. Например, я использовал эту технику, чтобы создать одну матрицу смежности для представления графа из множества меньших матриц. Без этой техники мне пришлось бы перебирать компоненты таким образом, чтобы это могло привести к ошибкам или разочарованию. Я мог бы сделать each_with_index, например. Вместо этого я объединил zip и flatten следующим образом,

# 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]работает
Otheus

Я не думаю, что "another" + "thing"это сработает, как ожидалось.
Алексис Уилк

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.