2009-06-13 3 views
1

Что за «Rubyist» способ сделать следующую структуру данных преобразования:Идиоматические Ruby: структура данных преобразования

У меня есть

 
    incoming = [ {:date => 20090501, :width => 2}, 
       {:date => 20090501, :height => 7}, 
       {:date => 20090501, :depth => 3}, 
       {:date => 20090502, :width => 4}, 
       {:date => 20090502, :height => 6}, 
       {:date => 20090502, :depth => 2}, 
       ] 

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

 
    outgoing = [ {:date => 20090501, :width => 2, :height => 7, :depth => 3}, 
       {:date => 20090502, :width => 4, :height => 6, :depth => 2}, 
       ] 

массив массивов также будет прекрасно на последнем шаге, при условии, что столбцы находятся в том же порядке, в каждой строке. Кроме того, что важно, я не знаю всех хэш-ключей заранее (то есть, я не знаю: width,: height, или: depth - они могут быть: кошки, собаки и хомяки).

+1

Если заказ важен, чем вы * хотите * массивы. Опора на хеш-порядок - плохая идея и работает только в Ruby 1.9. – Chuck

+0

Да, но если я сохраню структуру хеша, тогда мои ключи дадут мне любой заказ, который мне нужен.Я просто сделал дополнительный комментарий о заказе массива массивов, потому что нет возможности вернуть его обратно после факта, если у меня есть неупорядоченный подматрица. –

ответ

8

При использовании рубин 1.8.7 или рубин 1.9+ следующий код хорошо читается:

incoming.group_by{|hash| hash[:date]}.map do |_, hashes| 
    hashes.reduce(:merge) 
end 

Подчеркивание в атрибутах блоков (_, хэши) указывает на то, что нам не нужно/заботиться о том, что особенно атрибут.

#reduce является псевдонимом для #inject, который используется для уменьшить сбор в единый товар. В новых версиях Ruby он также принимает символ, который является именем метода, используемого для уменьшения .

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

[1, 3, 2, 2].reduce(:+) => [4, 2, 2] => [6, 2] => 8 
+0

nice ... не знал о функции group_by в последних рубинах. – SztupY

0

Попробуйте это:

incoming = [ {:date => 20090501, :width => 2}, 
       {:date => 20090501, :height => 7}, 
       {:date => 20090501, :depth => 3}, 
       {:date => 20090502, :width => 4}, 
       {:date => 20090502, :height => 6}, 
       {:date => 20090502, :depth => 2}, 
       ] 

# Grouping by `:date` 
temp = {} 

incoming.each do |row| 
    if temp[row[:date]].nil? 
     temp[row[:date]] = [] 
    end 

    temp[row[:date]] << row 
end  

# Merging it together 
outcoming = []   

temp.each_pair do |date, hashlist| 
    res = {} 
    hashlist.each do |hash| 
     res.merge!(hash) 
    end 
    outcoming << res 
end 

Для получения дополнительной информации, касающейся hash -members см this page

При заказе важно, вы должны использовать неровные массивы:

incoming = [ {:date => 20090501, :width => 2}, 
       {:date => 20090501, :height => 7}, 
       {:date => 20090501, :depth => 3}, 
       {:date => 20090502, :width => 4}, 
       {:date => 20090502, :height => 6}, 
       {:date => 20090502, :depth => 2}, 
       ] 

# Grouping by `:date` 
temp = {} 

incoming.each do |row| 
    if temp[row[:date]].nil? 
     temp[row[:date]] = [] 
    end 
    key = row[:date] 
    row.delete :date 
    temp[key] << row 
end  

# Merging it together 
outcoming = []   

temp.each_pair do |date, hashlist| 
    res = [:date, date] 
    hashlist.each do |hash| 
     hash.each_pair {|key, value| res << [key, value] } 
    end 
    outcoming << res 
end 
2

Вот один лайнер :)

incoming.inject({}){ |o,i| o[i[:date]]||=[];o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}} 

Но на самом деле предыдущий пост более ясен, и может быть и быстрее.

EDIT: с немного оптимизации:

p incoming.inject(Hash.new{|h,k| h[k]=[]}){ |o,i| o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}} 
+0

Пояснение: first inject создает хэш с датами в виде ключей и массивом хешей в качестве значений. Затем карта объединит все эти хэши в одну. – SztupY

+0

Ницца в любом случае ;-) – Dario

2

краткое решение:

incoming = [ {:date => 20090501, :width => 2}, 
      {:date => 20090501, :height => 7}, 
      {:date => 20090501, :depth => 3}, 
      {:date => 20090502, :width => 4}, 
      {:date => 20090502, :height => 6}, 
      {:date => 20090502, :depth => 2}, 
      ] 

temp = Hash.new {|hash,key| hash[key] = {}} 
incoming.each {|row| temp[row[:date]].update(row)} 
outgoing = temp.values.sort {|*rows| rows[0][:date] <=> rows[1][:date]} 

Единственное, что это вообще сложно здесь является Hash конструктор, который позволяет поставить блок это вызывается при доступе к несуществующему ключу. Поэтому у меня есть Hash, создающий пустой хэш для нас, чтобы обновить значения, которые мы находим. Затем я просто использую дату как хэш-ключи, сортирую хэш-значения по дате, и мы преобразуемся.