Advent of Code 2019 - Day 4

It’s already great you made it through if you’re new to Elixir! :clap:
And it’s a also great and fun way to learn :smiley:

defmodule Day04 do
  import Enum, only: [filter: 2, chunk_by: 2, any?: 2, sort: 1]
  def run(cnd), do: 124_075..580_769 |> filter(&valid?(Integer.digits(&1), cnd)) |> length()
  defp valid?(ds, cnd), do: chunk_by(ds, & &1) |> any?(&cnd.(length(&1))) and sort(ds) == ds
end

IO.inspect(Day04.run(&(&1 >= 2)), label: "part 1")
IO.inspect(Day04.run(&(&1 == 2)), label: "part 2")
10 Likes

My take: https://github.com/egze/aoc/blob/master/lib/aoc/y2019/d4.ex

Used some Guards and Flow. Compared to Enum, Flow was 2.5 times faster on my 2,6 GHz Quad-Core Intel Core i7.

1 Like

That is really impressive :icon_surprised:

1 Like

My solution: https://github.com/mkasztelnik/elixir-adventofcode-2019/blob/master/lib/advent_of_code/day_04.ex

My solution:

defmodule Day4 do
  def valid?(num) when is_number(num) do
    num
    |> Integer.digits()
    |> valid?()
  end

  def valid?([a,b,c,d,e,f]) do
    a <= b && b <= c && c <= d && d <= e && e <= f &&
     ((a == b && b != c) ||
      (a != b && b == c && c != d) ||
      (b != c && c == d && d != e) ||
      (c != d && d == e && d != f) ||
      (d != e && e == f))
  end
end

Since the input range was so small I just used a range and Stream.filter to check if the input was valid or not then counted up the results. Link to the repo.

1 Like

A video where I misread the specs (again!) and lose at least 15 minutes of your time developing crap we won’t need. Also I have some Common Test stuff in there as a short demo, and thought of optimizations after the fact: https://gist.github.com/ferd/5d8c2997bb2b834af302e4856681d91f

It is very unelegant and I am not a happy camper! At least it handles arbitrary password lengths!

3 Likes

I thought this was MY solution when I clicked that link, we had almost the same naming also :sweat_smile:

I finally found some time to sit down and do todays task:

I do not quite like it, but I rarely really enjoy those “password validation” tasks that happen every year again.

2 Likes

Today much more simple, here’s my solution.

@bossek: really liked your super compact and clean solution! :clap:

@ferd: thanks for making these great videos! :heart:

2 Likes

I am really humbled by some of your cool uses of Enum.chunk_every

I did really like using with instead of a bunch of &&… it may be slower, (I don’t know!) but it still short-circuits.

defmodule Advent2019.Day4 do
  @moduledoc "https://adventofcode.com/2019/day/4"
  @behaviour Advent

  def setup do
    387638..919123
  end

  @doc """
      iex> has_double([1, 1, 1, 1, 1, 1])
      true

      iex> has_double([1, 2, 3, 4, 5, 6])
      false

      iex> has_double([1, 2, 3, 3, 5, 6])
      true
  """
  def has_double([a, b, c, d, e, f]) do
    a == b || b == c || c == d || d == e || e == f
  end

  @doc """
      iex> no_triplets([1, 1, 1, 1, 1, 1])
      false

      iex> no_triplets([1, 2, 3, 4, 5, 6])
      false

      iex> no_triplets([1, 2, 3, 3, 5, 6])
      true
  """
  def no_triplets([a, b, c, d, e, f]) do
    a == b && !Enum.any?([c, d, e, f], & &1 == a) ||
    b == c && !Enum.any?([a, d, e, f], & &1 == b) ||
    c == d && !Enum.any?([a, b, e, f], & &1 == c) ||
    d == e && !Enum.any?([a, b, c, f], & &1 == d) ||
    e == f && !Enum.any?([a, b, c, d], & &1 == e)
  end

  @doc """
  - It is a six-digit number.
  - The value is within the range given in your puzzle input.
  - Two adjacent digits are the same (like 22 in 122345).
  - Going from left to right, the digits never decrease; they only ever
    increase or stay the same (like 111123 or 135679).

      iex> valid_password?(111111, &has_double/1)
      true

      iex> valid_password?(223450, &has_double/1)
      false

      iex> valid_password?(123789, &has_double/1)
      false
  """
  def valid_password?(number, func) do
    [a, b, c, d, e, f] = list_of_integers =
      for div <- [1, 10, 100, 1_000, 10_000, 100_000] do
        number
        |> Integer.floor_div(div)
        |> Integer.mod(10)
      end
      |> Enum.reverse

    with true <- a <= b,
         true <- b <= c,
         true <- c <= d,
         true <- d <= e,
         true <- e <= f,
         true <- func.(list_of_integers)
    do
      true
    else
      false -> false
    end
  end

  @doc """
  How many different passwords within the range given in your puzzle input meet
  these criteria?
  """
  def p1(range) do
    range
    |> Stream.filter(fn(num) -> valid_password?(num, &has_double/1) end)
    |> Enum.count
  end

  def p2(range) do
    range
    |> Stream.filter(fn(num) -> valid_password?(num, &no_triplets/1) end)
    |> Enum.count
  end
end
2 Likes

Nice solutions everyone! Mine is kind of straight-forward but still way faster than my Ruby solution. :slight_smile:

