this post was submitted on 05 Dec 2024
26 points (100.0% liked)

Advent Of Code

1039 readers
1 users here now

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2024

Solution Threads

M T W T F S S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 2 years ago
MODERATORS
 

Day 5: Print Queue

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

(page 2) 7 comments
sorted by: hot top controversial new old
[–] [email protected] 1 points 4 months ago

Elixir

defmodule AdventOfCode.Solution.Year2024.Day05 do
  use AdventOfCode.Solution.SharedParse

  @impl true
  def parse(input) do
    [rules, pages_list] =
      String.split(input, "\n\n", limit: 2) |> Enum.map(&String.split(&1, "\n", trim: true))

    {for(rule <- rules, do: String.split(rule, "|") |> Enum.map(&String.to_integer/1))
     |> MapSet.new(),
     for(pages <- pages_list, do: String.split(pages, ",") |> Enum.map(&String.to_integer/1))}
  end

  def part1({rules, pages_list}), do: solve(rules, pages_list, false)

  def part2({rules, pages_list}), do: solve(rules, pages_list, true)

  def solve(rules, pages_list, negate) do
    for pages <- pages_list, reduce: 0 do
      total ->
        ordered = Enum.sort(pages, &([&1, &2] in rules))

        if negate != (ordered == pages),
          do: total + Enum.at(ordered, div(length(ordered), 2)),
          else: total
    end
  end
end
[–] [email protected] 1 points 4 months ago

Zig

const std = @import("std");
const List = std.ArrayList;
const Map = std.AutoHashMap;

const tokenizeScalar = std.mem.tokenizeScalar;
const splitScalar = std.mem.splitScalar;
const parseInt = std.fmt.parseInt;
const print = std.debug.print;
const contains = std.mem.containsAtLeast;
const eql = std.mem.eql;

var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();

const Answer = struct {
    middle_sum: i32,
    reordered_sum: i32,
};

pub fn solve(input: []const u8) !Answer {
    var rows = splitScalar(u8, input, '\n');

    // key is a page number and value is a
    // list of pages to be printed before it
    var rules = Map(i32, List(i32)).init(alloc);
    var pages = List([]i32).init(alloc);
    defer {
        var iter = rules.iterator();
        while (iter.next()) |rule| {
            rule.value_ptr.deinit();
        }
        rules.deinit();
        pages.deinit();
    }

    var parse_rules = true;
    while (rows.next()) |row| {
        if (eql(u8, row, "")) {
            parse_rules = false;
            continue;
        }

        if (parse_rules) {
            var rule_pair = tokenizeScalar(u8, row, '|');
            const rule = try rules.getOrPut(try parseInt(i32, rule_pair.next().?, 10));
            if (!rule.found_existing) {
                rule.value_ptr.* = List(i32).init(alloc);
            }
            try rule.value_ptr.*.append(try parseInt(i32, rule_pair.next().?, 10));
        } else {
            var page = List(i32).init(alloc);
            var page_list = tokenizeScalar(u8, row, ',');
            while (page_list.next()) |list| {
                try page.append(try parseInt(i32, list, 10));
            }
            try pages.append(try page.toOwnedSlice());
        }
    }

    var middle_sum: i32 = 0;
    var reordered_sum: i32 = 0;

    var wrong_order = false;
    for (pages.items) |page| {
        var index: usize = page.len - 1;
        while (index > 0) : (index -= 1) {
            var page_rule = rules.get(page[index]) orelse continue;

            // check the rest of the pages
            var remaining: usize = 0;
            while (remaining < page[0..index].len) {
                if (contains(i32, page_rule.items, 1, &[_]i32{page[remaining]})) {
                    // re-order the wrong page
                    const element = page[remaining];
                    page[remaining] = page[index];
                    page[index] = element;
                    wrong_order = true;

                    if (rules.get(element)) |next_rule| {
                        page_rule = next_rule;
                    }

                    continue;
                }
                remaining += 1;
            }
        }
        if (wrong_order) {
            reordered_sum += page[(page.len - 1) / 2];
            wrong_order = false;
        } else {
            // middle page number
            middle_sum += page[(page.len - 1) / 2];
        }
    }
    return Answer{ .middle_sum = middle_sum, .reordered_sum = reordered_sum };
}

pub fn main() !void {
    const answer = try solve(@embedFile("input.txt"));
    print("Part 1: {d}\n", .{answer.middle_sum});
    print("Part 2: {d}\n", .{answer.reordered_sum});
}

