Який найкращий спосіб перетворити масив у хеш у Ruby


123

У Ruby дали масив в одній з наступних форм ...

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

... який найкращий спосіб перетворити це в хеш у вигляді ...

{apple => 1, banana => 2}

Відповіді:


91

ПРИМІТКА . Для стислого та ефективного рішення див. Відповідь Марка-Андре Лафортуна нижче.

Ця відповідь спочатку була запропонована як альтернатива підходам із застосуванням плоскостопості, які були найбільш високооціненими на момент написання. Я мав би уточнити, що я не мав наміру представляти цей приклад як найкращу практику чи ефективний підхід. Оригінальна відповідь випливає.


Увага! Рішення, що використовують 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}

15
Це те саме, що і Hash [a3], оскільки a3 == a3.map {| k, v | [k, v]} вірно, це насправді еквівалент a3.dup.
Кластер

2
Замість використання карти, чому б просто не вказати глибину вирівнювання? Наприклад: h3 = Hash[*a3.flatten(1)]замість цього h3 = Hash[*a3.flatten]виникла б помилка.
Джефф Маккун

3
Ця відповідь неефективна. Він також застарів. Дивіться мою відповідь.
Марк-Андре Лафортун

1
Так, я думаю, що Марк-Андре to_hкраще.
Б Сім

1
@ Marc-André Lafortune дякую, я оновив свою відповідь, щоб направити користувачів на ваших.
Тушковане

145

Просто використовуйте 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ключі та значення працюють як очікувалося.


4
О, красномовство! Ось чому я люблю Рубі
iGbanam

11
ПОПЕРЕДЖЕННЯ: відповіді за допомогою вирівнювання викличуть проблеми, якщо вам потрібні клавіші або значення масиву.
Тушковане

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

5
Краще не намагатись і не вирішувати для цього рішення. Якщо ваші ключі та значення спарені, як у [[key1, value1], [key2, value2]], тоді просто передайте їх Hash [] без відгодівлі. Хеш [a2] == Хеш [* a2.flatten]. Якщо масив уже вирівняний, як у [key1, value1, key2, value2], тоді просто приставте var з *, Hash [* a1]
Cluster

8
FWIW, якщо ви дійсно хочете (більше а) версії одного розміру для всіх, ви також можете використовувати Hash[*ary.flatten(1)], що збереже ключі та значення масиву. Це рекурсивне flattenруйнування тих, чого досить легко уникнути.
brymck

79

Найкращим способом є використання 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, це може створити проблеми зі значеннями, які є самими масивами.


4
Дякуємо за простоту нового методу .to_h!
кодування залежних

3
Мені подобається to_h метод краще, ніж наведені вище відповіді, оскільки він виражає наміри перетворення після роботи над масивом.
B Сім

1
@BSeven Ні, Array#to_hні Enumerable#to_hв основній рубіні 1.9.
Залізний Спас

Що робити, якщо у мене є масив як [[apple, 1], [banana, 2], [apple, 3], [banana, 4]]і я хочу, щоб результат був як {"apple" =>[1,3], "banana"=>[2,4]}?
нішант

@NishantKumar - це вже інше питання.
Марк-Андре Лафортун


9

Редагувати: побачив відповіді, опубліковані під час написання, 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 }

2
+1 для a.inject({})однолінійки, що дозволяє більш гнучко призначати значення.
Кріс Блум

Можна також відмовитись h = {}від другого прикладу за допомогою ін'єкції, що закінчуєтьсяa.each_slice(2).inject({}) { |h,i| h[i.first] = i.last; h }
lindes

Ви могли б зробитиa.each_slice(2).to_h
Конор О'Браєн

6

Ви також можете просто перетворити 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} 

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 і може бути підтримано в попередніх версіях). Для початково-плоского масиву це не працює:

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(та будь-якого іншого елемента масиву в а4, який є лише масивом 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::[]за 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'sap просто", щоб полегшити показ структури тут; для цього немає жодної концептуальної вимоги.)

Тож each_sliceрішення для незбалансованого плоского вводу працює лише у тому випадку, якщо небалансований біт знаходиться в самому кінці.


Винос:

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

Побічна примітка: я публікую цю відповідь, оскільки відчуваю, що варто додати цінність - деякі з існуючих відповідей мають неправильну інформацію, і жодна (яку я читав) не дала такої повноцінної відповіді, як я намагаюся робити тут. Я сподіваюся, що це корисно. Я все-таки дякую тим, хто перейшов до мене, декілька з яких дали натхнення для частини цієї відповіді.


3

Звернення до відповіді, але з використанням анонімних масивів та коментарів:

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}

Це також працює без splat ( *) та вирівнювання: Hash[("a,b,c,d".split(',').zip([1,2,3,4]))]=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4}. Більш детально у відповіді я додав.
lindes

0

якщо у вас є масив, який виглядає приблизно так -

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

0

Не впевнений, що це найкращий спосіб, але це працює:

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

-1

Якщо числові значення є послідовними індексами, то у нас можуть бути більш прості способи ... Ось подання мого коду, 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}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.