0

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"=>[""]}
Artjom B.
  • 61,146
  • 24
  • 125
  • 222
Nikos4Life
  • 101
  • 1
  • 9

5 Answers5

3

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"]}
Ilya
  • 13,337
  • 5
  • 37
  • 53
1

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"] }
Lasse Sviland
  • 1,479
  • 11
  • 23
  • This returns `{"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"]}`, which is not the required format. Also, it should be noted that this does not return the new value of `dates`. If, for example, it were wrapped in a method, you'd need a final line `dates`. – Cary Swoveland Apr 10 '17 at 23:47
  • I'd recommend getting familiar with `each_with_object`. – the Tin Man Apr 18 '17 at 21:38
1

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"]}]}
Cameron Roe
  • 290
  • 1
  • 9
1

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"}]}
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
0

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"]}
whodini9
  • 1,434
  • 13
  • 18