Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
devries committed Nov 3, 2024
0 parents commit 1a22a12
Show file tree
Hide file tree
Showing 12 changed files with 351 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: test

on:
push:
branches:
- master
- main
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: "26.0.2"
gleam-version: "1.5.1"
rebar3-version: "3"
# elixir-version: "1.15.4"
- run: gleam deps download
- run: gleam test
- run: gleam format --check src test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.beam
*.ez
/build
erl_crash.dump
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# aoc2024

[![Tests](https://github.com/devries/advent_of_code_2024/actions/workflows/test.yml/badge.svg)](https://github.com/devries/advent_of_code_2024/actions/workflows/test.yml)
[![Stars: 0](https://img.shields.io/badge/⭐_Stars-0-yellow)](https://adventofcode.com/2024)

This year I am going to try to do Advent of Code in [Gleam](https://gleam.run).
To run a day's problems use the command

```sh
gleam run -m day01/solution
```

To run the unit tests for all the days run

```sh
gleam test
```
20 changes: 20 additions & 0 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name = "aoc2024"
version = "1.0.0"

# Fill out these fields if you intend to generate HTML documentation or publish
# your project to the Hex package manager.
#
# description = ""
# licences = ["Apache-2.0"]
# repository = { type = "github", user = "", repo = "" }
# links = [{ title = "Website", href = "" }]
#
# For a full reference of all the available options, you can have a look at
# https://gleam.run/writing-gleam/gleam-toml/.

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
simplifile = ">= 2.2.0 and < 3.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
14 changes: 14 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file was generated by Gleam
# You typically do not need to edit this file

packages = [
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "gleam_stdlib", version = "0.41.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "1B2F80CB1B66B027E3198A2FF71EF3F2F31DF89ED97AD606F25FD387A4C3C1EF" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" },
]

[requirements]
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
simplifile = { version = ">= 2.2.0 and < 3.0.0" }
7 changes: 7 additions & 0 deletions src/aoc2024.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import gleam/io

pub fn main() {
io.println(
"Use \"gleam run -m dayXX/solution\" to run the solution\nfrom a particular day.\n\nFor example:\n gleam run -m day01/solution",
)
}
20 changes: 20 additions & 0 deletions src/internal/aoc_utils.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import gleam/result
import gleam/string
import simplifile

/// Read Advent of Code input file and split into a list of lines.
pub fn read_lines(
from filepath: String,
) -> Result(List(String), simplifile.FileError) {
simplifile.read(from: filepath)
// Be sure to get rid of final newline
|> result.map(string.trim)
|> result.map(string.split(_, "\n"))
}

pub fn solution_or_error(v: Result(String, String)) -> String {
case v {
Ok(solution) -> solution
Error(error) -> "ERROR: " <> error
}
}
88 changes: 88 additions & 0 deletions src/internal/dijkstra.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import internal/lheap
import gleam/dict
import gleam/option.{type Option, None, Some}
import gleam/result

// Since reprioritizing nodes is difficult and expensive in a leftist heap
// I track the minimum distance for each element and the previous node.
// If push a value that has a distance greater than or equal to the
// minimum seen, I just drop that element. If I pop one that is not the
// smallest distance seen I discard it and pop another.

pub opaque type Queue(element) {
Queue(
heap: lheap.Tree(element),
minmap: dict.Dict(element, #(Int, Option(element))),
)
}

pub fn new() -> Queue(element) {
Queue(heap: lheap.new(), minmap: dict.new())
}

pub fn push(
queue: Queue(element),
distance: Int,
node: element,
previous_node: Option(element),
) -> Queue(element) {
case dict.get(queue.minmap, node) {
Ok(#(n, _)) if n <= distance -> queue
_ -> {
let newminmap =
dict.insert(queue.minmap, node, #(distance, previous_node))
let newheap = lheap.push(queue.heap, distance, node)
Queue(heap: newheap, minmap: newminmap)
}
}
}

pub fn push_list(
queue: Queue(element),
values: List(#(Int, element)),
previous_node: Option(element),
) -> Queue(element) {
case values {
[] -> queue
[first, ..rest] -> {
let newqueue = push(queue, first.0, first.1, previous_node)
push_list(newqueue, rest, previous_node)
}
}
}

pub fn pop(
queue: Queue(element),
) -> Result(#(Queue(element), Int, element), Nil) {
use #(newheap, distance, node) <- result.try(lheap.pop(queue.heap))

let newqueue = Queue(heap: newheap, minmap: queue.minmap)

// check if this is the lowest distance variant of this node
// if it is not go on to the next one. This eliminates leftover
// elements which were added before better ones were found.
let best_result = dict.get(queue.minmap, node)
case best_result {
Ok(#(best, _)) if best < distance -> pop(newqueue)
_ -> Ok(#(newqueue, distance, node))
}
}

pub fn get_path(
queue: Queue(element),
ending: element,
) -> Result(List(element), Nil) {
get_path_acc(queue, ending, [ending])
}

fn get_path_acc(
queue: Queue(element),
ending: element,
acc: List(element),
) -> Result(List(element), Nil) {
case dict.get(queue.minmap, ending) {
Error(Nil) -> Error(Nil)
Ok(#(_, None)) -> Ok(acc)
Ok(#(_, Some(node))) -> get_path_acc(queue, node, [node, ..acc])
}
}
99 changes: 99 additions & 0 deletions src/internal/lheap.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import gleam/list

pub opaque type Tree(element) {
Null
Node(
key: Int,
payload: element,
s: Int,
left: Tree(element),
right: Tree(element),
)
}

fn merge(a: Tree(element), b: Tree(element)) -> Tree(element) {
case a, b {
Null, Null -> Null
Null, _ -> b
_, Null -> a
Node(a_key, a_payload, _, a_left, a_right), Node(b_key, _, _, _, _) -> {
case a_key > b_key {
True -> merge(b, a)
False -> {
let newright = merge(a_right, b)
case a_left, newright {
Null, Node(_, _, _, _, _) ->
Node(
key: a_key,
payload: a_payload,
s: 1,
left: newright,
right: a_left,
)
Node(_, _, l_s, _, _), Node(_, _, r_s, _, _) if l_s < r_s ->
Node(
key: a_key,
payload: a_payload,
s: l_s + 1,
left: newright,
right: a_left,
)
_, Node(_, _, r_s, _, _) ->
Node(
key: a_key,
payload: a_payload,
s: r_s + 1,
left: a_left,
right: newright,
)
_, _ -> Null
}
}
}
}
}
}

pub fn new() -> Tree(element) {
Null
}

pub fn push(a: Tree(element), value: Int, payload: element) -> Tree(element) {
let b = Node(value, payload, 1, Null, Null)
merge(a, b)
}

pub fn push_list(
a: Tree(element),
values: List(#(Int, element)),
) -> Tree(element) {
case values {
[] -> a
[first, ..rest] -> {
let n = push(a, first.0, first.1)
push_list(n, rest)
}
}
}

pub fn pop(a: Tree(element)) -> Result(#(Tree(element), Int, element), Nil) {
case a {
Null -> Error(Nil)
Node(val, payload, _, left, right) ->
Ok(#(merge(left, right), val, payload))
}
}

pub fn getall(a: Tree(element)) -> List(#(Int, element)) {
popall(a, [])
}

fn popall(a: Tree(element), acc: List(#(Int, element))) -> List(#(Int, element)) {
let r = pop(a)
case r {
Error(_) -> list.reverse(acc)
Ok(#(t, v, p)) -> {
popall(t, [#(v, p), ..acc])
}
}
}
17 changes: 17 additions & 0 deletions templates/dayXX_test.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import dayXX/solution
import gleam/string
import gleeunit/should

const testinput = ""

pub fn part1_test() {
let lines = string.split(testinput, "\n")
solution.solve_p1(lines)
|> should.equal(Ok(""))
}

pub fn part2_test() {
let lines = string.split(testinput, "\n")
solution.solve_p2(lines)
|> should.equal(Ok(""))
}
27 changes: 27 additions & 0 deletions templates/solution.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import gleam/io
import internal/aoc_utils

pub fn main() {
let filename = "inputs/dayXX.txt"

let lines_result = aoc_utils.read_lines(from: filename)
case lines_result {
Ok(lines) -> {
// If the file was converting into a list of lines
// successfully then run each part of the problem
io.println("Part 1: " <> aoc_utils.solution_or_error(solve_p1(lines)))
io.println("Part 2: " <> aoc_utils.solution_or_error(solve_p2(lines)))
}
Error(_) -> io.println("Error reading file")
}
}

// Part 1
pub fn solve_p1(lines: List(String)) -> Result(String, String) {
Error("Unimplemented")
}

// Part 2
pub fn solve_p2(lines: List(String)) -> Result(String, String) {
Error("Unimplemented")
}
15 changes: 15 additions & 0 deletions test/aoc2024_test.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import gleeunit
import gleeunit/should
import internal/aoc_utils

pub fn main() {
gleeunit.main()
}

pub fn solution_or_error_test() {
aoc_utils.solution_or_error(Ok("This is good"))
|> should.equal("This is good")

aoc_utils.solution_or_error(Error("This is bad"))
|> should.equal("ERROR: This is bad")
}

0 comments on commit 1a22a12

Please sign in to comment.