2016-05-16 3 views
1

Мне нужно построить массив объектов, чтобы отображать данные на графике, который показывает данные за последние 7 дней. Иногда в базе данных отсутствует определенная 7-дневная запись, поэтому мне нужно показать запись и пометить значение как 0, чтобы иметь 7 объектов в массиве.Итерация по дате за последние 7 дней

Прямо сейчас у меня есть

[ 
    { 
    "date": "2016-05-14", 
    "amount": 6000 
    }, 
    { 
    "date": "2016-05-12", 
    "amount": 12000 
    } 
] 

Что я хочу

[ 
    { 
     "date": "2016-05-14", 
     "amount": 6000 
    }, 
    { 
     "date": "2016-05-13", 
     "amount": 0 
    }, 
    { 
     "date": "2016-05-12", 
     "amount": 12000 
    }, 
    { 
     "date": "2016-05-11", 
     "amount": 0 
    }, 
    { 
     "date": "2016-05-10", 
     "amount": 0 
    }, 
    { 
     "date": "2016-05-09", 
     "amount": 0 
    }, 
    { 
     "date": "2016-05-09", 
     "amount": 0 
    } 
] 
+0

Можете ли вы сказать нам, является дата поле в вашей таблице? –

+0

@PraveshKhatri 'paid_on' да, это поле так в основном, потому что есть записи для тех дат, которые извлекаются данные. для дней его не нужно устанавливать значение по умолчанию –

ответ

6
ts = JSON.parse '[ 
{ 
    "date": "2016-05-14", 
    "amount": 6000 
}, 
{ 
    "date": "2016-05-12", 
    "amount": 12000 
}]' # support ruby < 2.2 

((Date.today-6..Date.today).map do |d| 
    [d.iso8601, {'amount' => 0, 'date' => d.iso8601 }] 
end.to_h.merge ts.group_by { |e| e['date'] }).values.flatten 

Здесь мы начинаем с создания хеша запрашиваемых дней, сопоставленных с нулевыми значениями, а затем объединяем его с хешем существующих значений. Последнее имеет преимущество:

[Date.today].map do |d| 
    [d.iso8601, {'amount' => 0, 'date' => d.iso8601 }] 
end.to_h 
#⇒ { '2016-05-16' => {'amount' => 0, 'date' => '2016-05-16' } } 

(в реальном хэш есть семь пунктов.)

Enumerable#group_by производит ту же структуру:

[ {'amount' => 42, 'date' => Date.today } ].group_by { |e| e['date'] } 
#⇒ { '2016-05-16' => [{'amount' => 42, 'date' => '2016-05-16' }] } 

В качестве последнего шага, мы сливаемся последнего в первый и избавиться от ключей даты, взяв values результата и сгладив его.


Использование Hash#default_proc:

hsh = Hash.new do |h, k| 
    ts.detect do |e| 
    e['date'] == k.iso8601 
    end || { 'amount' => 0, 'date' => k.iso8601 } 
end 
(Date.today-6..Date.today).map { |d| hsh[d] } 
+0

Черт, это пятно :) –

+0

Не могли бы вы поделиться небольшим объяснением, чтобы я мог узнать –

+0

@HarshaMV Выпало немного объяснений. – mudasobwa

1

Я надеюсь, что это помогает.

data = [ 
{ 
    "date": "2016-05-14", 
    "amount": 6000 
}, 
{ 
    "date": "2016-05-12", 
    "amount": 12000 
}] # Given data 
dates = data.map do |datum| datum[:date] end # Extract dates from data. 
today = Date.today # Get today. 
dates_list = 7.times.map do |index| (today + index).strftime("%F") end # Get 7days from today. 
dates_list.each do |date| 
    next if dates.include? date # if data already includes the date, such as "2016-05-14", pass. 
    data << {"date": date, "amount": 0} # if data does not include the date, append it with amount 0. 
end 
1

Это позволит получить правильный ответ:

require 'date' 
require 'pp' 

data = [ 
    { 
    "date": "2016-05-14", 
    "amount": 6000 
    }, 
    { 
    "date": "2016-05-12", 
    "amount": 12000 
    } 
] 

pp data 

today = DateTime.now 

7.times do |day| 
    current_day = (DateTime.now - day).strftime("%F") 
    next if data.find_index {|hash| hash[:date] == current_day } 
    data.push({ "date": current_day, "amount": 0 }) 
