DEV Community

Caleb Weeks
Caleb Weeks

Posted on

Advent of Code #5 (in Gleam)

Last year, there were a few problems that involved dealing with ordering functions. I was unaware of any language at the time that had functions in the standard library for composing ordering, and said that I would like to add that to my own language if I ever got around to finally making it. Well, I was delighted to find that Gleam as an order module, complete with functions to compose orders in different ways!

I discovered Gleam about 4 years ago when I started working with Elixir. The dynamic typing of Elixir was initially a turn off for me, so I looked around for options with static typing. Of course, Gleam was in its infancy at the time, so I never really tried it out until recently. But in many ways, Gleam is conceptually and semantically very similar to a language that I have been conceptually designing for the last few years.

In addition to the ordering module, the other feature I wanted to implement in my language was surfacing only one mechanism for conditional logic through pattern matching. Well, that's exactly what Gleam does. There is only a case expression. No if or else or cond or anything. It's neat to use Gleam to get of feel for what this actually looks like in practice.

There is one bit of code from my solution today that I think would be more intuitive with some other control flow structure. I would like to express something along the lines of:

if "a|b" in list return LT
if "b|a" in list return GT
else return EQ
Enter fullscreen mode Exit fullscreen mode

Without early returns (or any return keyword, for that matter), we have to either resort to nesting or combining into tuples to pattern match. I went with the second option:

let lt = list.contains(rules, a <> "|" <> b)
let gt = list.contains(rules, b <> "|" <> a)
case lt, gt {
  True, _ -> Lt
  _, True -> Gt
  _, _ -> Eq
}
Enter fullscreen mode Exit fullscreen mode

I don't think this code is particularly intuitive, but I'm not sure if there is a better way without perhaps using an additional helper function. If you can think of a more intuitive way of expressing this, please let me know! Here's my complete solution:

import gleam/int
import gleam/io
import gleam/list
import gleam/string
import gleam/dict
import gleam/result
import gleam/order.{type Order, Eq, Lt, Gt}
import simplifile as file

const example = "
47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13

75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47
"

pub fn main() {
  let assert Ok(input) = file.read("input")
  let assert 143 = part1(example)
  let assert 123 = part2(example)
  part1(input) |> int.to_string |> io.println
  part2(input) |> int.to_string |> io.println
}

fn parse_input(input: String) -> #(fn(String, String) -> Order, List(List(String))) {
  let assert [rules, updates] = 
    input
    |> string.trim
    |> string.split("\n\n")

  let rules = string.split(rules, "\n")

  let updates =
    updates
    |> string.split("\n")
    |> list.map(fn(update) {
      string.split(update, ",")
    })

  let ordering = fn(a, b) {
    let lt = list.contains(rules, a <> "|" <> b)
    let gt = list.contains(rules, b <> "|" <> a)
    case lt, gt {
      True, _ -> Lt
      _, True -> Gt
      _, _ -> Eq
    }
  }

  #(ordering, updates)
}

fn middle(list: List(String)) -> Int {
  let midpoint = list.length(list) / 2
  list
  |> list.drop(midpoint)
  |> list.first
  |> result.then(int.parse)
  |> result.unwrap(0)
}

fn part1(input: String) -> Int {
  let #(ordering, updates) = parse_input(input)
  list.fold(updates, 0, fn(sum, update) {
    case list.sort(update, ordering) == update {
      True -> sum + middle(update)
      False -> sum
    }
  })
}

fn part2(input: String) -> Int {
  let #(ordering, updates) = parse_input(input)
  list.fold(updates, 0, fn(sum, update) {
    let sorted = list.sort(update, ordering)
    case sorted != update {
      True -> sum + middle(sorted)
      False -> sum
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)