Каков наилучший способ преобразования массива в хэш в Ruby



в Ruby задается массив в одной из следующих форм...



[apple, 1, banana, 2]
[[apple, 1], [banana, 2]]


...каков наилучший способ преобразовать это в хэш в виде...



{apple => 1, banana => 2}
762   11  

11 ответов:

Примечание: для краткого и эффективного решения, пожалуйста, смотрите ответ Марка-Андре Лафорт ниже.

этот ответ был первоначально предложен в качестве альтернативы подходам с использованием 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}

просто использовать 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 ключи и значения работают, как ожидалось.

лучший способ-использовать Array#to_h:

[ [:apple,1],[:banana,2] ].to_h  #=> {:apple => 1, :banana => 2}

Примечание: это было введено в Ruby 2.1.0. Для старых Руби, вы можете использовать мой backports камень и require 'backports/2.1.0/array/to_h', или же использовать Hash[]:

array = [ [:apple,1],[:banana,2] ]
Hash[ array ]  #= > {:apple => 1, :banana => 2}

это доступно в Ruby 1.8.7 и выше. Если вы все еще используете Ruby 1.8.6, вы можете require "backports/1.8.7/hash/constructor", но тогда вы могли бы также использовать to_h backport.

наконец, в то время как многие решения использовать flatten, это может создать проблемы со значениями, которые сами массивы.

обновление

Ruby 2.1.0 выпущен сегодня. И я иду с Array#to_h (заметки и ruby-doc), что решает проблему преобразования Array до Hash.

Ruby docs пример:

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

Edit: видел ответы, опубликованные в то время как я писал, хэш[a.flatten] кажется, путь. Должно быть, пропустил этот бит в документации, когда я думал над ответом. Думал, что решения, которые я написал могут быть использованы в качестве альтернативы, если требуется.

вторая форма проще:

a = [[:apple, 1], [:banana, 2]]
h = a.inject({}) { |r, i| r[i.first] = i.last; r }

a = array, h = hash, r = return-value hash (тот, в котором мы накапливаемся), i = item in the array

самый аккуратный способ, которым я можно подумать, что делать первую форму это что-то вроде этого:

a = [:apple, 1, :banana, 2]
h = {}
a.each_slice(2) { |i| h[i.first] = i.last }

вы также можете просто преобразовать 2D массив в хэш с помощью:

1.9.3p362 :005 > a= [[1,2],[3,4]]

 => [[1, 2], [3, 4]]

1.9.3p362 :006 > h = Hash[a]

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

добавление к ответу, но с использованием анонимных массивов и аннотирования:

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

сгладить затем преобразует это:

["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}

резюме & TL; DR:

этот ответ надеется быть всесторонним обобщением информации из других ответов.

очень короткая версия, учитывая данные из вопроса плюс пара дополнений:

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, и может быть backported к более ранним версиям). Для изначально плоского массива это не работает:

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 a ключ и durian a стоимостью.

и это, как с a1, просто не работает:

a5.to_h # => TypeError: wrong element type String at 0 (expected array)

так a4.flatten не полезно для нас, мы просто хотим использовать Hash[a4]

The flatten(1) case (a6):

но как насчет только частичного уплощения? Стоит отметить, что вызов Hash::[] используя 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_prints ap просто чтобы было легче показать структуру здесь; для этого нет концептуального требования.)

так each_slice решение для несбалансированного плоского входа работает только в том случае, если несбалансированный бит находится в самом конце.


Take-aways:

  1. всякий раз, когда это возможно, установите вход для этих вещей как [key, value] пары (суб-массив для каждого элемента во внешнем массиве).
  2. когда вы действительно можете это сделать, либо #to_h или Hash::[] будет обе работы.
  3. если вы не можете,Hash::[] в сочетании с splat (*) будет работать, пока входы сбалансированы.
  4. с несбалансированного и квартира массив в качестве входных данных, единственный способ это будет работать на всех разумно-если последниеvalue item-единственный, который отсутствует.

Side-note: я публикую этот ответ потому что я чувствую, что есть ценность, которую нужно добавить – некоторые из существующих ответов имеют неверную информацию, и ни один (который я читал) не дал такого полного ответа, как я пытаюсь сделать здесь. Я надеюсь, что это поможет. Тем не менее я благодарю тех, кто пришел до меня, некоторые из которых вдохновили меня на часть этого ответа.

Если у вас есть массив, который выглядит вот так -

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, то мы могли бы иметь более простые способы... Вот мой код представления, мой Рубин немного ржавый

   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}

Comments

    Ничего не найдено.