Масив до Hash Ruby


192

Добре, ось ось угода, я впродовж століть шукаю рішення, і хоча там багато, вони, схоже, не виконують роботу, яку я шукаю.

В основному у мене такий масив структурований

["item 1", "item 2", "item 3", "item 4"] 

Я хочу перетворити це на хеш, щоб воно виглядало так

{ "item 1" => "item 2", "item 3" => "item 4" }

тобто елементи, які знаходяться в "парних" індексах, є ключами, а елементи в "непарних" індексах - значеннями.

Будь-які ідеї, як це зробити чисто? Я припускаю, що методом грубої сили буде просто витягнути всі парні індекси в окремий масив, а потім обвести навколо них, щоб додати значення.

Відповіді:


358
a = ["item 1", "item 2", "item 3", "item 4"]
h = Hash[*a] # => { "item 1" => "item 2", "item 3" => "item 4" }

Це воно. Це *називається оператором splat .

Одне застереження на @Mike Lewis (у коментарях): "Будьте дуже обережні з цим. Ruby розширює бризки на стеку. Якщо ви це зробите з великим набором даних, сподівайтеся, що підірве ваш стек".

Отже, для більшості випадків загального використання цей метод є чудовим, але використовуйте інший метод, якщо ви хочете зробити перетворення на великій кількості даних. Наприклад, @ Łukasz Niemier (також у коментарях) пропонує цей метод для великих наборів даних:

h = Hash[a.each_slice(2).to_a]

10
@tester, *називається оператором splat . Він займає масив і перетворює його в буквальний список елементів. Отже *[1,2,3,4]=> 1, 2, 3, 4. У цьому прикладі вищезгадане рівнозначне виконанню Hash["item 1", "item 2", "item 3", "item 4"]. І Hashмає []метод, який приймає список аргументів (складання парних ключів та непарних значень індексів), але Hash[]не приймає масив, тому ми забризкуємо масив, використовуючи *.
Бен Лі

15
Будьте дуже обережні з цим. Рубі розширює бризки на стеці. Якщо ви робите це з великим набором даних, очікуйте, що вибухнете ваш стек.
Майк Льюїс

9
На великих таблицях даних ви можете використовувати Hash[a.each_slice(2).to_a].
Hauleth

4
Що означає "видув твій стек"?
Кевін

6
@Kevin, стек використовує невелику область пам'яті, яку програма виділяє та резервує для певних конкретних операцій. Найчастіше він використовується для збереження стека методів, які були названі досі. Це походження сліду терміна стека , і саме тому нескінченно-рекурсивний метод може викликати переповнення стека . Метод у цій відповіді також використовує стек, але оскільки стек - це лише невелика область пам'яті, якщо ви спробуєте цей метод з великим масивом, він заповнить стек і спричинить помилку (помилка в тих же рядках, що і переповнення стека).
Бен Лі

103

Ruby 2.1.0 представив to_hметод Array, який виконує те, що вам потрібно, якщо ваш початковий масив складається з масивів пар ключових значень: http://www.ruby-doc.org/core-2.1.0/Array.html#method -і-до_х .

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}

1
Гарний! Набагато краще, ніж деякі інші рішення тут.
Денніс

3
для версій ruby ​​до 2.1.0, ви можете використовувати метод Hash :: [], щоб отримати подібні результати, якщо у вас є пари вкладеного масиву. так a = [[: foo,: 1], [bar, 2]] --- Hash [a] => {: foo => 1
,:

@AfDev, справді, дякую. Ви правильні (коли ігноруєте незначні помилки: barмає бути символом, а символ :2має бути цілим числом. Отже, вираз виправлений a = [[:foo, 1], [:bar, 2]]).
Йохем Шуленклоппер

28

Просто використовуйте Hash.[]зі значеннями в масиві. Наприклад:

arr = [1,2,3,4]
Hash[*arr] #=> gives {1 => 2, 3 => 4}

1
що означає [* arr]?
Алан Коромано

1
@Marius: *arrперетворюється arrу список аргументів, тому це викликає []метод Hash із вмістом arr як аргументів.
Чак

26

Або якщо у вас є масив [key, value]масивів, ви можете:

[[1, 2], [3, 4]].inject({}) do |r, s|
  r.merge!({s[0] => s[1]})
end # => { 1 => 2, 3 => 4 }

2
Ви відповідаєте, що це не стосується питання, і у вашому випадку все-таки набагато простіше використовувати те самеHash[*arr]
Yossi

2
Ні. Це повернеться { [1, 2] => [3, 4] }. А оскільки в заголовку питання йдеться про "масив до хешу", а вбудований метод "хеш до масиву": { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]я думав, що тут може закінчитися більше, ніж намагатися отримати зворотну сторону вбудованого методу "хеш до масиву". Насправді, так я все-таки закінчився тут.
Ерік Ескобедо

1
Вибачте, я додав запасну зірочку. Hash[arr]зробить роботу за вас.
Йоссі

9
Краще рішення ІМХО: Хеш [* array.flatten (1)]
гість

2
Йоссі: Вибачте за те, що підняв мертвих, але є одна більша проблема з його відповіддю, і це використання #injectметоду. З #merge!, #each_with_objectповинні були використовуватися. Якщо #injectна цьому наполягають, #mergeа не #merge!повинні були використовуватися.
Boris Stitnicky

12

Це те, що я шукав, коли гуглив це:

[{a: 1}, {b: 2}].reduce({}) { |h, v| h.merge v } => {:a=>1, :b=>2}


Ви не хочете користуватися merge, він створює та відкидає новий хеш за цикл ітерації і дуже повільний. Якщо у вас є масив хешів, спробуйте [{a:1},{b:2}].reduce({}, :merge!)замість цього - він об'єднує все в той же (новий) хеш.

Спасибі, це я теж хотів! :)
Thanasis Petsas

