2013-05-31 2 views
2

Учитывая этот массив (сгенерированный из файла)Как я могу реорганизовывать преобразование этого массива в Hash

["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], 
["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], 
["Camden", "DM1182", "54.64 USD"]] 

преобразовать его в хэш индексируется второй элемент (ы) с помощью следующей формы:

result = Hash.new([]) 
trans_data.each do |arr| 
    result[arr[1]].empty? ? result[arr[1]] = [[arr[0], arr[2]]] : result[arr[1]] << [arr[0], arr[2]] 
end 
result 

Это выводит хэш в формате я хочу это:

{"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]} 

Я не чувствую, что мой код ... чистый. Есть ли лучший способ сделать это?

EDIT: До сих пор я был в состоянии заменить его: (result[arr[1]] ||= []) << [arr[0], arr[2]]

С без значения по умолчанию для хэш

+0

Является ли это офис ссылка? DM = Dunder Mifflin? :) – squiguy

+1

Идеальный вопрос, дающий ввод и ожидаемый результат и то, что вы пробовали до сих пор. Отлично сработано! –

+0

@ThomasKlemm На самом деле выражение в начале не является допустимым выражением Ruby. И не объясняется, что такое 'trans_data'. И я не знаю, что означает ску. – sawa

ответ

7

Похоже, люди должны узнать о group_by:

ary = [ 
    ["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], 
    ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], 
    ["Camden", "DM1182", "54.64 USD"] 
] 
hash = ary.group_by{ |a| a.slice!(1) } 

Какие результаты в:

=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]} 

Можно записать это довольно сжато withou т slice!, позволяя ary оставаться неизменными, и без необходимости тянуть в каких-либо дополнительных классах или модулях:

 
irb(main):036:0> Hash[ary.group_by{ |a| a[1] }.map{ |k, v| [k, v.map{ |a,b,c| [a,c] } ] }] 
=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]} 
irb(main):037:0> ary 
=> [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]] 

Несколько других ответов использует each_with_object, что снимает необходимость принуждать возвращаемый массив в хэш с использованием Hash[...]. Вот как я хотел бы использовать each_with_object, чтобы избежать кучи линейного шума внутри блока, как они пытаются инициализировать неизвестные ключей:

ary.each_with_object(Hash.new{ |h,k| h[k] = [] }) { |(a, b, c), h| 
    h[b] << [a, c] 
} 
=> {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]} 

Это использует Hash.new принимает блок инициализации, который вызывается, когда клавиша hasn» t были определены ранее.

+0

Ты мужчина. – oldergod

+0

Нет, это все Ruby; Это отличный язык. –

+0

Я так рад, что задал этот вопрос ... – Senjai

0

попробовать это

arr = [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]] 

hash = Hash.new{|h,k| h[k] = []} 

arr.each{|a| hash[a[1]].push([a[0],a[2]])} 


    hash => {"DM1210"=>[["Yonkers", "70.00 USD"], ["Scranton", "68.76 USD"]], "DM1182"=>[["Yonkers", "19.68 AUD"], ["Nashua", "58.58 AUD"], ["Camden", "54.64 USD"]]} 
+0

Я понятия не имею, как это работает с новым блоком. – Senjai

+0

исправленный код теперь попробуйте –

+0

Ahh, так что это правильный способ установить значение по умолчанию. Раньше мой хэш был пуст, если я его не проиндексировал. – Senjai

2

Примечание: Принимается ответ лучший ответ, но я очень доволен той странной удивительностью, которую я использую, и как я объясняю это:

arr = [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], 
["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], 
["Camden", "DM1182", "54.64 USD"]] 
arr.each_with_object({}){|(a, b, c), hash| (hash[b] || hash[b]=[]).push [a,c]} 

Подкрепление к Старому Богу для each_with_object!

Объяснение: Здесь происходят две путаные вещи. Первый, (a, b, c) магии, я думаю, что это работает так:

( 

    #This bit: 
    arr.collect{|(a,b,c)| "#{a}#{b}#{c}"} 

) - (

    #Is equivalent to this bit: 
    (0..arr.size).collect {|i| 
    (a,b,c) = arr[i] #=> (a,b,c) = ["Yonkers", "DM1210", "70.00 USD"] 
    "#{a}#{b}#{c}" 
    } 

    #as you can see, they generate identical arrays: 
) == [] 

Обратите внимание, что вы можете рассматривать скобки, как заложенная в определенных обстоятельствах: arr.collect{|a, b, c| [a, b, c]} == arr