end 

pp data 

Это произведет следующий вывод:

[{:date=>"2016-05-14", :amount=>6000}, 
{:date=>"2016-05-12", :amount=>12000}, 
{:date=>"2016-05-15", :amount=>0}, 
{:date=>"2016-05-13", :amount=>0}, 
{:date=>"2016-05-11", :amount=>0}, 
{:date=>"2016-05-10", :amount=>0}, 
{:date=>"2016-05-09", :amount=>0}] 

Если вы хотите, чтобы массив, отсортированный по дате, вы можете использовать это:

data.sort {|a,b| a[:date] <=> b[:date] }.reverse 

Это будет производить thi s выход:

[{:date=>"2016-05-15", :amount=>0}, 
{:date=>"2016-05-14", :amount=>6000}, 
{:date=>"2016-05-13", :amount=>0}, 
{:date=>"2016-05-12", :amount=>12000}, 
{:date=>"2016-05-11", :amount=>0}, 
{:date=>"2016-05-10", :amount=>0}, 
{:date=>"2016-05-09", :amount=>0}] 
2

Функциональный подход с помощью карты уменьшить, вероятно, наиболее простым и эффективным:

timeseries = [ 
    { 
    "date": "2016-05-14", 
    "amount": 6000 
    }, 
    { 
    "date": "2016-05-12", 
    "amount": 12000 
    } 
] 

last_7_days  = 6.days.ago.to_date..Date.today 

# Transform array of hashes into a single hash with each key as a Date object. 
# Eg. { "2016-05-12" => 12000, "2016-05-14" => 6000 } 
counts_each_day = timeseries.reduce(Hash.new(0)) do |acc, item| 
    acc.merge(Date.parse(item[:date]) => item[:amount]) 
end 

# Merge the results with an empty count, if it doesn't exist in our transformed hash. 
filled_timeseries = last_7_days.map do |day| 
    { date: day.to_s, amount: counts_each_day[day] } 
end 

Результат

[ 
    { :date => "2016-05-10", :amount => 0 }, 
    { :date => "2016-05-11", :amount => 0 }, 
    { :date => "2016-05-12", :amount => 12000 }, 
    { :date => "2016-05-13", :amount => 0 }, 
    { :date => "2016-05-14", :amount => 6000 }, 
    { :date => "2016-05-15", :amount => 0 }, 
    { :date => "2016-05-16", :amount => 0 } 
] 
+0

'неявное преобразование даты в строку 'am получает следующую ошибку –

+0

На какой строке ошибка? –

+0

'acc.merge (Date.parse (item [: date]) => item [: amount])' this line –

1

Вот еще один подход, который я думаю, достаточно ясно, и получает дату начала из данных, а не Date.today. Он предполагает, что во входных данных нет недопустимых дат и что вход не пуст.

Обратите внимание, что, указав "string":..., вы говорите то же самое, что и string:..., но в гораздо более запутанном виде. Я думаю, это из данных JSON, но он должен быть проанализирован в массив хешей Ruby. Я изменил это в своем коде.

#!/usr/bin/env ruby 

require 'date' 


def to_date(string) 
    Date.strptime(string, '%Y-%m-%d') 
end 


# Transform the input to a hash whose keys are date objects 
# and whose values are the original input records 
def transform_input_to_hash(input_records) 
    input_records.each_with_object({}) do |record, hsh| 
    key = to_date(record[:date]) 
    hsh[key] = record 
    end 
end 


def fill_in_missing_dates(input_records) 
    input_record_hash = transform_input_to_hash(input_records) 

    start_date = input_record_hash.keys.first 
    output_dates = ((start_date - 6)..start_date).to_a.reverse 

    output_dates.each_with_object([]) do |date, array| 
    array << if input_record_hash.keys.include?(date) 
     input_record_hash[date] 
    else 
     { date: date, amount: 0 } 
    end 
    end 
end 



INPUT_RECORDS = [ 
    { 
    date: "2016-05-14", 
    amount: 6000 
    }, 
    { 
    date: "2016-05-12", 
    amount: 12000 
    } 
] 



output_array = fill_in_missing_dates(INPUT_RECORDS) 
puts output_array 

Кстати, это работает в Ruby без Rails. Кроме того, код размещен по адресу https://gist.github.com/keithrbennett/fd876aac938f1e5d6222896dbd30e8f2.