在列表中查找桥接天数

时间:2017-10-28 17:44:06

标签: elixir phoenix-framework

我想从一系列日子中找到“桥梁日”。清单:

days = [
 %{value: ~D[2017-04-01], categories: ["weekend"]},
 %{value: ~D[2017-04-02], categories: ["weekend"]},
 %{value: ~D[2017-04-03], categories: []},
 %{value: ~D[2017-04-04], categories: []},
 ...
 %{value: ~D[2017-04-13], categories: ["bank holiday"]},
 %{value: ~D[2017-04-14], categories: ["bank holiday"]},
 %{value: ~D[2017-04-15], categories: ["weekend"]},
 %{value: ~D[2017-04-16], categories: ["weekend", "bank holiday"]},
 ... ]

在网页上呈现的月份:

Screenshot of April in the calendar

有人想要最大化他/她的休假日,将在10日,11日和12日休假,因为这将导致10天假期(8日至17日),投资只有3个休假日。

我想编写一个函数bridge_days(days, number_of_invested_vacation_days),当使用[~D[2017-04-10], ~D[2017-04-11], ~D[2017-04-12]]调用时,会生成这三天bridge_days(days, 3)的列表。 3是投资假期的数量。

另一个月的例子:

Screenshot of Mai in the calendar

bridge_days(days, 1)会产生[~D[2017-05-26]],因为1个休假日的投资会带来4天的休假。

实际上bridge_days/2通常会产生一个列表列表,因为很多时候会有多个选项。

我的方法是遍历列表,比较每天的+1和-1。问题在于它需要永远这样做。

有没有比使用这种暴力解决这个问题更聪明的方法?

2 个答案:

答案 0 :(得分:1)

您已请求了算法。我认为关键是要把你的日子折成一份“值班/休假”的清单。天。对于四月,这将对应于:

collapsed_days = [
  {:off, 2}, 
  {:on, 5}, {:off, 2}, 
  {:on, 3}, {:off, 5}, 
  {:on, 4}, {:off, 2}, 
  {:on, 5}, {:off, 2}
]

现在您的function桥天数如下: bridge_days(collapsed_days, 3)

在此function中,您可以执行以下操作:

  • 搜索最少量的值班日,在本例中为{:on, 3}
  • 相应地应用尽可能多的休假日,因此它变为:{:off, 3}
  • 折叠生成的连续{:off, _}天,列表变为:

    [   {:off,2},   {:on,5},{:off,10},   {:on,4},{:off,2},   {:on,5},{:off,2} ]

  • 如果您有更多的休假日申请,请相应地标记它们。例如bridge_days(collapsed_days, 5),除了{:off, 10}之外,您还需要在{:off, 2}之前或之后立即创建{:off, 10}

  • 如果您的休假天数少于最小{:on, _}金额,则尽可能多地申请。

我相信这应该总能带来最佳的解决方案,但是我可能错了,因为我没有在数学上证明这是事实。如果你不能看到如何在数学上证明这一点,那么试着通过测试所有边缘情况来证明它已经耗尽。

由于您需要返回完全dates而不是简单的数字,因此最好使用三元组:{:off, 2, [~D[2017-04-01], ~D[2017-04-02]]},并相应地合并您的列表。

如果您发现会破坏此方法的边缘案例,请在评论中提供反馈。

答案 1 :(得分:1)

这是一个强力O(n^2)算法的想法,对于短列表应该相当快。我将使用日期的简化表示:布尔列表。 true表示这是假期,false表示不是假期。以下是算法的工作原理:

我们将列表的每个索引视为可能的开始日。

对于每个指数,我们都会找到在给定假期数下我们可以拥有的天数。为此,我们将以前的日期分开,然后使用reduce_while,继续花几天时间,直到我们使用了所有假期(或列表结束)。我们还跟踪遇到的假期数量。

我们使用Enum.max_by每天应用上述步骤,并回到开始假期的最佳日子。你可以通过迭代这个索引来获得实际的休假日。

defmodule A do
  def go(days, vacation) do
    0..(length(days) - 1)
    |> Enum.max_by(fn from ->
      days |> Enum.drop(from) |> Enum.reduce_while({0, 0}, fn holiday?, {collected, taken} ->
        cond do
          # If it's a holiday, we've collected one day without consuming more vacations.
          holiday? -> {:cont, {collected + 1, taken}}
          # If we've taken all vacations possible, we halt.
          vacation == taken -> {:halt, {collected, taken}}
          # Otherwise, we collect another day and also consume one vacation day.
          true -> {:cont, {collected + 1, taken + 1}}
        end
      end)
      |> elem(0)
    end)
  end
end

t = true
f = false
# April from your screenshot.
days = [t, t, f, f, f, f, f, t, t, f, f, f, t, t, t, t, t, f, f, f, f, t, t, f, f, f, f, f, t, t]
IO.inspect A.go(days, 3)

输出:

7

因此,本月的第8天是开始度假的最佳日子,与您的描述相符。