比较两个列表以查找Elixir中的日期和时间重叠

时间:2019-06-12 19:03:07

标签: datetime functional-programming elixir list-comprehension elixir-framework

作为运行预订系统的某些代码的一部分,我们有一个time_slots列表,它们是包含{start_time, end_time}的元组。这些是可以预订的可用时隙:

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]}
]

然后,我们还有一个预订列表,其中包含每个{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]}
  ]
]

在这种情况下,我们希望结果为两个time_slots都已填写的预订:

  • 2019-06-13
  • 2019-06-20

因为所有时隙都已填满,然后以Date的形式返回这些结果。


要提供更多信息:

  • 要填补一个时间段,它需要预订的开始或结束时间在其中重叠(无论重叠量有多小):
    • 例如预订0900–1000将填补0900–13000900–17000900–2100的时间段
  • 一个时隙可以填写多个预订:
    • 例如我们可以预订0900–10001000–1200,这两个预订都可以在0900–1300时段内使用。
  • 如果预订超出最大时间段,则视为已满:
    • 例如0800—2200的预订将填补0900–2100的时间段(以及所有其他时间段)

3 个答案:

答案 0 :(得分:2)

所以我对这个问题的理解是:对于预订列表,所有时隙是否至少与一个预订冲突?

可以通过检查以下两项来回答有冲突的预订:

  1. 如果在时间段开始之前开始预订,则如果在时间段开始之后完成预订则发生冲突。

  2. 如果在时间段开始或之后开始预订,则如果在时间段结束之前开始预订,则发生冲突。

因此,工作代码如下:

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:请注意,您不应将时间/日期/日期时间与><和朋友进行比较。始终使用相关的比较功能。

答案 1 :(得分:1)

尽管这可能无法涵盖所有​​情况,但鉴于您提供的示例数据可以正常工作

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]]

这个想法是,通过将bookings列表减少为一个空列表,每个枚举将成为当天已占用广告位的列表。

减少此列表,累积所有可用时隙的列表。

在此过程中,通过时隙累加器将其减少到一个空列表中。

对于每个时段,请检查当天预订时段的开始时间和结束时间是否与该时段重叠。如果确实存在,则仅按原样返回内部累加器。如果没有,请将插槽添加到此累加器中。

day_bookings减少的最后,如果您有一个空白列表,则表示当天没有空位。因此,将其添加到外部累加器中,这将是已满的天数的列表。

最后,您再次减少结果,以便将其取反,然后在此过程中将每个元素设置为Date,而不是当天的预订列表。

答案 2 :(得分:1)

假设您在第二次预订中遇到错字,并且在自己结束后不到 个星期后才开始输入错,那么解决方案可能比认真减少方案简单得多。

在预定开始和结束时的确切位置上的空位已满:

{start, end} =
  time_slots
  |> Enum.flat_map(&Tuple.to_list/1)
  |> Enum.min_max()          
#⇒ {~T[09:00:00], ~T[21:00:00]}

这使支票几乎微不足道:

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保证将过滤掉所有不期望的内容。