У 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 , не збережуть ключі або значення масиву!
Спираючись на популярну відповідь @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
в основній рубіні 1.9.
[[apple, 1], [banana, 2], [apple, 3], [banana, 4]]
і я хочу, щоб результат був як {"apple" =>[1,3], "banana"=>[2,4]}
?
Оновлення
Ruby 2.1.0 виходить сьогодні . І я прибуваю Array#to_h
( нотатки випуску та ruby-doc ), що вирішує питання перетворення a Array
в aHash
.
Приклад документів 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
(та будь-якого іншого елемента масиву в а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
рішення для незбалансованого плоского вводу працює лише у тому випадку, якщо небалансований біт знаходиться в самому кінці.
[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]]
вирівнювання потім перетворює це на:
["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}
*
) та вирівнювання: 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
Якщо числові значення є послідовними індексами, то у нас можуть бути більш прості способи ... Ось подання мого коду, 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}