Aetherus

Aetherus

Advent of Code 2020 - Day 2

This topic is about Day 2 of the Advent of Code 2020.

First 10 of 33 Posts! Switch mode

Aetherus

Aetherus

My solution:

#!/usr/bin/env elixir

xor = &((&1 and not(&2)) or (not(&1) and &2))

ok_for_part1? = fn{lo, hi, char, password}->
  password
  |> String.graphemes()
  |> Enum.count(& &1 == char)
  |> Kernel.in(lo..hi)
end

ok_for_part2? = fn{i, j, char, password}->
  xor.(
    String.at(password, i - 1) == char,
    String.at(password, j - 1) == char
  )
end

"./day2.txt"
|> File.stream!([], :line)
|> Stream.map(&String.trim/1)
|> Stream.map(&String.split(&1, ~r/[-: ]/, trim: true))
|> Stream.map(fn[lo, hi, char, password]-> {String.to_integer(lo), String.to_integer(hi), char, password} end)
|> Enum.count(ok_for_part1?)
|> IO.puts()

For part 2, just modify this line: |> Enum.count(ok_for_part1?)

adamu

adamu

Bit cheeky to mark your own post as the solution :stuck_out_tongue:

My answer was pretty similar, but I used a Regex with captures to parse the input - I prefer your use of String.split though. I was wondering if it was worth importing Bitwise, but like you I just wrote it manually.

Aetherus

Aetherus

I’d like to mark everyone’s working code as solution, mine included :grin:

The Bitwise.^^^/2 only works on integers, so we can’t use it.

bossek

bossek

Following should work:

(String.at(password, i - 1) == char) != (String.at(password, j - 1) == char)
Aetherus

Aetherus

Since the input is all ANSI characters, it could be faster just handling bytes.

String.at(string, n) is O(n) if string contains only ANSI characters, but :binary.at(binary, n) is O(1). (Confirmed using Benchee)

#!/usr/bin/env elixir

xor = &((&1 and not(&2)) or (not(&1) and &2))

ok_for_part1? = fn{lo, hi, char, password}->
  password
  |> :binary.bin_to_list()
  |> Enum.count(& &1 == char)
  |> Kernel.in(lo..hi)
end

ok_for_part2? = fn{i, j, char, password}->
  xor.(
    :binary.at(password, i - 1) == char,
    :binary.at(password, j - 1) == char
  )
end

"./day2.txt"
|> File.stream!([], :line)
|> Stream.map(&String.trim/1)
|> Stream.map(&String.split(&1, ~r/[-: ]/, trim: true))
|> Stream.map(fn[lo, hi, char, password]-> {String.to_integer(lo), String.to_integer(hi), :binary.first(char), password} end)
|> Enum.count(ok_for_part1?)
|> IO.inspect()
everte

everte

I’m the only one who didn’t think about using regex it seems, although I think I like the String.split method used by Aetherus.

Thanks for sharing the solutions, it definitely gives me some inspiration on how to improve mine:
https://git.sr.ht/~servert/aoc2020/tree/master/day2/lib/day2.ex (not sure how to link to the version in this specific commit on sourcehut so it’s immutable).

Aetherus

Aetherus

Thank you for sharing your solution. I like the pins :+1:

faried

faried

Enum.count! That could’ve helped me. Oh, well.

defmodule Day02 do
  def readinput() do
    File.stream!("2.input.txt")
    |> Enum.map(&split/1)
  end

  def part1(input \\ readinput()) do
    Enum.reduce(input, 0, fn row, acc -> if valid1(row), do: acc + 1, else: acc end)
  end

  def part2(input \\ readinput()) do
    Enum.reduce(input, 0, fn row, acc -> if valid2(row), do: acc + 1, else: acc end)
  end

  def split(line) do
    [[_, left, right, char, password]] = Regex.scan(~r/(\d+)-(\d+) (\w): (\w+)/, line)

    [
      [String.to_integer(left), String.to_integer(right)],
      char,
      password
    ]
  end

  def valid1([[left, right], char, password]) do
    pchar = String.to_charlist(char)
    pwdlist = String.to_charlist(password)
    range = Range.new(left, right)

    Enum.reduce(pwdlist, 0, fn char, acc -> if [char] == pchar, do: acc + 1, else: acc end) in range
  end

  def valid2([[index1, index2], char, password]) do
    at1 = String.at(password, index1 - 1) == char
    at2 = String.at(password, index2 - 1) == char

    case [at1, at2] do
      [true, false] -> true
      [false, true] -> true
      _ -> false
    end
  end
