Array to Hash Ruby

192

Итак, вот в чем дело, я много лет гуглял, чтобы найти решение этой проблемы, и хотя их там много, похоже, они не выполняют ту работу, которую я ищу.

В основном у меня есть массив, структурированный так

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

Я хочу преобразовать это в хэш, чтобы он выглядел так

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

то есть элементы в «четных» индексах являются ключами, а элементы в «нечетных» индексах являются значениями.

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

djhworld
источник

Ответы:

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

Вот и все. Это *называется оператором сплат .

Одно предостережение в отношении @Mike Lewis (в комментариях): «Будьте очень осторожны с этим. Ruby расширяет знаки в стеке. Если вы делаете это с большим набором данных, ожидайте, что ваш стек будет взорван».

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

h = Hash[a.each_slice(2).to_a]
Бен Ли
источник
10
@tester, то *называется знак оператора. Он берет массив и преобразует его в буквальный список элементов. Итак *[1,2,3,4]=> 1, 2, 3, 4. В этом примере вышесказанное эквивалентно выполнению Hash["item 1", "item 2", "item 3", "item 4"]. И Hashимеет []метод, который принимает список аргументов (создает четные ключи ключей и нечетные значения индексов), но Hash[]не принимает массив, поэтому мы разбиваем массив на части с помощью *.
Бен Ли
15
Будьте очень осторожны с этим. Ruby расширяет знаки в стеке. Если вы сделаете это с большим набором данных, ожидайте, что вы разбросаете свой стек.
Майк Льюис
9
На больших таблицах данных вы можете использовать Hash[a.each_slice(2).to_a].
Хаулет
4
Что значит «взорвать свой стек»?
Кевин
6
@Kevin, стек использует небольшую область памяти, которую программа выделяет и резервирует для определенных конкретных операций. Чаще всего он используется для хранения стека методов, которые были вызваны до сих пор. Это происхождение термина трассировки стека , и именно поэтому бесконечно рекурсивный метод может вызвать переполнение стека . Метод в этом ответе также использует стек, но поскольку стек - это только небольшая область памяти, если вы попробуете этот метод с большим массивом, он заполнит стек и вызовет ошибку (ошибка в тех же строках, что и переполнение стека).
Бен Ли
103

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

[[:foo, :bar], [1, 2]].to_h
# => {:foo => :bar, 1 => 2}
Йохем Шуленклоппер
источник
1
Прекрасный! Намного лучше, чем некоторые другие решения здесь.
Деннис
3
для версий до 2.1.0 ruby ​​вы можете использовать метод 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]
Йосси
2
Нет. Это вернется { [1, 2] => [3, 4] }. И поскольку в заголовке вопроса написано «Array to Hash», а встроенный метод «Hash to Array» делает так: { 1 => 2, 3 => 4}.to_a # => [[1, 2], [3, 4]]я думал, что более чем один может закончить здесь, пытаясь получить обратное значение встроенного метода «Hash to Array». Собственно, так все и закончилось.
Эрик Эскобедо
1
Извините, я добавил запасную звездочку. Hash[arr]сделает работу за вас.
Йосси
9
ИМХО лучшее решение: Hash [* array.flatten (1)]
гость
2
Йосси: Извините за воскрешение мертвых, но есть еще одна проблема с его ответом, а именно использование #injectметода. С #merge!, #each_with_objectдолжен был быть использован. Если #injectнастаивают на, #mergeа не #merge!должны были быть использованы.
Борис Ститницкий
12

Вот что я искал, когда гуглял это:

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

Карл Глейзер
источник
Вы не хотите использовать merge, он создает и отбрасывает новый хэш за цикл итерации и очень медленно. Если у вас есть массив хэшей, попробуйте [{a:1},{b:2}].reduce({}, :merge!)вместо этого - он объединит все в один (новый) хеш.
Спасибо, это то, что я тоже хотел! :)
Танасис Петсас
Вы также можете сделать.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}
Jenorish
источник
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 , который изначально пуст , и будет извлекать элементы из исходного массива a при необходимости. Только тогда они действительно будут храниться в h!
Даниэль Вернер
1

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

Давайте посмотрим на:

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

Вы потеряете item 1 => item 2пару, поскольку она переопределена bij item 1 => item 5:

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

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

Вполне возможно, что это именно то, что вы ожидаете. Но в других случаях вы, вероятно, захотите получить результат со Arrayзначением for:

{"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делать выше в одной строке, но это становится гораздо сложнее рассуждать и читать.

Беркеш
источник