Идиоматический Ruby: преобразование структуры данных - PullRequest
1 голос
/ 13 июня 2009

Что такое "Rubyist" способ сделать следующее преобразование структуры данных:

У меня есть

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

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

Ответы [ 4 ]

8 голосов
/ 13 июня 2009

Если вы используете Ruby 1.8.7 или Ruby 1.9+, следующий код выглядит хорошо:

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

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

# limit - это псевдоним для #inject, который используется для сокращения коллекции в один элемент. В новых версиях Ruby он также принимает символ, который является именем метода, используемого для выполнения сокращения .

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

[1, 3, 2, 2].reduce(:+) => [4, 2, 2] => [6, 2] => 8
2 голосов
/ 13 июня 2009

Вот один вкладыш:)

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

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

РЕДАКТИРОВАТЬ: с небольшой оптимизацией:

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)}}
2 голосов
/ 13 июня 2009

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

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, создающий пустой Hash для нас, чтобы мы обновили значения, которые мы находим. Затем я просто использую дату в качестве хэш-ключей, сортирую хэш-значения по дате, и мы преобразуемся.

0 голосов
/ 13 июня 2009

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

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 членах см. на этой странице

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

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
...