end

mexicat

mexicat

My solution.

defmodule AdventOfCode.Day02 do
  def part1(input) do
    input
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(fn line ->
      [min, max, letter, password] = get_line_data(line)
      check_password_1(String.to_integer(min), String.to_integer(max), letter, password)
    end)
    |> Enum.count(& &1)
  end

  def part2(input) do
    input
    |> String.trim()
    |> String.split("\n")
    |> Enum.map(fn line ->
      [pos_1, pos_2, letter, password] = get_line_data(line)
      check_password_2(String.to_integer(pos_1), String.to_integer(pos_2), letter, password)
    end)
    |> Enum.count(& &1)
  end

  def get_line_data(line) do
    Regex.run(~r/^(\d+)-(\d+) ([a-z]): ([a-z]+)$/, line, capture: :all_but_first)
  end

  def check_password_1(min, max, letter, password) do
    letter_count =
      password
      |> String.codepoints()
      |> Enum.count(&(&1 == letter))

    letter_count >= min && letter_count <= max
  end

  def check_password_2(pos_1, pos_2, letter, password) do
    letters = String.codepoints(password)
    # positions are 1-indexed
    pos_1_valid = Enum.at(letters, pos_1 - 1) == letter
    pos_2_valid = Enum.at(letters, pos_2 - 1) == letter

    pos_1_valid != pos_2_valid
  end
end

Edit: I forgot about String.at, that’s probably a minor refactor that could be done.

hauleth

hauleth

My approach to make it not only fast, but also clean and readable:

defmodule Solution do
  def read(path) do
    path
    |> File.stream!()
    |> Enum.map(&String.trim/1)
    |> Enum.map(&parse/1)
  end

  defp parse(input) do
    [spec, pass] = String.split(input, ": ", parts: 2)
    [range, <<char>>] = String.split(spec, " ", parts: 2)
    [min, max] =
      range
      |> String.split("-", parts: 2)
      |> Enum.map(&String.to_integer/1)

    {min..max, char, pass}
  end

  def validate_1({range, char, pass}) do
    count = for <<^char <- pass>>, reduce: 0, do: (n -> n + 1)

    count in range
  end

  def validate_2({a..b, char, pass}) do
    <<char_1>> = binary_part(pass, a - 1, 1)
    <<char_2>> = binary_part(pass, b - 1, 1)

    char_1 != char_2 and char in [char_1, char_2]
  end
end

data = Solution.read("2/input.txt")

IO.inspect(Enum.count(data, &Solution.validate_1/1), label: "task 1")
IO.inspect(Enum.count(data, &Solution.validate_2/1), label: "task 2")

Last Post!

APB9785

APB9785

Creator of ECSx

This seemed to be all about parsing the input properly. I chose to use a list of 4-tuples, and from there, both parts were trivial to solve.

defp parse_line(line) do
  [reqs, pass] = String.split(line, ": ")
  [qty_range, letter] = String.split(reqs, " ")
  [a, b] = String.split(qty_range, "-") |> Enum.map(&String.to_integer/1)

  {pass, a, b, letter}
end

Full solution @ Github

Where Next?

Trending in Challenges Top

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
type1fool
I just stumbled on a newly redesigned elixir-lang.org. :tada: It looks like @Software_Mansion did the work, and I think it is generally a...
New
akoutmos
@hugobarauna and I (Alex Koutmos) have been hard at work on writing a book on Nerves that takes you from simply blinking LEDs to building...
New

We're in Beta

About us Mission Statement