euler_ex/problems/solutions/19/solution.livemd

1.4 KiB

Project Euler Problem 19

defmodule CountingSundays do
  defguard is_leap_year(y) when rem(y, 4) == 0 and (rem(y, 100) != 0 or rem(y, 400) == 0)
  
  @days %{
    0 => :sun,
    1 => :mon,
    2 => :tue,
    3 => :wed,
    4 => :thur,
    5 => :fri,
    6 => :sat,
  }
  
  @months %{
    0 => :jan,
    1 => :feb,
    2 => :mar,
    3 => :apr,
    4 => :may,
    5 => :jun,
    6 => :jul,
    7 => :aug,
    8 => :sep,
    9 => :oct,
    10 => :nov,
    11 => :dec
  }

  def days_in_month(year, month) do
    case month do
      m when m in [:apr, :jun, :nov, :sep] -> 30
      :feb when is_leap_year(year) -> 29
      :feb -> 28
      _ -> 31
    end
  end
  
  def add_days(d, days), do: rem(d + days, 7)

  defp count_sundays_between(year, month, fin_year, fin_month, _, acc) 
    when fin_month < month and fin_year == year or year > fin_year do
    acc
  end
  
  defp count_sundays_between(year, month, fin_year, fin_month, d, acc) do
    next = add_days(d, days_in_month(year, @months[month]))

    acc = if @days[next] == :sun, do: acc + 1, else: acc
    {year, month} = case month do
      11 -> { year + 1, 0}
      n -> { year, n + 1}
    end
    
    count_sundays_between(year, month, fin_year, fin_month, next, acc)
  end

  def count_sundays() do
    count_sundays_between(1900, 0, 2000, 11, 1, 0) - count_sundays_between(1900, 0, 1900, 11, 1, 0)
  end
end
CountingSundays.count_sundays()