Home > Enterprise >  Ruby sort hash by day name to calculate free time slots
Ruby sort hash by day name to calculate free time slots

Time:06-23

Front-end sends me a string in which it gets the user's meeting schedule which looks like below:

> payload
=> "Sun 10:00-20:00\nFri 05:00-10:00\nFri 16:30-23:50\nSat 10:00-24:00\nSun 01:00-04:00\nSat 02:00-06:00\nTue 03:30-18:15\nTue 19:00-20:00\nWed 04:25-15:14\nWed 15:14-22:40\nThu 00:00-23:59\nMon 05:00-13:00\nMon 15:00-21:00"

Each line is a substring representing one meeting in the schedule. One string represents whole week. I have to calculate the free slots between meetings (in minutes) and send this information to front-end.

I think to do this I need to change the string to a hash like below:

# first split payload
> splitted_dates = payload.split("\n").map { |date| date.split }
=> [["Sun", "10:00-20:00"], ["Fri", "05:00-10:00"], ["Fri", "16:30-23:50"], ["Sat", "10:00-24:00"], ["Sun", "01:00-04:00"], ["Sat", "02:00-06:00"], ["Tue", "03:30-18:15"], ["Tue", "19:00-20:00"], ["Wed", "04:25-15:14"], ["Wed", "15:14-22:40"], ["Thu", "00:00-23:59"], ["Mon", "05:00-13:00"], ["Mon", "15:00-21:00"]]

# then create a hash with structure { day:, time: }
> schedule_hash = splitted_dates.map { |d| { day: d[0], time: d[1] } }
=> [{:day=>"Sun", :time=>"10:00-20:00"},
 {:day=>"Fri", :time=>"05:00-10:00"},
 {:day=>"Fri", :time=>"16:30-23:50"},
 {:day=>"Sat", :time=>"10:00-24:00"},
 {:day=>"Sun", :time=>"01:00-04:00"},
 {:day=>"Sat", :time=>"02:00-06:00"},
 {:day=>"Tue", :time=>"03:30-18:15"},
 {:day=>"Tue", :time=>"19:00-20:00"},
 {:day=>"Wed", :time=>"04:25-15:14"},
 {:day=>"Wed", :time=>"15:14-22:40"},
 {:day=>"Thu", :time=>"00:00-23:59"},
 {:day=>"Mon", :time=>"05:00-13:00"},
 {:day=>"Mon", :time=>"15:00-21:00"}]

To calculate the free slots I first have to sort schedule_hash by date and time. How to do so if both are just a string?

CodePudding user response:

You can do that as follows.

schedule_hash =[
  {:day=>"Sun", :time=>"10:00-20:00"},
  {:day=>"Fri", :time=>"05:00-10:00"},
  {:day=>"Fri", :time=>"16:30-23:50"},
  {:day=>"Sat", :time=>"10:00-24:00"},
  {:day=>"Sun", :time=>"01:00-04:00"},
  {:day=>"Sat", :time=>"02:00-06:00"},
  {:day=>"Tue", :time=>"03:30-18:15"},
  {:day=>"Tue", :time=>"19:00-20:00"},
  {:day=>"Wed", :time=>"04:25-15:14"},
  {:day=>"Wed", :time=>"15:14-22:40"},
  {:day=>"Thu", :time=>"00:00-23:59"},
  {:day=>"Mon", :time=>"05:00-13:00"},
  {:day=>"Mon", :time=>"15:00-21:00"}
]
day_order = { "Sun"=>0, "Mon"=>1, "Tue"=>2, "Wed"=>3,  "Thu"=>4, "Fri"=>5, "Sat"=>6 }
schedule_hash.sort_by { |h| [day_order[h[:day]], h[:time][0,5]] }
  #=> [{:day=>"Sun", :time=>"01:00-04:00"},
  #    {:day=>"Sun", :time=>"10:00-20:00"},
  #    {:day=>"Mon", :time=>"05:00-13:00"},
  #    {:day=>"Mon", :time=>"15:00-21:00"},
  #    {:day=>"Tue", :time=>"03:30-18:15"},
  #    {:day=>"Tue", :time=>"19:00-20:00"},
  #    {:day=>"Wed", :time=>"04:25-15:14"},
  #    {:day=>"Wed", :time=>"15:14-22:40"},
  #    {:day=>"Thu", :time=>"00:00-23:59"},
  #    {:day=>"Fri", :time=>"05:00-10:00"},
  #    {:day=>"Fri", :time=>"16:30-23:50"},
  #    {:day=>"Sat", :time=>"02:00-06:00"},
  #    {:day=>"Sat", :time=>"10:00-24:00"}]

Enumerable#sort_by uses the method Array#<=> (the "spaceship operator") to sort arrays. See especially the third paragraph of the doc for <=>.

Note that the arrays by which the elements of schedule_hash are sorted are as follows.

schedule_hash.map { |h| [day_order[h[:day]], h[:time][0,5]] }
  #=> [[0, "10:00"], [5, "05:00"], [5, "16:30"], [6, "10:00"],
  #    [0, "01:00"], [6, "02:00"], [2, "03:30"], [2, "19:00"],
  #    [3, "04:25"], [3, "15:14"], [4, "00:00"], [1, "05:00"],
  #    [1, "15:00"]]

CodePudding user response:

The Ruby class Date already has an array of abbreviated daynames with Date::ABBR_DAYNAMES. You can use that (especially in Rails where it's already included - maybe don't add it solely for this purpose), but note that "Sun" is first, so if your target audience starts their week on a Monday, as most countries around the world do, you can call rotate on the resulting array.

I would also probably separate the start time and end time of each appointment to make it easier to perform calculations on them later:

require 'date'
daynames = Date::ABBR_DAYNAMES
  => ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

# Turn splitted_dates to a hash with day, start time and end time of appointment
schedule_hash = splitted_dates.map do |(day, time)|
  { day: day, start_time: time[0..4], end_time: time[-5..-1] }
end

# Sort the hash by the index of the day name within "daynames", and then by start_time
sorted = schedule_hash.sort_by { |h| [daynames.index(h[:day]), h[:start_time]] }
  •  Tags:  
  • ruby
  • Related