defmodule Day4 do
  def part1, do: run(fn repeated_count -> Enum.any?(repeated_count, &(&1 >  1)) end)
  def part2, do: run(fn repeated_count -> Enum.any?(repeated_count, &(&1 == 2)) end)

  def run(filter) do
    372_037..905_157
    |> Stream.filter(&is_not_decreasing/1)
    |> Stream.filter(&(count_repeating_digits(&1) |> Map.values() |> filter.()))
    |> Enum.count()
  end

  defp is_not_decreasing(n),                     do: ind(-1, Integer.digits(n))
  defp ind(last, [head|tail]) when last <= head, do: ind(head, tail)
  defp ind(_last, []),                           do: true
  defp ind(_last, _list),                        do: false

  def count_repeating_digits(n),                     do: crd(%{}, -1, Integer.digits(n))
  def crd(map, last, [head|tail]) when last == head, do: crd(Map.update(map, head, 2, &(&1 + 1)), head, tail)
  def crd(map, _last, [head|tail]),                  do: crd(map, head, tail)
  def crd(map, _last, []),                           do: map
end

Day4.part1() |> IO.puts()
Day4.part2() |> IO.puts()
1 Like

A friend and I came up with this solution that only uses regexes in awk to work:

seq $START $STOP | awk '/([^0]|^)00([^0]+|$)|([^1]|^)11([^1]+|$)|([^2]|^)22([^2]+|$)|([^3]|^)33([^3]+|$)|([^4]|^)44([^4]+|$)|([^5]|^)55([^5]+|$)|([^6]|^)66([^6]+|$)|([^7]|^)77([^7]+|$)|([^8]|^)88([^8]+|$)|([^9]|^)99([^9]+|$)/ && !/[1-9]0|[2-9][01]|[3-9][0-2]|[4-9][0-3]|[5-9][0-4]|[6-9][0-5]|[7-9][0-6]|[89][0-7]|9[0-8]/ {print $0}' | wc -l

It’s about 3x slower than an actual code solution, but it’s surprisingly fast, and I believe it works on any input string 4 digits or longer.

4 Likes

My Day 4 solution

1 Like

I decided to :golf: my solution and do unholy things with for comprehensions

defmodule Day4 do
  def part_1(range) do
    for n <- range,
        [a, b, c, d, e, f] = digits = Integer.digits(n),
        # has a pair
        digits |> Enum.chunk_every(2, 1) |> Enum.any?(&match?([x, x], &1)),
        # never decrease
        a <= b && b <= c && c <= d && d <= e && e <= f,
        reduce: 0,
        do: (a -> a + 1)
  end

  def part_2(range) do
    for n <- range,
        [a, b, c, d, e, f] = digits = Integer.digits(n),
        # never decrease
        a <= b && b <= c && c <= d && d <= e && e <= f,
        # at least an exact pair
        digits |> Enum.chunk_by(& &1) |> Enum.any?(&match?([x, x], &1)),
        reduce: 0,
        do: (a -> a + 1)
  end
end
3 Likes

Upvote for the match?/2. I didn’t know that.

This has been my favorite problem so far. I solved it with a regex.

My solution is at https://github.com/simon-wolf/advent-of-code-2019/tree/master/day_04

I really enjoyed this one and I went for pattern matching as the central logic but I also took the opportunity to allow my validation functions to be daisy-chained in a pipeline.

My solution for day 4 in Gleam is here

The most common mistake I make when writing Gleam is typing true or false instead of True or False when pattern matching :man_facepalming: . This compiles just fine but silently fails because e.g. when false is first, it always matches and binds the value to first

edit: this is not a big problem because Erlang warns about unused variables, but in my case these were buried between other Erlang warnings :man_facepalming:

edit2: Gleam may eventually have exhaustiveness checks, it’s being researched: https://github.com/gleam-lang/gleam/issues/34

I went for a quick and dirty approach, using just a for comprehension and some Enum functions:

defmodule PasswordDigger do
  @moduledoc """
  Documentation for PasswordDigger.
  A function to calc all possible combinations
  given a set of rules:
  - each digit is equal or higher than previous
  - six digits
  - at least one pair (two same digits)
  - range: 109165-576723
  """

  def part1 do
    items =
      for a <- 1..5,
          b <- a..9,
          c <- b..9,
          d <- c..9,
          e <- d..9,
          f <- e..9,
          Integer.undigits([a, b, c, d, e, f]) < 576_723,
          Enum.uniq([a, b, c, d, e, f]) != [a, b, c, d, e, f],
          do: Integer.undigits([a, b, c, d, e, f])

    IO.puts("Number of possible passwords is: #{Enum.count(items)}")
  end

  def part2 do
    large_group_filter = fn password ->
      password
      |> Enum.group_by(& &1)
      |> Enum.any?(fn {_k, vs} -> Enum.count(vs) == 2 end)
    end

    items =
      for a <- 1..5,
          b <- a..9,
          c <- b..9,
          d <- c..9,
          e <- d..9,
          f <- e..9,
          Integer.undigits([a, b, c, d, e, f]) < 576_723,
          Enum.uniq([a, b, c, d, e, f]) != [a, b, c, d, e, f],
          large_group_filter.([a, b, c, d, e, f]),
          do: Integer.undigits([a, b, c, d, e, f])

    IO.puts("Number of possible passwords is: #{Enum.count(items)}")
  end
end
2 Likes