Ruby: Как я могу преобразовать этот массив в этот хэш? - PullRequest
0 голосов
/ 28 июня 2018

У меня есть массив массивов. Каждый элемент в массиве содержит три строки: количество ног, животное и звук.

a = [ ['4', 'dog', 'woof'] , ['4', 'cow', 'moo'], ['2', 'human', 'yo'] , ['2', 'yeti', 'wrarghh'] ]

Я хочу превратить массив в этот хеш:

{ 
  '2' => [ { 'human' => 'yo' }, { 'yeti' => 'wrarghh'} ],
  '4' => [ { 'dog' => 'woof' }, { 'cow' => 'moo'} ]
}

Я думал, что сокращение будет хорошим способом, но мне не повезло Мой текущий удар выглядит так:

a.reduce({}) do |acc, item|
             acc[item.first] = [] unless acc.key? item.first
             acc[item.first] << { item[1] => item[2] }
           end

Но появляется ошибка:

NoMethodError: undefined method `key?' for [{"dog"=>"woof"}]:Array

Каков наилучший способ достичь этого?

Ответы [ 2 ]

0 голосов
/ 28 июня 2018
a.each_with_object({}) { |(kout, kin, val), h| (h[kout] ||= []) << { kin => val } }
  #=> {"4"=>[{"dog"=>"woof"}, {"cow"=>"moo"}], "2"=>[{"man"=>"yo"}, {"yeti"=>"wrarghh"}]}

У нас есть

enum = a.each_with_object({})
  #=> #<Enumerator: [["4", "dog", "woof"], ["4", "cow", "moo"], ["2", "man", "yo"],
  #                  ["2", "yeti", "wrarghh"]]:each_with_object({})>

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

(kout, kin, val), h = enum.next
  #=> [["4", "dog", "woof"], {}]

, который разлагается следующим образом.

kout
  #=> "4"
kin
  #=> "dog"
val
  #=> "woof"
h #=> {}

Таким образом, расчет блока равен

(h[kout] ||= []) << { kin => val }
  #=> (h[kout] = h[kout] || []) << { "dog" => "wolf" }
  #=> (h["4"] = h["4"] || []) << { "dog" => "wolf" }
  #=> (h["4"] = nil ||= []) << { "dog" => "wolf" }
  #=> (h["4"] = []) << { "dog" => "wolf" }
  #=> [] << { "dog" => "wolf" }
  #=> [{ "dog" => "wolf" }]

h["4"] || [] #=> [], поскольку h не имеет ключа "4" и, следовательно, h["4"] #=> nil.

Следующее значение enum передается в блок, и вычисления повторяются.

(kout, kin, val), h = enum.next
  #=> [["4", "cow", "moo"], {"4"=>[{"dog"=>"woof"}]}]
kout
  #=> "4"
kin
  #=> "cow"
val
  #=> "moo"
h #=> {"4"=>[{"dog"=>"woof"}]}

(h[kout] ||= []) << { kin => val }
  #=> (h[kout] = h[kout] || []) << { "cow" => "moo" }
  #=> (h["4"] = h["4"] || []) << { "cow" => "moo" }
  #=> (h["4"] = [{"dog"=>"woof"}] ||= []) << { "cow" => "moo" }
  #=> (h["4"] = [{"dog"=>"woof"}]) << { "cow" => "moo" }
  #=> [{"dog"=>"woof"}] << { "cow" => "moo" }
  #=> [{ "dog" => "wolf" }, { "cow" => "moo" }]

На этот раз h["4"] || [] #=> [{ "dog" => "wolf" }], поскольку h теперь имеет ключ "4" с истинным значением ([{ "dog" => "wolf" }]).

Остальные расчеты аналогичны.

0 голосов
/ 28 июня 2018

Ваш способ работает, но для reduce возвращаемое значение (т. Е. Последняя строка) блока становится следующим значением для (в данном случае) acc, так что все вы нужно изменить это:

a.reduce({}) do |acc, item|
  acc[item.first] = [] unless acc.key? item.first
  acc[item.first] << { item[1] => item[2] }

  acc # just add this line
end

Поскольку возвращаемым значением для Array#<< является сам массив, вторая итерация дала acc в качестве массива для первого элемента. Конечно, есть много способов сделать это, некоторые из них, возможно, более чистые, но я считаю, что полезно знать, где я ошибся, когда что-то, по моему мнению, должно сработать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...