aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Chojnacki <tomaszchojnacki2001@gmail.com>2023-01-31 22:19:02 +0100
committerTomasz Chojnacki <tomaszchojnacki2001@gmail.com>2023-01-31 22:19:02 +0100
commit523fb053827b60bdcb4023e19509aec94f27dbfb (patch)
tree63977b7ae8125937308e7c0e095a823c6788cdcf
parent96d397d8e184606315ef0e08273cea4f3fcd3f5d (diff)
downloadgleam_aoc2020-523fb053827b60bdcb4023e19509aec94f27dbfb.tar.gz
gleam_aoc2020-523fb053827b60bdcb4023e19509aec94f27dbfb.zip
Very rough day 2 implementation
-rw-r--r--aoc-2020-gleam/src/aoc_2020_gleam.gleam2
-rw-r--r--aoc-2020-gleam/src/days/day02.gleam86
-rw-r--r--aoc-2020-gleam/src/util/parser.gleam192
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)
+}