Advent of Code 2021 - Day 1

Hmm, my non-optimal solution. First one here to use Enum.with_index :innocent:. Probably not a good sign!

 def parse_input(input) do
    input
    |> Enum.map(fn x -> String.to_integer(x) end)
    |> IO.inspect
  end

  def find_increased_number_count(numbers) do
      Enum.reduce(numbers, {:infinity, 0}, fn x, {prev, count} ->
        case x > prev do
          true ->  {x, count + 1}
          false -> {x, count}
        end
      end)
    end
  
  def creaete_sliding_windows(numbers) do
    Enum.with_index(numbers)
    |> Enum.map(fn {number, index} -> number + Enum.at(numbers, index + 1, 0) + Enum.at(numbers, index + 2, 0) end)
    |> IO.inspect
  end
1 Like

Hereā€™s my take:

defmodule Sol do
  @file_path Path.join(~w(priv input.txt))

  def run_1(file \\ @file_path) do
    file
    |> prepare_stream()
    |> count(&increase?/2)
  end

  def run_2(file \\ @file_path) do
    file
    |> prepare_stream()
    |> to_sliding_window()
    |> Stream.map(&sum_window/1)
    |> count(&increase?/2)
  end

  defp prepare_stream(file) do
    file
    |> File.stream!()
    |> Stream.map(&String.trim/1)
    |> Stream.map(&String.to_integer/1)
  end

  defp to_sliding_window(stream, size \\ 3) do
    Stream.chunk_every(stream, size, 1, :discard)
  end

  defp sum_window(numbers) when length(numbers) == 3, do: Enum.sum(numbers)

  defp count(stream, condition) do
    {_, count} =
      Enum.reduce(stream, {nil, 0}, fn val, {prev, count} ->
        count =
          case condition.(prev, val) do
            true -> count + 1
            false -> count
          end

        {val, count}
      end)

    count
  end

  defp increase?(prev, curr)
       when is_integer(prev) and
              is_integer(curr) and
              prev < curr do
    true
  end

  defp increase?(_, _), do: false
end
2 Likes

A no-brainer optimization is to convert a [{elem, index}] list into a map %{index => elem}.

list
|> Enum.with_index()
|> Map.new(fn {elem, index} -> {index, elem} end)

Accessing a list element by index is O(index), while accessing a value by key in a map is O(log n) I guess.

2 Likes

My solution is using pattern matching and recursion. I could also reuse part1 for part2 by preprocessing the input.

The input is small enough so I didnā€™t bother with streaming. :slight_smile:

defmodule Day1 do
  def part1([x | [y | _] = rest]) when y > x, do: 1 + part1(rest)
  def part1([_ | rest]), do: part1(rest)
  def part1([]), do: 0

  def part2(input) do
    input
    |> avg3()
    |> part1()
  end

  defp avg3([a | [b | [c | _]] = rest]),
    do: [a + b + c | acc_avg(rest)]

  defp avg3(_), do: []

  def parse_file(path) do
    path
    |> Path.expand(__DIR__)
    |> File.read!()
    |> parse_input()
  end

  def parse_input(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&String.to_integer/1)
  end
end

"example.txt"
|> Day1.parse_file()
|> Day1.part1()
2 Likes

Very interesting solution. Iā€™d love to see a tail-recursive version :wink:

By the way, [a | [b | [c | _]]] can be written as [a, b, c | _].

1 Like

It is LiveBook, so I paste it as a whole:


Day 1

Load input

stream =
  File.stream!("day1.txt")
  |> Stream.map(&String.to_integer(String.trim(&1)))

Task 1

Compute count of consecutive increases

stream
|> Stream.chunk_every(2, 1, :discard)
|> Enum.count(fn [a, b] -> a < b end)

Task 2

Compute count of consecutive increases of sums of trigrams

stream
|> Stream.chunk_every(3, 1)
|> Stream.map(&Enum.sum/1)
|> Stream.chunk_every(2, 1, :discard)
|> Enum.count(fn [a, b] -> a < b end)
4 Likes

First time poster, first time playing with Livebook. Itā€™s actually pretty great for this!

posting the export, so you can re-import


# Day 1

## Input

<!-- livebook:{"livebook_object":"cell_input","name":"input","type":"text","value":"199 200 208 210 200 207 240 269 260 263"} -->

## Part 1

```elixir
processed = IO.gets(:input) |> String.split(~r{\s}, trim: true) |> Enum.map(&String.to_integer/1)
```

```elixir
processed
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [x, y] -> y > x end)
|> Enum.count(& &1)
```

## Part 2

```elixir
processed = IO.gets(:input) |> String.split(~r{\s}, trim: true) |> Enum.map(&String.to_integer/1)
```

```elixir
processed
|> Enum.chunk_every(3, 1, :discard)
|> Enum.map(&Enum.sum/1)
|> Enum.chunk_every(2, 1, :discard)
|> Enum.map(fn [x, y] -> y > x end)
|> Enum.count(& &1)
```