второй дурацкая вещь:

(hash[b] || hash[b]=[]).push(...) 

Помните, что все в Ruby является выражением и ссылкой.

[ 

(hash[:a] || "foo") == (nil || "foo"), 
(hash[:b]=[]) == [], 
(hash[:b]=[]) === hash[:b], 
(hash[:b] || "foo") == ([] || "foo"), 

] == [true, true, true, true] 

hash[b], когда ключ не существует, вычисляется в nil (который falsey), поэтому мы оцениваем и вернуть вторую половину: hash[b]=[], которая возвращает значение задания, которое является массивом в настоящее время ссылается hash[b], поэтому мы можем нажать на него, и hash[b] будет [по-прежнему] ссылаться на обновленный массив.

: D

PS - Это, как мне кажется, первый рубин вопрос, который я когда-либо отвечал, и это первый раз, когда я даже подумал, не говоря уже быть в состоянии повернуть комментарии в код, и о, мне нравится. Спасибо за загадку!

+0

Мне очень нравится использование здесь короткого замыкания! Brilliant – Senjai

+0

Я обновил свое сообщение с рефакторинговой версией до сих пор. – Senjai

+1

То, что вы называете «дурацким», на самом деле не имеет ничего общего. Они могли бы вас удивить, но они логически используют язык. Мы использовали трюк '||' для назначения или инициализации, а затем назначение, в Perl несколько лет назад. Вероятно, он заимствован из сценариев оболочки с немного другим синтаксисом или C. –

3

насчет

result = trans_data.each_with_object({}) do |arr, hash| 
    (hash[arr[1]] ||= []) << [arr[0], arr[2]] 
end 
+0

Я даже не знал, что существует каждый_with_object. так что проходит в пустой хеш на каждой итерации? Или это настойчиво. – Senjai

+0

Предположим, что он отслеживает объект object_id, который вы передали. Таким образом, вам не нужно * копировать * объект каждый раз, как в 'inject'. – oldergod

+0

Ах, я понимаю. Отлично. Спасибо, что научили меня чему-то новому и не спеша, чтобы помочь другим :) – Senjai

4

Функциональный подход с использованием абстракции Enumerable#map_by из Грановитой:

require 'facets' 
records.map_by { |name, key, price| [key, [name, price]] } 
#=> {"DM1210"=>[["Yonkers", "70.00 USD"], ... } 

Жаль, что Руби не грузит map_by в ядре, это очень полезно (как это неизвестно) изменение Enumerable#group_by (где вы выбираете ключ группировки и значение для накопления).

+0

Я только что проверил 'map_by'. Я думаю, что это сложно использовать, особенно когда второй элемент массива является фаальным. Например, '[[: a, true], [: b, false]]. Map_by {| v1, v2 | [V1, v2]} '' {дает : а => [истинно], : Ь => [[ : б, ложные ]] } ', который является нелогичным. – sawa

+1

@sawa, вы правы, но это не проблема абстракции как таковой, это ошибка в реализации: она должна выполнить более тщательную проверку возвращаемого значения или вообще полностью отключить функцию «act like group_by» (это не имеет смысла, если я хочу что-то действовать, как 'group_by' ... Я использую' group_by'). Конечно, он должен возвращать '{: a => [true],: b => [false]}'. Я открою билет. – tokland

+1

У граней есть очень удобный код, в котором он похож на Rails. Я бы хотел, чтобы основная команда сделала выбор вишни и вытащила больше полезных методов из обоих. У Active Support есть отличные инструменты, которые я часто вытягиваю, хотя редко работаю в Rails. –

0

Более или менее извлеченный из facets library tokland предлагает:

ary = [["Yonkers", "DM1210", "70.00 USD"], ["Yonkers", "DM1182", "19.68 AUD"], ["Nashua", "DM1182", "58.58 AUD"], ["Scranton", "DM1210", "68.76 USD"], ["Camden", "DM1182", "54.64 USD"]] 

hash = {} 
ary.each{ |a,b,c| (hash[b] ||= []) << [a,c] } 

hash 
# => {"Camden"=>[["DM1182", "54.64 USD"]], "Nashua"=>[["DM1182", "58.58 AUD"]], "Scranton"=>[["DM1210", "68.76 USD"]], "Yonkers"=>[["DM1210", "70.00 USD"], ["DM1182", "19.68 AUD"]]} 
Смежные вопросы