test "test input" {
    const answer = try solve(@embedFile("test.txt"));
    try std.testing.expectEqual(143, answer.middle_sum);
    try std.testing.expectEqual(123, answer.reordered_sum);
}

[–] [email protected] 1 points 4 months ago

Lisp

Part 1 and 2


(defun p1-process-rules (line)
  (mapcar #'parse-integer (uiop:split-string line :separator "|")))

(defun p1-process-pages (line)
  (mapcar #'parse-integer (uiop:split-string line :separator ",")))

(defun middle (pages)
  (nth (floor (length pages) 2) pages))

(defun check-rule-p (rule pages)
  (let ((p1 (position (car rule) pages))
        (p2 (position (cadr rule) pages)))
    (or (not p1) (not p2) (< p1 p2))))

(defun ordered-p (pages rules)
  (loop for r in rules
        unless (check-rule-p r pages)
          return nil
        finally
           (return t)))

(defun run-p1 (rules-file pages-file) 
  (let ((rules (read-file rules-file #'p1-process-rules))
        (pages (read-file pages-file #'p1-process-pages)))
    (loop for p in pages
          when (ordered-p p rules)
            sum (middle p)
          )))

(defun fix-pages (rules pages)
  (sort pages (lambda (p1 p2) (ordered-p (list p1 p2) rules)) ))

(defun run-p2 (rules-file pages-file) 
  (let ((rules (read-file rules-file #'p1-process-rules))
        (pages (read-file pages-file #'p1-process-pages)))
    (loop for p in pages
          unless (ordered-p p rules)
            sum (middle (fix-pages rules p))
          )))

[–] [email protected] 1 points 4 months ago

Uiua

This is the first one that caused me some headache because I didn't read the instructions carefully enough.
I kept trying to create a sorted list for when all available pages were used, which got me stuck in an endless loop.

Another fun part was figuring out to use memberof (∈) instead of find (βŒ•) in the last line of FindNext. So much time spent on debugging other areas of the code

Run with example input here

FindNext ← βŠ™(
  ⊑1⍉,
  βŠƒβ–½(β–½Β¬)⊸∈
  βŠ™βŠ™(⊑0⍉.)
  :βŠ™(⟜(β–½Β¬βˆˆ))
)

# find the order of pages for a given set of rules
FindOrder ← (
  β—΄β™­.
  []
  ⍒(βŠ‚FindNext|β‹…(>1⧻))
  βŠ™β—ŒβŠ‚
)

PartOne ← (
  &rs ∞ &fo "input-5.txt"
  βˆ©Β°β–‘Β°βŠŸβŠœβ–‘Β¬βŒ•"\n\n".
  βŠ™(⊜(β–‘βŠœβ‹•β‰ @,.)β‰ @\n.β†˜1)
  ⊜(βŠœβ‹•β‰ @|.)β‰ @\n.

  βŠ™.
  Β€
  ⊞(β—‘(Β°β–‘:)
    ⟜:βŠ™(Β°βŠŸβ‰)
    =2+∩∈
    β–½
    FindOrder
    βŠΈβ‰Β°β–‘:
    βŠ™β—Œ
  )
  ≑◇(⊑⌊÷2⧻.)β–½β™­
  /+
)

PartTwo ← (
  &rs ∞ &fo "input-5.txt"
  βˆ©Β°β–‘Β°βŠŸβŠœβ–‘Β¬βŒ•"\n\n".
  βŠ™(⊜(β–‘βŠœβ‹•β‰ @,.)β‰ @\n.β†˜1)
  ⊜(βŠœβ‹•β‰ @|.)β‰ @\n.
  βŠ™.
  ⍜€⊞(
    β—‘(Β°β–‘:)
    ⟜:βŠ™(Β°βŠŸβ‰)
    =2+∩∈
    β–½
    FindOrder
    βŠΈβ‰Β°β–‘:
    βŠŸβˆ©β–‘
  )
  βŠ™β—Œ
  βŠƒ(⊑0)(⊑1)⍉
  ≑◇(⊑⌊÷2⧻.)▽¬≑°░
  /+
)

&p "Day 5:"
&pf "Part 1: "
&p PartOne
&pf "Part 2: "
&p PartTwo
[–] [email protected] 1 points 4 months ago* (last edited 4 months ago)

I've got a "smart" solution and a really dumb one. I'll start with the smart one (incomplete but you can infer). I did four different ways to try to get it faster, less memory, etc.

// this is from a nuget package. My Mathy roommate told me this was a topological sort.
// It's also my preferred, since it'd perform better on larger data sets.
return lines
    .AsParallel()
    .Where(line => !IsInOrder(GetSoonestOccurrences(line), aggregateRules))
    .Sum(line => line.StableOrderTopologicallyBy(
            getDependencies: page =>
                aggregateRules.TryGetValue(page, out var mustPreceed) ? mustPreceed.Intersect(line) : Enumerable.Empty<Page>())
        .Middle()
    );

The dumb solution. These comparisons aren't fully transitive. I can't believe it works.

public static SortedSet<Page> Sort3(Page[] line,
    Dictionary<Page, System.Collections.Generic.HashSet<Page>> rules)
{
    // how the hell is this working?
    var sorted = new SortedSet<Page>(new Sort3Comparer(rules));
    foreach (var page in line)
        sorted.Add(page);
    return sorted;
}

public static Page[] OrderBy(Page[] line, Dictionary<Page, System.Collections.Generic.HashSet<Page>> rules)
{
    return line.OrderBy(identity, new Sort3Comparer(rules)).ToArray();
}

sealed class Sort3Comparer : IComparer<Page>
{
    private readonly Dictionary<Page, System.Collections.Generic.HashSet<Page>> _rules;

    public Sort3Comparer(Dictionary<Page, System.Collections.Generic.HashSet<Page>> rules) => _rules = rules;

    public int Compare(Page x, Page y)
    {
        if (_rules.TryGetValue(x, out var xrules))
        {
            if (xrules.Contains(y))
                return -1;
        }

        if (_rules.TryGetValue(y, out var yrules))
        {
            if (yrules.Contains(x))
                return 1;
        }

        return 0;
    }
}
Method Mean Error StdDev Gen0 Gen1 Allocated
Part2_UsingList (literally just Insert) 660.3 us 12.87 us 23.20 us 187.5000 35.1563 1144.86 KB
Part2_TrackLinkedList (wrong now) 1,559.7 us 6.91 us 6.46 us 128.9063 21.4844 795.03 KB
Part2_TopologicalSort 732.3 us 13.97 us 16.09 us 285.1563 61.5234 1718.36 KB
Part2_SortedSet 309.1 us 4.13 us 3.45 us 54.1992 10.2539 328.97 KB
Part2_OrderBy 304.5 us 6.09 us 9.11 us 48.8281 7.8125 301.29 KB
[–] [email protected] 0 points 4 months ago

Rust

Kinda sorta got day 5 done on time.

use std::cmp::Ordering;

use crate::utils::{bytes_to_num, read_lines};

pub fn solution1() {
    let mut lines = read_input();
    let rules = parse_rules(&mut lines);

    let middle_rules_sum = lines
        .filter_map(|line| {
            let line_nums = rule_line_to_list(&line);
            line_nums
                .is_sorted_by(|&a, &b| is_sorted(&rules, (a, b)))
                .then_some(line_nums[line_nums.len() / 2])
        })
        .sum::<usize>();

    println!("Sum of in-order middle rules = {middle_rules_sum}");
}

pub fn solution2() {
    let mut lines = read_input();
    let rules = parse_rules(&mut lines);

    let middle_rules_sum = lines
        .filter_map(|line| {
            let mut line_nums = rule_line_to_list(&line);

            (!line_nums.is_sorted_by(|&a, &b| is_sorted(&rules, (a, b)))).then(|| {
                line_nums.sort_by(|&a, &b| {
                    is_sorted(&rules, (a, b))
                        .then_some(Ordering::Less)
                        .unwrap_or(Ordering::Greater)
                });

                line_nums[line_nums.len() / 2]
            })
        })
        .sum::<usize>();

    println!("Sum of middle rules = {middle_rules_sum}");
}

fn read_input() -> impl Iterator<Item = String> {
    read_lines("src/day5/input.txt")
}

fn parse_rules(lines: &mut impl Iterator<Item = String>) -> Vec<(usize, usize)> {
    lines
        .take_while(|line| !line.is_empty())
        .fold(Vec::new(), |mut rules, line| {
            let (a, b) = line.as_bytes().split_at(2);
            let a = bytes_to_num(a);
            let b = bytes_to_num(&b[1..]);

            rules.push((a, b));

            rules
        })
}

fn rule_line_to_list(line: &str) -> Vec<usize> {
    line.split(',')
        .map(|s| bytes_to_num(s.as_bytes()))
        .collect::<Vec<_>>()
}

fn is_sorted(rules: &[(usize, usize)], tuple: (usize, usize)) -> bool {
    rules.iter().any(|&r| r == tuple)
}

Reusing my bytes_to_num function from day 3 feels nice. Pretty fun challenge.

load more comments
view more: β€Ή prev next β€Ί