EDIT: Why did I brainfart and didnā€™t put fun directly into count :man_facepalming:

1 Like

Iā€™m aware, but I wanted to match the rest part so I can continue recursing (could have done that like part([b, c | rest]) though. :slight_smile:

I usually lean on this statement regarding body vs tail recursion.
https://www.erlang.org/doc/efficiency_guide/myths.html#myth--tail-recursive-functions-are-much-faster-----than-recursive-functions

1 Like

My take on it: y2021/d1.ex

This is such a simple problem yet I learned quite a bit looking at everyoneā€™s solutions here. Thank you for sharing everyone.

I hope to see more like these in the next one!

5 Likes

That is an awesome observation!

3 Likes

Me too.

By the way, hereā€™s my AoC-2017 Day 3 Part 2 solution, though I worked out today, and far from optimal :sweat_smile:

#!/usr/bin/env elixir

input = 265149  # my input number

step = fn
  {n, {n, y}} when y == -n -> {n + 1, {n + 1, y}}  # bottom-right
  {n, {x, y}} when y == -n -> {n, {x + 1, y}}  # towards bottom-right
  {n, {n, n}} -> {n, {n - 1, n}}  # top-right
  {n, {n, y}} -> {n, {n, y + 1}}  # towards top-right
  {n, {x, n}} when x == -n -> {n, {x, n - 1}}  # top-left
  {n, {x, n}} -> {n, {x - 1, n}}  # towards top-left
  {n, {x, y}} when x == -n and y == -n -> {n, {x + 1, y}}  # bottom-left
  {n, {x, y}} when x == -n -> {n, {x, y - 1}}  # towards bottom-left
end

{%{{0, 0} => 1}, {1, {1, 0}}}
|> Stream.unfold(fn {map, {_, {x, y}} = s} ->
  sum = for i <- -1..1, j <- -1..1, reduce: 0 do
    sum -> sum + Map.get(map, {x + i, y + j}, 0)
  end
  map = Map.put(map, {x, y}, sum)
  {map, {map, step.(s)}}
end)
|> Stream.map(&Enum.max(Map.values(&1)))
|> Stream.drop_while(& &1 <= input)
|> Enum.at(0)
|> IO.inspect()
1 Like

Nice! I played a golf match this morning, pinning my F# code versus my Elixir code. Looks like that will be this years AoC theme for me :smiley:

FYI: https://twitter.com/josevalim/status/1466048514786574339 :slight_smile: :eyes:

Enum.reduce for part 1, manual reduce for part 2. Did this in five minutes at 11pm, will probably optimize the solution more eventually.

My solution:

defmodule Day01 do
  @sample ~w/199
  200
  208
  210
  200
  207
  240
  269
  260
  263/
  |> Enum.map(&String.to_integer/1)

  @report_list "assets/report_measurement.txt"
      |> File.read!()
      |> String.split(~r/\n/, trim: true)
      |> Enum.map(&String.to_integer/1)

  def list do
    @report_list
  end

  defp larger_than_previous([]), do: []
  defp larger_than_previous([h1 | [h2 | _] = t]) do
    [h2 > h1 | larger_than_previous(t)]
  end
  defp larger_than_previous([_ | t]), do: larger_than_previous(t)

  def count do
    ltp_count(list())
  end

  def count_sample do
    ltp_count(@sample)
  end

  # Puzzle 2

  defp sum3([]), do: []
  defp sum3([h1 | [h2 | [h3 | _]] = t]) do
    [h1 + h2 + h3 | sum3(t)]
  end
  defp sum3([_ | t]), do: sum3(t)

  def count2_sample do
    sum3 = sum3(@sample)
    ltp_count(sum3)
  end

  def count2 do
    sum3 = sum3(@report_list)
    ltp_count(sum3)
  end

  defp ltp_count(list) do
    list_ltp = larger_than_previous(list)
    Enum.count(list_ltp , fn x -> x == true end)
  end
end

# Results of the Sample
IO.puts "The result of the 1st sample is: #{Day01.count_sample}"
IO.puts "The result of the 2nd sample is: #{Day01.count2_sample}"

# Results of the Puzzle
IO.puts "The result of the 1st puzzle is: #{Day01.count}"
IO.puts "The result of the 2nd puzzle is: #{Day01.count2}"

run iex -S mix:

iex(1)> r Day01
The result of the 1st sample is: 7
The result of the 2nd sample is: 5
The result of the 1st puzzle is: 1462
The result of the 2nd puzzle is: 1497
{:reloaded, Day01, [Day01]}
1 Like

Hereā€™s my humble - and not efficient - attempt:

defmodule Measurements do
  def increases(depths, window_size \\ 1) do
    depths
    |> Enum.chunk_every(window_size, 1, :discard)
    |> Enum.map(&Enum.sum/1)
    |> Enum.chunk_every(2, 1, :discard)
    |> Enum.count(fn [m1, m2] -> m2 > m1 end)
  end
end

(Assuming depths is a list of integers)

Itā€™s a general solution that works with both parts, you just have to change the window_size from 1 to 3.

5 Likes

I decided to avoid use Enum module just to play with the functions with different arity:

defmodule AofC2021.Day1 do
  @day_1_input "2021/day1.txt"

  def solve_first_part(input) do
    do_first_solution(input)
  end

  def solve_first_part do
    input = get_input()
    do_first_solution(input)
  end

  def do_first_solution(input) do
    input
    |> parse_to_integers()
    |> compare_with_previous_one()
    |> count_increases()
  end

  defp parse_to_integers(input_list) do
    Enum.map(input_list, &String.to_integer/1)
  end

  defp compare_with_previous_one(input_parsed) do
    [first | [second | tail]] = input_parsed
    get_comparisions(first, second, [second | tail], [])
  end

  defp get_comparisions(first, second, [_last], counters) when first > second do
    counters ++ [:decreased]
  end

  defp get_comparisions(first, second, [_last], counters) when first < second do
    counters ++ [:increased]
  end

  defp get_comparisions(first, second, tail, counters) when first == second do
    counters = counters ++ [:nochange]
    [first | [second | tail]] = tail
    get_comparisions(first, second, [second | tail], counters)
  end

  defp get_comparisions(first, second, tail, counters) when first > second do
    counters = counters ++ [:decreased]
    [first | [second | tail]] = tail
    get_comparisions(first, second, [second | tail], counters)
  end

  defp get_comparisions(first, second, tail, counters) when first < second do
    counters = counters ++ [:increased]
    [first | [second | tail]] = tail
    get_comparisions(first, second, [second | tail], counters)
  end

  defp count_increases(counters) do
    Enum.count(counters, fn i -> i == :increased end)
  end

  ############### Second Part

  def solve_second_part(input) do
    do_second_solution(input)
  end

  def solve_second_part() do
    input = get_input()
    do_second_solution(input)
  end

  def do_second_solution(input) do
    input
    |> parse_to_integers()
    |> get_sums_list()
    |> compare_with_previous_one()
    |> count_increases()
  end

  def get_sums_list(input) do
    build_sum_list(input, [])
  end

  def build_sum_list([_first, _second], sums_list) do
    sums_list
  end

  def build_sum_list([first|[second|[third|tail]]], sums_list) do
    sum = first + second + third
    sums_list = sums_list ++ [sum]
    build_sum_list([second|[third|tail]], sums_list)
  end

  def get_input() do
    InputReader.get_input(@day_1_input)
  end
end

1 Like

My attempt as an Elixir beginner:

Part 1:

defmodule Submarine do
  def depth_count([head | tail]) do
    depth_count(tail, head, 0)
  end
  
  def depth_count([head | tail], previous, total) do
    case head > previous do
      :true -> depth_count(tail, head, total + 1)
      :false -> depth_count(tail, head, total)
    end
  end

  def depth_count([], _, total), do: total
end

Part 2:

defmodule Submarine2 do
  def depth_count(list) do
    depth_count(list, Enum.take(list, 3), 0)
  end

  def depth_count([_head | tail], previous_window, total) do
    sliding_window = Enum.take(tail, 3)    
    case Enum.count(sliding_window) == 3 do
      :true ->
        sliding_window_sum = Enum.reduce(sliding_window, fn x, acc -> x + acc end)
        previous_window_sum = Enum.reduce(previous_window, fn x, acc -> x + acc end)
        case sliding_window_sum > previous_window_sum do
          :true -> depth_count(tail, sliding_window, total + 1)
          :false -> depth_count(tail, sliding_window, total)
        end
      :false -> total
    end
  end
end

Here is my take using recursion:

defmodule AoC2021.Day1 do
  def simple_count_increases(_readings = [head | tail]) do
    do_count_increases(head, tail, 0)
  end

  defp do_count_increases(_last, [], acc), do: acc

  defp do_count_increases(last, [reading | rest], acc) do
    new_acc = if reading > last, do: acc + 1, else: acc
    do_count_increases(reading, rest, new_acc)
  end
  
  def sliding_count_increases(_readings = [a, b, c | rest]) do
    do_sliding_count_increases(a, b, c, rest, 0)
  end

  defp do_sliding_count_increases(_a, _b, _c, [], acc), do: acc

  defp do_sliding_count_increases(a, b, c, [d | rest], acc) do
    new_acc = if d > a, do: acc + 1, else: acc
    do_sliding_count_increases(b, c, d, rest, new_acc)
  end

  def get_readings do
    "fixtures/day1.txt"
    |> File.read!()
    |> String.split()
    |> Enum.map(&String.to_integer/1)
  end
end
1 Like