As part of some code that runs a booking system, we have a list of time_slots
, which are tuples containing {start_time, end_time}
. These are the available time slots that can be booked:
time_slots = [
{~T[09:00:00], ~T[13:00:00]},
{~T[09:00:00], ~T[17:00:00]},
{~T[09:00:00], ~T[21:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[21:00:00]},
{~T[17:00:00], ~T[21:00:00]}
]
Then we also have a list of bookings, which contains lists of tuples containing each {booking_start, booking_end}
.
bookings = [
[
{~N[2019-06-13 09:00:00], ~N[2019-06-13 17:00:00]},
{~N[2019-06-13 17:00:00], ~N[2019-06-13 21:00:00]}
],
[{~N[2019-06-20 09:00:00], ~N[2019-06-20 21:00:00]}],
[
{~N[2019-06-22 13:00:00], ~N[2019-06-22 17:00:00]},
{~N[2019-06-22 17:00:00], ~N[2019-06-22 21:00:00]}
]
]
In this case, we would want the results to be the two bookings with all of their time_slots
filled up:
2019-06-13
2019-06-20
As they have all of their time slots filled up, and then return these results as Date
s.
To provide a bit more information:
0900–1000
would fill the 0900–1300
, 0900–1700
and 0900–2100
time slots 0900–1000
and 1000–1200
, which would both fit inside the 0900–1300
time slot. 0800—2200
would fill the 0900–2100
time slot (along with all the others) So my understanding of the question is: for a list of bookings, do all time slots conflict with at least one booking?
A conflicting booking can be answered by checking two things:
If the booking starts BEFORE the time slot starts, it conflicts if the booking finishes AFTER the time slot starts.
If the booking starts ON OR AFTER the time slot starts, it conflicts if the BOOKING starts before the time slot finishes.
A working code therefore would look like:
time_slots = [
{~T[09:00:00], ~T[13:00:00]},
{~T[09:00:00], ~T[17:00:00]},
{~T[09:00:00], ~T[21:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[21:00:00]},
{~T[17:00:00], ~T[21:00:00]}
]
bookings = [
[
{~N[2019-06-13 09:00:00], ~N[2019-06-13 17:00:00]},
{~N[2019-06-13 17:00:00], ~N[2019-06-13 21:00:00]}
],
[{~N[2019-06-20 09:00:00], ~N[2019-06-13 21:00:00]}],
[
{~N[2019-06-22 13:00:00], ~N[2019-06-22 17:00:00]},
{~N[2019-06-22 17:00:00], ~N[2019-06-22 21:00:00]}
]
]
bookings
|> Enum.filter(fn booking ->
Enum.all?(time_slots, fn {time_start, time_end} ->
Enum.any?(booking, fn {booking_start, booking_end} ->
if Time.compare(booking_start, time_start) == :lt do
Time.compare(booking_end, time_start) == :gt
else
Time.compare(booking_start, time_end) == :lt
end
end)
end)
end)
|> Enum.map(fn [{booking_start, _} | _] -> NaiveDateTime.to_date(booking_start) end)
PS: note you should not compare time/date/datetime with >
, <
and friends. Always use the relevant compare functions.
Although this might not cover all cases, given the sample data you provided this would work
defmodule BookingsTest do
@slots [
{~T[09:00:00], ~T[13:00:00]},
{~T[09:00:00], ~T[17:00:00]},
{~T[09:00:00], ~T[21:00:00]},
{~T[13:00:00], ~T[17:00:00]},
{~T[13:00:00], ~T[21:00:00]},
{~T[17:00:00], ~T[21:00:00]}
]
def booked_days(bookings, time_slots \\ @slots) do
Enum.reduce(bookings, [], fn(day_bookings, acc) ->
Enum.reduce(day_bookings, time_slots, fn({%{hour: s_time}, %{hour: e_time}}, ts) ->
Enum.reduce(ts, [], fn
({%{hour: slot_s}, %{hour: slot_e}} = slot, inner_acc) ->
case is_in_slot(s_time, e_time, slot_s, slot_e) do
true -> inner_acc
_ -> [slot | inner_acc]
end
end)
end)
|> case do
[] -> [day_bookings | acc]
_ -> acc
end
end)
|> Enum.reduce([], fn([{arb, _} | _], acc) -> [NaiveDateTime.to_date(arb) | acc] end)
end
def is_in_slot(same_start, _, same_start, _), do: true
def is_in_slot(s_time, e_time, slot_s, slot_e) when s_time < slot_s and e_time > slot_s, do: true
def is_in_slot(s_time, e_time, slot_s, slot_e) when s_time > slot_s and s_time < slot_e, do: true
def is_in_slot(_, _, _, _), do: false
end
> bookings = [
[
{~N[2019-06-13 10:00:00], ~N[2019-06-13 17:00:00]},
{~N[2019-06-13 17:00:00], ~N[2019-06-13 21:00:00]}
],
[{~N[2019-06-20 09:00:00], ~N[2019-06-20 21:00:00]}],
[
{~N[2019-06-22 13:00:00], ~N[2019-06-22 17:00:00]},
{~N[2019-06-22 17:00:00], ~N[2019-06-22 21:00:00]}
]
]
> BookingsTest.booked_days(bookings)
[~D[2019-06-13], ~D[2019-06-20]]
The idea is, reduce through the bookings
list accumulating into an empty list, each enumeration will be the list of occupied slots for the day.
Reduce through this list, accumulating with the list of all time slots available.
Inside this reduce through the timeslots accumulator into an empty list.
For each slot check if the start and end time of the current day booking slot overlaps into the slot. If it does just return the inner accumulator as is. If it doesn't, add the slot into this accumulator.
In the end of the day_bookings
reduction, if you have an empty list it means no slot remains available for the day. So you add it to the outer accumulator, this will be the list of fully booked days.
In the end you reduce again the results so to invert them and in the process set each element to be the Date, instead of the list of bookings for the day.
Assuming you have a typo in the second booking and it does not start almost a week after it's own end, the solution might be much simpler than careful reducing.
The slots are filled when the booking starts and ends exactly at:
{start, end} =
time_slots
|> Enum.flat_map(&Tuple.to_list/1)
|> Enum.min_max()
#⇒ {~T[09:00:00], ~T[21:00:00]}
Which make the check almost trivial:
Enum.filter(bookings, fn booking ->
{s, e} = {Enum.map(booking, &elem(&1, 0)), Enum.map(booking, &elem(&1, 1))}
with {[s], [e]} <- {s -- e, e -- s} do
same_date =
[s, e]
|> Enum.map(&NaiveDateTime.to_date/1)
|> Enum.reduce(&==/2)
full = Enum.map([s, e], &NaiveDateTime.to_time/1)
same_date and full == [start, end]
end
end)
Kernel.SpecialForms.with/1
guarantees that whatever not expected will be filtered out.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.