Nikos4Life
Nikos4Life

Reputation: 101

How to group Date and time data from API

I am trying to group data I am getting from an API to serve to our front application. I mean group "time" by "date". dates: {date1: [time1, time2, timeN], date2: [time1...]}

My input is like this:

{"date"=>"2017-04-04T00:00:00", "time"=>"1754-01-01T13:00:00"}
{"date"=>"2017-04-04T00:00:00", "time"=>"1754-01-01T14:00:00"}
{"date"=>"2017-04-05T00:00:00", "time"=>"1754-01-01T12:00:00"}
{"date"=>"2017-04-05T00:00:00", "time"=>"1754-01-01T13:00:00"}

And my output should be like this:

dates: [{date: "2017-04-04T00:00:00", availableTimes: ["1754-01-01T13:00:00", "1754-01-01T14:00:00"]}, {date: "2017-04-05T00:00:00", availableTimes: ["1754-01-01T12:00:00", "1754-01-01T13:00:00"]}]

I am trying to do this this way but without going into loop madness. I have the following:

dates  = Hash[input_data.map{|sd| [sd.date, [""]]}]

This gives me the data outpout like this:

{"2017-04-04T00:00:00"=>[""],
 "2017-04-05T00:00:00"=>[""],
 "2017-04-11T00:00:00"=>[""],
 "2017-04-12T00:00:00"=>[""],
 "2017-04-18T00:00:00"=>[""],
 "2017-04-19T00:00:00"=>[""],
 "2017-04-25T00:00:00"=>[""],
 "2017-04-26T00:00:00"=>[""]}

Upvotes: 0

Views: 96

Answers (5)

Cary Swoveland
Cary Swoveland

Reputation: 110725

I would use Enumerable#group_by.

dates = [{"date"=>"2017-04-04T00:00:00", "time"=>"1754-01-01T13:00:00"},
         {"date"=>"2017-04-04T00:00:00", "time"=>"1754-01-01T14:00:00"},
         {"date"=>"2017-04-05T00:00:00", "time"=>"1754-01-01T12:00:00"},
         {"date"=>"2017-04-05T00:00:00", "time"=>"1754-01-01T13:00:00"}]

dates.group_by { |g| g["date"] }.
      map { |k,v| { date: k, available_times: v.map { |h| h["time"] } } }
  #=> [{:date=>"2017-04-04T00:00:00",
  #     :available_times=>["1754-01-01T13:00:00", "1754-01-01T14:00:00"]},
  #    {:date=>"2017-04-05T00:00:00",
  #     :available_times=>["1754-01-01T12:00:00", "1754-01-01T13:00:00"]}] 

The first step produces the following intermediate value:

dates.group_by { |g| g["date"] }      
  #=> {"2017-04-04T00:00:00"=>
  #     [{"date"=>"2017-04-04T00:00:00", "time"=>"1754-01-01T13:00:00"},
  #      {"date"=>"2017-04-04T00:00:00", "time"=>"1754-01-01T14:00:00"}],
  #    "2017-04-05T00:00:00"=>
  #     [{"date"=>"2017-04-05T00:00:00", "time"=>"1754-01-01T12:00:00"},
  #      {"date"=>"2017-04-05T00:00:00", "time"=>"1754-01-01T13:00:00"}]}

Upvotes: 1

whodini9
whodini9

Reputation: 1434

There are probably more elegant ways, but

results = Hash.new
dates.each do |date|
  d, t = date['date'].split('T') # (clean up/split date and time formatting)
  results.key?(d) ? nil : results[d] = Array.new
  results[d] << t
end
puts results
# => {"2017-04-04"=>["13:00:00", "14:00:00"], "2017-04-05"=>["12:00:00", "13:00:00"]}

Upvotes: 0

Cameron Roe
Cameron Roe

Reputation: 290

You are getting that output because your map function is not actually modifying any sort of data structure. It is simply returning a new array full of arrays that contain the date and an array with an empty string. Basically, this isn't going to be done with just a single map call.

So, the basic algorithm would be:

  1. Find array of all unique dates
  2. Loop through unique dates and use select to only get the date/time pairs for the current date in the loop iteration
  3. Set up the data in the format you prefer

This code will have filteredDates be in the format you need the data

filteredDates = { dates: [] }
uniqueDates = input_data.map { |d| d["date"] }.uniq # This is an array of only unique dates
uniqueDates.each do |date|
    dateTimes = input_data.select { |d| d["date"] == date }
    newObj = { date: date }
    newObj[:availableTimes] = dateTimes.map { |d| d["time"] }
    filteredDates[:dates].push(newObj)
end

Here is what filteredDates will look like:

{:dates=>[{:date=>"2017-04-04T00:00:00", :availableTimes=>["1754-01-01T13:00:00", "1754-01-01T14:00:00"]}, {:date=>"2017-04-05T00:00:00", :availableTimes=>["1754-01-01T12:00:00", "1754-01-01T13:00:00"]}]}

Upvotes: 1

Ilya
Ilya

Reputation: 13487

Just one possible way:

input.each_with_object(Hash.new { |h, k| h[k] = [] }) do |h, m|
  m[h['date']] << h['time']
end.map { |k, v| { date: k, avaliable_times: v } }
#=> [{:date=>"2017-04-04T00:00:00", :avaliable_times=>["1754-01-01T13:00:00", "1754-01-01T14:00:00"]},
#    {:date=>"2017-04-05T00:00:00", :avaliable_times=>["1754-01-01T12:00:00", "1754-01-01T13:00:00"]}]

Actually, it seems like your data structure would be more concise without last map, I mean:

#=> {"2017-04-04T00:00:00"=>["1754-01-01T13:00:00", "1754-01-01T14:00:00"],
#    "2017-04-05T00:00:00"=>["1754-01-01T12:00:00", "1754-01-01T13:00:00"]}

Upvotes: 3

Lasse Sviland
Lasse Sviland

Reputation: 1517

There is many ways you can do this, one way is to create a new hash, and set the default value to be an array, then loop over the results and insert the dates:

dates = Hash.new { |hash, key| hash[key] = [] }
input_data.each{ |sd| dates[sd["date"]] << sd["time"] }

Upvotes: 1

Related Questions