В Ruby задан массив в одной из следующих форм ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... как лучше всего преобразовать это в хеш в форме ...
{apple => 1, banana => 2}
В Ruby задан массив в одной из следующих форм ...
[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]
... как лучше всего преобразовать это в хеш в форме ...
{apple => 1, banana => 2}
Ответы:
ПРИМЕЧАНИЕ . Краткое и эффективное решение см. В ответе Марка-Андре Лафортуна ниже.
Этот ответ изначально предлагался в качестве альтернативы подходам с использованием flatten, которые на момент написания получили наибольшее количество голосов. Мне следовало пояснить, что я не намеревался представить этот пример как передовой опыт или эффективный подход. Оригинальный ответ следует.
Предупреждение! Решения, использующие flatten , не сохраняют ключи или значения массива!
Основываясь на популярном ответе @John Topley, давайте попробуем:
a3 = [ ['apple', 1], ['banana', 2], [['orange','seedless'], 3] ]
h3 = Hash[*a3.flatten]
Это вызывает ошибку:
ArgumentError: odd number of arguments for Hash
from (irb):10:in `[]'
from (irb):10
Конструктор ожидал массив четной длины (например, ['k1', 'v1,' k2 ',' v2 ']). Что еще хуже, другой массив, который сглаживается до одинаковой длины, просто молча дает нам хэш с неправильными значениями.
Если вы хотите использовать ключи или значения массива, вы можете использовать карту :
h3 = Hash[a3.map {|key, value| [key, value]}]
puts "h3: #{h3.inspect}"
Это сохраняет ключ массива:
h3: {["orange", "seedless"]=>3, "apple"=>1, "banana"=>2}
h3 = Hash[*a3.flatten(1)]
вместо этого h3 = Hash[*a3.flatten]
выдает ошибку.
to_h
лучше.
Просто используйте Hash[*array_variable.flatten]
Например:
a1 = ['apple', 1, 'banana', 2]
h1 = Hash[*a1.flatten(1)]
puts "h1: #{h1.inspect}"
a2 = [['apple', 1], ['banana', 2]]
h2 = Hash[*a2.flatten(1)]
puts "h2: #{h2.inspect}"
Использование Array#flatten(1)
ограничивает рекурсию, поэтому Array
ключи и значения работают должным образом.
Hash[*ary.flatten(1)]
, которая сохранит ключи и значения массива. Их flatten
разрушает рекурсивный метод , которого достаточно легко избежать.
Лучше всего использовать Array#to_h
:
[ [:apple,1],[:banana,2] ].to_h #=> {apple: 1, banana: 2}
Обратите внимание, что to_h
также принимает блок:
[:apple, :banana].to_h { |fruit| [fruit, "I like #{fruit}s"] }
# => {apple: "I like apples", banana: "I like bananas"}
Примечание : to_h
принимает блок в Ruby 2.6.0+; для ранних рубинов вы можете использовать мой backports
драгоценный камень иrequire 'backports/2.6.0/enumerable/to_h'
to_h
без блока был представлен в Ruby 2.1.0.
До Ruby 2.1 можно было использовать менее разборчивый Hash[]
:
array = [ [:apple,1],[:banana,2] ]
Hash[ array ] #= > {:apple => 1, :banana => 2}
Наконец, будьте осторожны с любыми решениями, которые используют flatten
, это может создать проблемы со значениями, которые сами по себе являются массивами.
to_h
метод нравится больше, чем приведенные выше ответы, потому что он выражает намерение преобразования после работы с массивом.
Array#to_h
ни другого Enumerable#to_h
.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
и я хочу получить результат как {"apple" =>[1,3], "banana"=>[2,4]}
?
Обновить
Сегодня выпущен Ruby 2.1.0 . И у меня есть Array#to_h
( примечания к выпуску и ruby-doc ), который решает проблему преобразования файла Array
вHash
.
Пример документации Ruby:
[[:foo, :bar], [1, 2]].to_h # => {:foo => :bar, 1 => 2}
Изменить: видел ответы, опубликованные, когда я писал, Hash [a.flatten] кажется подходящим вариантом. Должно быть, я пропустил этот момент в документации, когда обдумывал ответ. Думал, что решения, которые я написал, можно использовать в качестве альтернативы при необходимости.
Вторая форма более простая:
a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }
a = массив, h = хэш, r = хэш возвращаемого значения (тот, который мы накапливаем), i = элемент в массиве
Самый простой способ, который я могу придумать для создания первой формы, выглядит примерно так:
a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }
a.inject({})
однострочника, который позволяет более гибко присваивать значения.
h = {}
a.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
a.each_slice(2).to_h
Этот ответ надеется быть исчерпывающим обобщением информации из других ответов.
Очень короткая версия, учитывая данные из вопроса и пару дополнений:
flat_array = [ apple, 1, banana, 2 ] # count=4
nested_array = [ [apple, 1], [banana, 2] ] # count=2 of count=2 k,v arrays
incomplete_f = [ apple, 1, banana ] # count=3 - missing last value
incomplete_n = [ [apple, 1], [banana ] ] # count=2 of either k or k,v arrays
# there's one option for flat_array:
h1 = Hash[*flat_array] # => {apple=>1, banana=>2}
# two options for nested_array:
h2a = nested_array.to_h # since ruby 2.1.0 => {apple=>1, banana=>2}
h2b = Hash[nested_array] # => {apple=>1, banana=>2}
# ok if *only* the last value is missing:
h3 = Hash[incomplete_f.each_slice(2).to_a] # => {apple=>1, banana=>nil}
# always ok for k without v in nested array:
h4 = Hash[incomplete_n] # or .to_h => {apple=>1, banana=>nil}
# as one might expect:
h1 == h2a # => true
h1 == h2b # => true
h1 == h3 # => false
h3 == h4 # => true
Обсуждение и подробности следуют далее.
Чтобы показать данные, которые мы будем использовать заранее, я создам несколько переменных, представляющих различные возможности для данных. Они попадают в следующие категории:
a1
и a2
:(Примечание: я предполагаю, что apple
и banana
были предназначены для представления переменных. Как и другие, я буду использовать строки с этого момента, чтобы ввод и результаты могли совпадать.)
a1 = [ 'apple', 1 , 'banana', 2 ] # flat input
a2 = [ ['apple', 1], ['banana', 2] ] # key/value paired input
a3
:В некоторых других ответах была представлена другая возможность (которую я здесь расширяю) - ключи и / или значения могут быть массивами сами по себе:
a3 = [ [ 'apple', 1 ],
[ 'banana', 2 ],
[ ['orange','seedless'], 3 ],
[ 'pear', [4, 5] ],
]
a4
:Для удобства я подумал, что добавлю один на случай, когда у нас может быть неполный ввод:
a4 = [ [ 'apple', 1],
[ 'banana', 2],
[ ['orange','seedless'], 3],
[ 'durian' ], # a spiky fruit pricks us: no value!
]
a1
:Некоторые предлагали использовать #to_h
(который появился в Ruby 2.1.0 и может быть перенесен в более ранние версии). Для изначально плоского массива это не работает:
a1.to_h # => TypeError: wrong element type String at 0 (expected array)
Использование в Hash::[]
сочетании с оператором splat :
Hash[*a1] # => {"apple"=>1, "banana"=>2}
Итак, это решение для простого случая, представленного a1
.
a2
:С массивом [key,value]
массивов типов можно пойти двумя путями.
Во-первых, Hash::[]
все еще работает (как и с *a1
):
Hash[a2] # => {"apple"=>1, "banana"=>2}
А потом тоже #to_h
работает сейчас:
a2.to_h # => {"apple"=>1, "banana"=>2}
Итак, два простых ответа для случая простого вложенного массива.
a3
:Hash[a3] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
a3.to_h # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "pear"=>[4, 5]}
Если мы получили несбалансированные входные данные, мы столкнемся с проблемами #to_h
:
a4.to_h # => ArgumentError: wrong array length at 3 (expected 2, was 1)
Но Hash::[]
все еще работает, просто устанавливая nil
в качестве значения для durian
(и любого другого элемента массива в a4, который является просто массивом с 1 значением):
Hash[a4] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
a5
иa6
Упомянутые несколько других ответов flatten
с 1
аргументом или без него , поэтому давайте создадим несколько новых переменных:
a5 = a4.flatten
# => ["apple", 1, "banana", 2, "orange", "seedless" , 3, "durian"]
a6 = a4.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian"]
Я решил использовать a4
в качестве базовых данных из-за проблемы с балансом, которая у нас возникла a4.to_h
. Я думаю звонюflatten
может быть одним из подходов, которые кто-то может использовать, чтобы попытаться решить эту проблему, что может выглядеть следующим образом.
flatten
без аргументов ( a5
):Hash[*a5] # => {"apple"=>1, "banana"=>2, "orange"=>"seedless", 3=>"durian"}
# (This is the same as calling `Hash[*a4.flatten]`.)
На наивном взгляде, это , кажется, работает - но у нас на неправильной ноге с бессемонными апельсинами, таким образом , также делает 3
на ключ и durian
на значение .
А это, как и с a1
, просто не работает:
a5.to_h # => TypeError: wrong element type String at 0 (expected array)
Так что a4.flatten
это бесполезно для нас, мы просто хотим использоватьHash[a4]
flatten(1)
Случай ( a6
):Но как насчет частичного сглаживания? Стоит отметить, что вызов Hash::[]
using splat
в частично сглаженном массиве ( a6
) - это не то же самое, что вызов Hash[a4]
:
Hash[*a6] # => ArgumentError: odd number of arguments for Hash
a6
):Но что, если бы мы получили массив именно так? (То есть, по сравнению с a1
нашими входными данными - только на этот раз некоторые данные могут быть массивами или другими объектами.) Мы видели, что Hash[*a6]
это не работает, но что, если бы мы все еще хотели получить поведение, при котором последний элемент (важный! см. ниже) выступал в роли ключа для nil
значения?
В такой ситуации все еще есть способ сделать это, используя Enumerable#each_slice
для возврата к парам ключ / значение как элементам внешнего массива:
a7 = a6.each_slice(2).to_a
# => [["apple", 1], ["banana", 2], [["orange", "seedless"], 3], ["durian"]]
Обратите внимание, что это приводит к получению нового массива, который не " идентичен " a4
, но имеет те же значения :
a4.equal?(a7) # => false
a4 == a7 # => true
Таким образом, мы снова можем использовать Hash::[]
:
Hash[a7] # => {"apple"=>1, "banana"=>2, ["orange", "seedless"]=>3, "durian"=>nil}
# or Hash[a6.each_slice(2).to_a]
Важно отметить, что each_slice(2)
решение возвращает все к здравому смыслу только в том случае, если в последнем ключе отсутствовало значение. Если позже мы добавим дополнительную пару ключ / значение:
a4_plus = a4.dup # just to have a new-but-related variable name
a4_plus.push(['lychee', 4])
# => [["apple", 1],
# ["banana", 2],
# [["orange", "seedless"], 3], # multi-value key
# ["durian"], # missing value
# ["lychee", 4]] # new well-formed item
a6_plus = a4_plus.flatten(1)
# => ["apple", 1, "banana", 2, ["orange", "seedless"], 3, "durian", "lychee", 4]
a7_plus = a6_plus.each_slice(2).to_a
# => [["apple", 1],
# ["banana", 2],
# [["orange", "seedless"], 3], # so far so good
# ["durian", "lychee"], # oops! key became value!
# [4]] # and we still have a key without a value
a4_plus == a7_plus # => false, unlike a4 == a7
И два хэша, которые мы получим из этого, очень сильно отличаются:
ap Hash[a4_plus] # prints:
{
"apple" => 1,
"banana" => 2,
[ "orange", "seedless" ] => 3,
"durian" => nil, # correct
"lychee" => 4 # correct
}
ap Hash[a7_plus] # prints:
{
"apple" => 1,
"banana" => 2,
[ "orange", "seedless" ] => 3,
"durian" => "lychee", # incorrect
4 => nil # incorrect
}
(Примечание: я использую awesome_print
's ap
только для того, чтобы упростить отображение структуры здесь; для этого нет никаких концептуальных требований.)
Таким образом, each_slice
решение проблемы небалансного плоского входа работает только в том случае, если несимметричный бит находится в самом конце.
[key, value]
элементов в виде пар (подмассив для каждого элемента внешнего массива).#to_h
или Hash::[]
оба будут работать.Hash::[]
комбинация со splat ( *
) будет работать, пока входы сбалансированы .value
элемент будет единственным, который отсутствует.Боковое примечание: я публикую этот ответ, потому что чувствую, что есть ценность, которую нужно добавить - некоторые из существующих ответов содержат неверную информацию, и ни один (который я прочитал) не дал столь полного ответа, как я пытаюсь сделать здесь. Я надеюсь, что это поможет. Тем не менее, я благодарю тех, кто был до меня, некоторые из которых вдохновили меня на части этого ответа.
Добавляем к ответу, но используя анонимные массивы и аннотируя:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
Разбирая этот ответ, начиная с внутренней стороны:
"a,b,c,d"
на самом деле строка.split
по запятым в массив.zip
это вместе со следующим массивом.[1,2,3,4]
это фактический массив.Промежуточный результат:
[[a,1],[b,2],[c,3],[d,4]]
Flatten затем преобразует это в:
["a",1,"b",2,"c",3,"d",4]
а потом:
*["a",1,"b",2,"c",3,"d",4]
разворачивает это в
"a",1,"b",2,"c",3,"d",4
которые мы можем использовать в качестве аргументов Hash[]
метода:
Hash[*("a,b,c,d".split(',').zip([1,2,3,4]).flatten)]
что дает:
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
*
) и flatten: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
. Более подробно в ответе я добавил.
если у вас есть массив, который выглядит так -
data = [["foo",1,2,3,4],["bar",1,2],["foobar",1,"*",3,5,:foo]]
и вы хотите, чтобы первые элементы каждого массива стали ключами для хеша, а остальные элементы стали массивами значений, тогда вы можете сделать что-то вроде этого:
data_hash = Hash[data.map { |key| [key.shift, key] }]
#=>{"foo"=>[1, 2, 3, 4], "bar"=>[1, 2], "foobar"=>[1, "*", 3, 5, :foo]}
Не уверен, что это лучший способ, но это работает:
a = ["apple", 1, "banana", 2]
m1 = {}
for x in (a.length / 2).times
m1[a[x*2]] = a[x*2 + 1]
end
b = [["apple", 1], ["banana", 2]]
m2 = {}
for x,y in b
m2[x] = y
end
Если числовые значения являются индексами seq, тогда у нас могут быть более простые способы ... Вот мой код, My Ruby немного ржавый
input = ["cat", 1, "dog", 2, "wombat", 3]
hash = Hash.new
input.each_with_index {|item, index|
if (index%2 == 0) hash[item] = input[index+1]
}
hash #=> {"cat"=>1, "wombat"=>3, "dog"=>2}