# Advent of Code 2019 - Day 4

It’s already great you made it through if you’re new to Elixir! And it’s a also great and fun way to learn ``````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

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 1 Like

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 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! @ferd: thanks for making these great videos! 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

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).

true

false

false
"""
[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. ``````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, []),                           do: true
defp ind(_last, _list),                        do: false

def count_repeating_digits(n),                     do: crd(%{}, -1, Integer.digits(n))
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]|[3-9][0-2]|[4-9][0-3]|[5-9][0-4]|[6-9][0-5]|[7-9][0-6]|[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
1 Like

I decided to 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.

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 . 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 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 """
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
|> 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