DEV Community

Caleb Weeks
Caleb Weeks

Posted on

1

Advent of Code #2 (in Gleam)

One thing that tripped me up for a while when solving today's puzzle is that grouping expressions in Gleam is done using {} brackets instead of parentheses. That probably cost me a good 15 minutes to figure out.

Gleam doesn't have a whole lot of functions that let you operate on a list by index. There is index_map and index_fold, but no way of adding or removing an item by index. Instead, you have to split at the index and use drop to take out items. It took me a while to put those pieces together. There is probably a more analytical method of finding which level to remove from the report for part 2, but I ended up going with the naive approach.

import gleam/int
import gleam/io
import gleam/list
import gleam/string
import simplifile as file

const example = "
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
"

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

fn parse_reports(input: String) -> List(List(Int)) {
  input
  |> string.trim
  |> string.split("\n")
  |> list.map(fn(line) {
    string.split(line, " ")
    |> list.map(fn(number) {
      let assert Ok(number) = int.parse(number)
      number
    })
  })
}

fn safe_report(report: List(Int)) -> Bool {
  let diff = 
    list.window_by_2(report)
    |> list.map(fn(pair) {
      let #(left, right) = pair
      left - right
    })
  let all_decreasing = list.all(diff, fn(level) { level < 0 })
  let all_increasing = list.all(diff, fn(level) { level > 0 })
  let all_in_range = list.all(diff, fn(level) {
    int.absolute_value(level) <= 3 && int.absolute_value(level) > 0
  })
  all_in_range && {all_decreasing || all_increasing} 
}

fn part1(input: String) -> Int {
  let reports = parse_reports(input)
  list.filter(reports, safe_report)
  |> list.length
}

fn report_variations(report: List(Int)) -> List(List(Int)) {
  list.index_map(report, fn(_, index) {
    let #(left, right) = list.split(report, index)
    list.append(left, list.drop(right, 1))
  })
}

fn part2(input: String) -> Int {
  let reports = parse_reports(input)
  list.filter(reports, fn(report) {
    list.any([report, ..report_variations(report)], safe_report)
  })
  |> list.length
}
Enter fullscreen mode Exit fullscreen mode

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (4)

Collapse
 
mvlootman profile image
Michiel Vlootman

To create the variations you could use the list.combinations

 let n = list.length(report)
 list.combinations(report, n - 1)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
sethcalebweeks profile image
Caleb Weeks • Edited

That's great! Thanks for the tip!

Is that guaranteed to preserve ordering?

Collapse
 
mvlootman profile image
Michiel Vlootman

Yes, it preserves the order of the list.
see github.com/gleam-lang/stdlib/blob/...

Thread Thread
 
sethcalebweeks profile image
Caleb Weeks

Good to know!

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay