diff options
author | Tomasz Chojnacki <tomaszchojnacki2001@gmail.com> | 2023-01-31 22:19:02 +0100 |
---|---|---|
committer | Tomasz Chojnacki <tomaszchojnacki2001@gmail.com> | 2023-01-31 22:19:02 +0100 |
commit | 523fb053827b60bdcb4023e19509aec94f27dbfb (patch) | |
tree | 63977b7ae8125937308e7c0e095a823c6788cdcf /aoc-2020-gleam | |
parent | 96d397d8e184606315ef0e08273cea4f3fcd3f5d (diff) | |
download | gleam_aoc2020-523fb053827b60bdcb4023e19509aec94f27dbfb.tar.gz gleam_aoc2020-523fb053827b60bdcb4023e19509aec94f27dbfb.zip |
Very rough day 2 implementation
Diffstat (limited to 'aoc-2020-gleam')
-rw-r--r-- | aoc-2020-gleam/src/aoc_2020_gleam.gleam | 2 | ||||
-rw-r--r-- | aoc-2020-gleam/src/days/day02.gleam | 86 | ||||
-rw-r--r-- | aoc-2020-gleam/src/util/parser.gleam | 192 |
3 files changed, 280 insertions, 0 deletions
diff --git a/aoc-2020-gleam/src/aoc_2020_gleam.gleam b/aoc-2020-gleam/src/aoc_2020_gleam.gleam index e27910d..a208bfc 100644 --- a/aoc-2020-gleam/src/aoc_2020_gleam.gleam +++ b/aoc-2020-gleam/src/aoc_2020_gleam.gleam @@ -1,11 +1,13 @@ import gleam/io import util/runner import days/day01 +import days/day02 pub fn main() -> Nil { use day <- runner.with_day() case day { 1 -> day01.run() + 2 -> day02.run() _ -> io.println("Day not found!") } } diff --git a/aoc-2020-gleam/src/days/day02.gleam b/aoc-2020-gleam/src/days/day02.gleam new file mode 100644 index 0000000..a56e0e1 --- /dev/null +++ b/aoc-2020-gleam/src/days/day02.gleam @@ -0,0 +1,86 @@ +import gleam/io +import gleam/list +import gleam/string +import gleam/pair +import gleam/bool +import ext/resultx +import util/input_util +import util/parser as p + +type Policy { + Policy(min: Int, max: Int, grapheme: String) +} + +type Line { + Line(policy: Policy, password: String) +} + +fn is_line_valid1(line: Line) -> Bool { + line.password + |> string.to_graphemes + |> list.filter(for: fn(g) { g == line.policy.grapheme }) + |> list.length + |> fn(l) { line.policy.min <= l && l <= line.policy.max } +} + +fn is_line_valid2(line: Line) -> Bool { + let graphemes = string.to_graphemes(line.password) + let grapheme_matches = fn(idx) { + list.at(in: graphemes, get: idx - 1) + |> resultx.force_unwrap == line.policy.grapheme + } + bool.exclusive_or( + grapheme_matches(line.policy.min), + grapheme_matches(line.policy.max), + ) +} + +fn parse_policy() -> p.Parser(Policy) { + p.int() + |> p.then_skip(p.grapheme("-")) + |> p.and_then(p.int()) + |> p.then_skip(p.grapheme(" ")) + |> p.and_then(p.any_grapheme()) + |> p.then_skip(p.grapheme(":")) + |> p.then_skip(p.grapheme(" ")) + |> p.map(with: fn(x) { + let #(#(min, max), grapheme) = x + Policy(min, max, grapheme) + }) +} + +fn parse_line(string: String) -> Line { + let line_parser = + parse_policy() + |> p.and_then(p.all_remaining()) + |> p.map(fn(t) { Line(pair.first(t), pair.second(t)) }) + + assert Ok(#(policy, _)) = p.run(line_parser, on: string) + policy +} + +fn part1(lines: List(String)) -> Int { + lines + |> list.map(with: parse_line) + |> list.filter(for: is_line_valid1) + |> list.length +} + +fn part2(lines: List(String)) -> Int { + lines + |> list.map(with: parse_line) + |> list.filter(for: is_line_valid2) + |> list.length +} + +pub fn run() -> Nil { + let test = input_util.read_lines("test02") + assert 2 = part1(test) + assert 1 = part2(test) + + let input = input_util.read_lines("day02") + io.debug(part1(input)) + io.debug(part2(input)) + + Nil +} diff --git a/aoc-2020-gleam/src/util/parser.gleam b/aoc-2020-gleam/src/util/parser.gleam new file mode 100644 index 0000000..43c4e5c --- /dev/null +++ b/aoc-2020-gleam/src/util/parser.gleam @@ -0,0 +1,192 @@ +import gleam/string +import gleam/list +import gleam/function +import gleam/pair +import ext/resultx +import ext/intx + +// Heavily inspired by https://fsharpforfunandprofit.com/posts/understanding-parser-combinators/ + +pub type ParseResult(a) = + Result(#(a, String), String) + +pub opaque type Parser(a) { + Parser(parser: fn(String) -> ParseResult(a)) +} + +pub fn run(parser: Parser(a), on input: String) -> ParseResult(a) { + assert Parser(function) = parser + function(input) +} + +pub fn any_grapheme() -> Parser(String) { + let inner = fn(input) { + case string.pop_grapheme(input) { + Ok(#(first, rest)) -> Ok(#(first, rest)) + _ -> Error("Error!") + } + } + Parser(inner) +} + +pub fn grapheme(expected: String) -> Parser(String) { + let inner = fn(input) { + case string.pop_grapheme(input) { + Ok(#(first, rest)) if first == expected -> Ok(#(expected, rest)) + _ -> Error("Error!") + } + } + Parser(inner) +} + +pub fn and_then(parser1: Parser(a), parser2: Parser(b)) -> Parser(#(a, b)) { + let inner = fn(input) { + let result1 = run(parser1, on: input) + case result1 { + Error(err) -> Error(err) + Ok(#(value1, remaining1)) -> { + let result2 = run(parser2, on: remaining1) + case result2 { + Error(err) -> Error(err) + Ok(#(value2, remaining2)) -> { + let new_value = #(value1, value2) + Ok(#(new_value, remaining2)) + } + } + } + } + } + Parser(inner) +} + +pub fn then_skip(parser1: Parser(a), parser2: Parser(b)) -> Parser(a) { + parser1 + |> and_then(parser2) + |> map(with: pair.first) +} + +pub fn or_else(parser1: Parser(a), parser2: Parser(a)) -> Parser(a) { + let inner = fn(input) { + let result1 = run(parser1, on: input) + case result1 { + Ok(_result) -> result1 + Error(_err) -> { + let result2 = run(parser2, on: input) + result2 + } + } + } + Parser(inner) +} + +pub fn choice(from parsers: List(Parser(a))) -> Parser(a) { + list.reduce(over: parsers, with: or_else) + |> resultx.force_unwrap +} + +pub fn any_of(given graphemes: List(String)) -> Parser(String) { + graphemes + |> list.map(with: grapheme) + |> choice +} + +pub fn lowercase() -> Parser(String) { + "abcdefghijklmnopqrstuvwxyz" + |> string.to_graphemes + |> any_of +} + +pub fn digit() -> Parser(String) { + "0123456789" + |> string.to_graphemes + |> any_of +} + +pub fn map(parser: Parser(a), with mapper: fn(a) -> b) -> Parser(b) { + let inner = fn(input) { + let result = run(parser, on: input) + case result { + Ok(#(value, remaining)) -> { + let new_value = mapper(value) + Ok(#(new_value, remaining)) + } + Error(err) -> Error(err) + } + } + Parser(inner) +} + +fn return(x: a) -> Parser(a) { + let inner = fn(input) { Ok(#(x, input)) } + Parser(inner) +} + +fn apply(fp: Parser(fn(a) -> b), xp: Parser(a)) -> Parser(b) { + and_then(fp, xp) + |> map(fn(p) { + let #(f, x) = p + f(x) + }) +} + +fn lift2(f: fn(a, b) -> c) -> fn(Parser(a), Parser(b)) -> Parser(c) { + let inner = fn(xp, yp) { + let fc = function.curry2(f) + apply(apply(return(fc), xp), yp) + } + inner +} + +pub fn sequence(of parsers: List(Parser(a))) -> Parser(List(a)) { + let cons_p = lift2(list.prepend) + case parsers { + [] -> return([]) + [head, ..tail] -> cons_p(sequence(tail), head) + } +} + +fn parse_zero_or_more(parser: Parser(a), input: String) -> #(List(a), String) { + let first_result = run(parser, on: input) + case first_result { + Error(_err) -> #([], input) + Ok(#(first_value, input_after_first_parse)) -> { + let #(subsequent_values, remaining_input) = + parse_zero_or_more(parser, input_after_first_parse) + let values = [first_value, ..subsequent_values] + #(values, remaining_input) + } + } +} + +pub fn many(parser: Parser(a)) -> Parser(List(a)) { + let inner = fn(input) { Ok(parse_zero_or_more(parser, input)) } + Parser(inner) +} + +pub fn many1(parser: Parser(a)) -> Parser(List(a)) { + let inner = fn(input) { + let first_result = run(parser, on: input) + case first_result { + Error(err) -> Error(err) + Ok(#(first_value, input_after_first_parse)) -> { + let #(subsequent_values, remaining_input) = + parse_zero_or_more(parser, input_after_first_parse) + let values = [first_value, ..subsequent_values] + Ok(#(values, remaining_input)) + } + } + } + Parser(inner) +} + +pub fn int() -> Parser(Int) { + digit() + |> many1 + |> map(with: function.compose(string.concat, intx.force_parse)) +} + +pub fn all_remaining() -> Parser(String) { + any_grapheme() + |> many + |> map(with: string.concat) +} |