问题
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:
- For a time slot to be filled up it would require either a booking’s start or finish to overlap inside of it (regardless of how small that overlap is):
- E.g. a booking of
0900–1000
would fill the0900–1300
,0900–1700
and0900–2100
time slots
- E.g. a booking of
- A time slot can be filled with more than one booking:
- E.g. we can have bookings of
0900–1000
and1000–1200
, which would both fit inside the0900–1300
time slot.
- E.g. we can have bookings of
- If there is a booking that extends beyond the largest time slot, it counts as being filled:
- E.g. a booking of
0800—2200
would fill the0900–2100
time slot (along with all the others)
- E.g. a booking of
回答1:
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.
回答2:
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.
回答3:
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.
来源:https://stackoverflow.com/questions/56568816/compare-two-lists-to-find-date-and-time-overlaps-in-elixir