Також можна зробити.reduce(&:merge!)
Бен Лі

1
[{a: 1}, {b: 2}].reduce(&:merge!)оцінює до{:a=>1, :b=>2}
Бен Лі

Це працює, тому що введення / зменшення має функцію, за якою ви можете опускати аргумент, і в цьому випадку він використовує перший аргумент масиву для роботи в якості вхідного аргументу, а решту масиву як масив. Поєднайте це із символом на замовлення, і ви отримаєте цю стисну конструкцію. Іншими словами [{a: 1}, {b: 2}].reduce(&:merge!)- це те саме, [{a: 1}, {b: 2}].reduce { |m, x| m.merge(x) }що і те саме, що [{b: 2}].reduce({a: 1}) { |m, x| m.merge(x) }.
Бен Лі

10

Enumeratorвключає Enumerable. Оскільки 2.1, Enumerableтакож є метод #to_h. Тому ми можемо написати: -

a = ["item 1", "item 2", "item 3", "item 4"]
a.each_slice(2).to_h
# => {"item 1"=>"item 2", "item 3"=>"item 4"}

Тому що #each_sliceбез блоку не дає нам Enumerator, і відповідно до вищезгаданого пояснення, ми можемо викликати #to_hметод на Enumeratorоб'єкті.


7

Ви можете спробувати так, для одного масиву

irb(main):019:0> a = ["item 1", "item 2", "item 3", "item 4"]
  => ["item 1", "item 2", "item 3", "item 4"]
irb(main):020:0> Hash[*a]
  => {"item 1"=>"item 2", "item 3"=>"item 4"}

для масиву масиву

irb(main):022:0> a = [[1, 2], [3, 4]]
  => [[1, 2], [3, 4]]
irb(main):023:0> Hash[*a.flatten]
  => {1=>2, 3=>4}

6
a = ["item 1", "item 2", "item 3", "item 4"]
Hash[ a.each_slice( 2 ).map { |e| e } ]

або, якщо ви ненавидите Hash[ ... ]:

a.each_slice( 2 ).each_with_object Hash.new do |(k, v), h| h[k] = v end

або, якщо ви ледачий фанат зламаного функціонального програмування:

h = a.lazy.each_slice( 2 ).tap { |a|
  break Hash.new { |h, k| h[k] = a.find { |e, _| e == k }[1] }
}
#=> {}
h["item 1"] #=> "item 2"
h["item 3"] #=> "item 4"

Якщо ви не зовсім ненавидите, Hash[ ... ]але хочете використовувати його як прикований метод (як це можна зробити to_h), ви можете комбінувати пропозиції Бориса і писати:arr.each_slice( 2 ).map { |e| e }.tap { |a| break Hash[a] }
b-studios

Щоб зробити семантику коду вище зрозумілою: Це створить h "ледачий хеш" h , який спочатку порожній , і витягне елементи з початкового масиву a, коли потрібно. Тільки тоді вони фактично будуть зберігатися в год!
Даніель Вернер

1

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

Давайте розглянемо:

a = ["item 1", "item 2", "item 3", "item 4", "item 1", "item 5"]

Ви втратите item 1 => item 2пару, оскільки вона буде переохолоджена item 1 => item 5:

Hash[*a]
=> {"item 1"=>"item 5", "item 3"=>"item 4"}

Всі методи, включаючи reduce(&:merge!)результат у тому самому видаленні.

Можливо, це саме те, чого ви очікуєте. Але в інших випадках, ймовірно, ви хочете отримати результат із Arrayзначенням "на зміну":

{"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Наївним способом було б створити хелперну змінну, хеш, який має значення за замовчуванням, а потім заповнити її у циклі:

result = Hash.new {|hash, k| hash[k] = [] } # Hash.new with block defines unique defaults.
a.each_slice(2) {|k,v| result[k] << v }
a
=> {"item 1"=>["item 2", "item 5"], "item 3"=>["item 4"]}

Можливо, можна використовувати assocі reduceробити вище в одному рядку, але це стає набагато складніше міркувати і читати.

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