This topic is about Day 2 of the Advent of Code 2020.
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?)
Bit cheeky to mark your own post as the solution
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.
Iād like to mark everyoneās working code as solution, mine included
The Bitwise.^^^/2
only works on integers, so we canāt use it.
Following should work:
(String.at(password, i - 1) == char) != (String.at(password, j - 1) == char)
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()
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).
Thank you for sharing your solution. I like the pins
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
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.
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")
defmodule Aoc.Y2020.D2 do
use Aoc.Boilerplate,
transform: fn raw ->
raw
|> String.split("\n")
|> Enum.map(fn line ->
[rules, password] = line |> String.split(": ")
[occurance, letter] = rules |> String.split(" ")
[min, max] = occurance |> String.split("-")
{password, String.to_integer(min), String.to_integer(max), letter}
end)
end
def part1(input \\ processed()) do
input
|> Enum.reduce(0, fn {password, min, max, letter}, valid_passwords ->
if valid_password?(password, min, max, letter) do
valid_passwords + 1
else
valid_passwords
end
end)
end
def part2(input \\ processed()) do
input
|> Enum.reduce(0, fn {password, position1, position2, letter}, valid_passwords ->
if valid_toboggan_password?(password, position1, position2, letter) do
valid_passwords + 1
else
valid_passwords
end
end)
end
@doc """
Validates the password based on rules.
Uses a regex, for example `~r/^([^a]*a){1,3}[^a]*$/`
^ Start of string
( Start of group
[^a]* Any character except `a`, zero or more times
a our character `a`
){1,3} End and repeat the group 1 to 3 times
[^a]* Any character except `a`, zero or more times again
$ End of string
Examples:
iex> Aoc.Y2020.D2.valid_password?("aabbcc", 1, 2, "a")
true
iex> Aoc.Y2020.D2.valid_password?("aaabbcc", 1, 2, "a")
false
"""
def valid_password?(password, min, max, letter) do
Regex.match?(~r/^([^#{letter}]*#{letter}){#{min},#{max}}[^#{letter}]*$/, password)
end
@doc """
Validates the password based on Toboggan rule.
Examples:
iex> Aoc.Y2020.D2.valid_toboggan_password?("aabbcc", 1, 3, "a")
true
iex> Aoc.Y2020.D2.valid_toboggan_password?("aaabbcc", 1, 2, "a")
false
"""
def valid_toboggan_password?(password, position1, position2, letter) do
letter1 = String.at(password, position1 - 1)
letter2 = String.at(password, position2 - 1)
(letter1 == letter || letter2 == letter) && letter1 != letter2
end
end
Short solution part 1:
File.stream!("input")
|> Stream.filter(fn str ->
[_, min, max, char, pass] = Regex.run(~r/^(\d+)-(\d+) (.): (\S+)/, str)
count = String.graphemes(pass) |> Enum.frequencies() |> Map.get(char, 0)
count >= String.to_integer(min) && count <= String.to_integer(max)
end)
|> Enum.count()
|> IO.puts()
Short solution part 2:
true_one = &if String.at(&1, String.to_integer(&2) - 1) == &3, do: 1, else: 0
File.stream!("input")
|> Enum.count(fn str ->
[_, p1, p2, char, pass] = Regex.run(~r/^(\d+)-(\d+) (.): (\S+)/, str)
true_one.(pass, p1, char) + true_one.(pass, p2, char) == 1
end)
|> IO.puts()
My solution:
defmodule AdventOfCode.DayTwo do
def validate_1(path) do
path
|> read_file()
|> String.split("\r\n", trim: true)
|> Enum.filter(&process_input_1/1)
|> Enum.count()
|> IO.puts()
end
def validate_2(path) do
path
|> read_file()
|> String.split("\r\n", trim: true)
|> Enum.filter(&process_input_2/1)
|> Enum.count()
|> IO.puts()
end
def process_input_1(input) do
[spec, password] = String.split(input, ": ")
[range, key] = String.split(spec, " ")
[min, max] = String.split(range, "-")
count = password |> String.graphemes() |> Enum.frequencies() |> Map.get(key, 0)
count >= String.to_integer(min) && count <= String.to_integer(max)
end
def process_input_2(input) do
[spec, password] = String.split(input, ": ")
[range, key] = String.split(spec, " ")
[min, max] = String.split(range, "-")
min = String.to_integer(min)
max = String.to_integer(max)
letter1 = String.at(password, min - 1)
letter2 = String.at(password, max - 1)
(letter1 == key || letter2 == key) && letter1 != letter2
end
def read_file(path) do
case File.read(path) do
{:ok, body} ->
body
{:error, reason} ->
reason
end
end
end
AdventOfCode.DayTwo.validate_1("input.txt")
AdventOfCode.DayTwo.validate_2("input.txt")
Seems no one stumbled upon :erang.xor
defmodule Event2 do
def run do
IO.puts("Test part1: #{part1("input/event2/test.txt")}")
IO.puts("Puzzle part1: #{part1("input/event2/puzzle.txt")}")
IO.puts("Test part2: #{part2("input/event2/test.txt")}")
IO.puts("Puzzle part2: #{part2("input/event2/puzzle.txt")}")
end
def part1(path), do: input_stream(path) |> Stream.filter(&filter_fun/1) |> Enum.count()
def part2(path), do: input_stream(path) |> Stream.filter(&filter_fun2/1) |> Enum.count()
def input_stream(path), do: path |> File.stream!() |> Stream.map(&parse_input/1)
def parse_input(input) do
[low, high, letter, pass] = String.trim(input) |> String.split(~r/[-: ]/, trim: true)
{String.to_integer(low), String.to_integer(high), letter, pass}
end
def filter_fun({low, high, letter, pass}) do
pass_letter_count = pass |> String.graphemes() |> Enum.count(&(&1 == letter))
low <= pass_letter_count and pass_letter_count <= high
end
def filter_fun2({low, high, letter, pass}),
do: :erlang.xor(String.at(pass, low - 1) == letter, String.at(pass, high - 1) == letter)
end
Not sure how kosher it is to use an import but this seemed like a good exercise for using a parser library. I really like how easy NimbleParsec is to use.
defmodule Day2 do
import NimbleParsec
rule =
integer(min: 1, max: 3)
|> ignore(string("-"))
|> integer(min: 1, max: 3)
|> ignore(string(" "))
|> ascii_char([?a..?z])
|> ignore(string(": "))
defparsec(:pw_parse, rule)
def pw_check(file, fun) do
File.stream!(file)
|> Enum.map(&pw_parse(&1))
|> Enum.count(&fun.(&1))
end
end
valid? = fn parsed ->
case parsed do
{:ok, [a, b, c], pw, _, _, _} ->
a..b
|> Enum.member?(String.to_charlist(pw) |> Enum.count(fn x -> x == c end))
_ ->
false
end
end
valid2? = fn parsed ->
case parsed do
{:ok, [a, b, c], pw, _, _, _} ->
[a - 1, b - 1]
|> Enum.count(&(Enum.at(String.to_charlist(pw), &1) == c))
|> Kernel.==(1)
_ ->
false
end
end
IO.inspect(Day2.pw_check("lib/input.txt", valid?))
IO.inspect(Day2.pw_check("lib/input.txt", valid2?))
Seems like we all came up with the same solution, more or less
defmodule Aoc2020.Day02 do
def part1() do
input_stream("lib/02/input.txt")
|> Enum.count(fn {policy, password} -> valid_password?(password, policy) end)
|> IO.inspect(label: "part1")
end
def part2() do
input_stream("lib/02/input.txt")
|> Enum.count(fn {policy, password} ->
valid_password2?(password, policy)
end)
|> IO.inspect(label: "part2")
end
defp input_stream(path) do
File.stream!(path)
|> Stream.map(fn line ->
[policy, password] = String.split(line, ":")
{parse_policy(policy), String.trim(password)}
end)
end
def parse_policy(policy) do
[spec, char] = String.split(policy, " ", parts: 2)
[min, max] = String.split(spec, "-", parts: 2)
{String.to_integer(min), String.to_integer(max), char}
end
def valid_password?(password, {min, max, char}) do
password
|> String.graphemes()
|> Enum.count(fn x -> x === char end)
|> Kernel.in(min..max)
end
@spec valid_password2?(binary, {integer, integer, any}) :: boolean
def valid_password2?(password, {a, b, char}) do
xor(String.at(password, a - 1) === char, String.at(password, b - 1) === char)
end
defp xor(a, b) when is_boolean(a) and is_boolean(b), do: a != b
end
I really like your solutions. Would it be too much too ask if you could explain this syntax a little bit?
<<^char <- pass>>
is a generator I presume?
for, reduce, do
- I remember vaguely it was one of the newer syntax in 1.10 or about but canāt find the docs now.
Iāll give it a shot.
<<^char <- pass>>
filters the characters from the pass
string to match the char
which is pinned to avoid reassignment. I confess Iām not sure why the pin is necessary. You can see the docs here about the filtering step. This works because wrapping it in the << ... >>
syntax exposes the bitstring representation of pass
.
Generators can also be used to filter as it removes any value that doesnāt match the pattern on the left side of ā
The pin operator is discussed here.
Hopefully someone more knowledgeable will correct me or explain it better.