Spyke
advent_of_codeΒ·Advent Of Codebyhades

πŸ¦† Everybody.Codes 2025 Quest 5 Solutions πŸ¦†

Quest 5: Fishbone Order

  • 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

Link to participate: https://everybody.codes/

View original on programming.dev

Rust

use itertools::Itertools;

type Fishbone = Vec<(i64, Option<i64>, Option<i64>)>;

fn parse_fishbone(quality_str: &str) -> Fishbone {
    let mut fishbone: Fishbone = vec![];
    'outer: for num in quality_str.split(",").map(|x| x.parse().unwrap()) {
        for e in fishbone.iter_mut() {
            if num < e.0 && e.1.is_none() {
                e.1 = Some(num);
                continue 'outer;
            }
            if num > e.0 && e.2.is_none() {
                e.2 = Some(num);
                continue 'outer;
            }
        }
        fishbone.push((num, None, None));
    }
    fishbone
}

fn compute_quality(fishbone: &Fishbone) -> i64 {
    fishbone
        .iter()
        .map(|(c, _, _)| c.to_string())
        .join("")
        .parse()
        .unwrap()
}

pub fn solve_part_1(input: &str) -> String {
    let (_, data) = input.split_once(":").unwrap();
    compute_quality(&parse_fishbone(data)).to_string()
}

pub fn solve_part_2(input: &str) -> String {
    let mut worst_quality = i64::MAX;
    let mut best_quality = i64::MIN;
    for sword in input.lines() {
        let (_, data) = sword.split_once(":").unwrap();
        let quality = compute_quality(&parse_fishbone(data));
        worst_quality = worst_quality.min(quality);
        best_quality = best_quality.max(quality);
    }
    (best_quality - worst_quality).to_string()
}

pub fn solve_part_3(input: &str) -> String {
    let mut swords: Vec<_> = input
        .lines()
        .map(|def| {
            let (id, data) = def.split_once(":").unwrap();
            let fishbone = parse_fishbone(data);
            (id.parse::<i64>().unwrap(), fishbone)
        })
        .collect();
    swords.sort_by(|a, b| {
        let cmp = compute_quality(&a.1).cmp(&compute_quality(&b.1));
        if !matches!(cmp, std::cmp::Ordering::Equal) {
            return cmp;
        }
        for (a_seg, b_seg) in a.1.iter().zip(b.1.iter()) {
            let a_val = match a_seg {
                (a, Some(b), Some(c)) => format!("{b}{a}{c}"),
                (a, Some(b), None) => format!("{b}{a}"),
                (a, None, Some(c)) => format!("{a}{c}"),
                (a, None, None) => format!("{a}"),
            };
            let b_val = match b_seg {
                (a, Some(b), Some(c)) => format!("{b}{a}{c}"),
                (a, Some(b), None) => format!("{b}{a}"),
                (a, None, Some(c)) => format!("{a}{c}"),
                (a, None, None) => format!("{a}"),
            };
            let cmp = a_val.parse::<i64>().unwrap().cmp(&b_val.parse().unwrap());
            if !matches!(cmp, std::cmp::Ordering::Equal) {
                return cmp;
            }
        }
        a.0.cmp(&b.0)
    });
    swords.reverse();
    swords
        .into_iter()
        .enumerate()
        .map(|(pos, (id, _))| id * (pos as i64 + 1))
        .sum::<i64>()
        .to_string()
}
3

Nim

Nothing fancy. Simple iterative tree construction and sort, using the std/algorithm and a custom < operator on types.

type
  LeafNode = ref object
    value: int
  Node = ref object
    value: int
    left, right: LeafNode
    center: Node
  Sword = object
    id, quality: int
    levels: seq[int]
    fishbone: Node

proc add(node: var Node, val: int) =
  var curNode = node
  while not curNode.isNil:
    if val < curNode.value and curNode.left.isNil:
      curNode.left = LeafNode(value: val)
      return
    elif val > curNode.value and curNode.right.isNil:
      curNode.right = LeafNode(value: val)
      return
    elif curNode.center.isNil:
      curNode.center = Node(value: val)
      return
    else: curNode = curNode.center
  node = Node(value: val)

proc calcQuality(sword: Sword): int =
  var res = ""
  var curNode = sword.fishbone
  while not curNode.isNil:
    res &= $curNode.value
    curNode = curNode.center
  return parseInt(res)

proc getLevels(s: Sword): seq[int] =
  var curNode = s.fishbone
  while not curNode.isNil:
    var strVal = ""
    strVal &= (if curNode.left.isNil:  "" else: $curNode.left.value)
    strVal &= $curNode.value
    strVal &= (if curNode.right.isNil: "" else: $curNode.right.value)
    result.add parseInt(strVal)
    curNode = curNode.center

proc `<`(s1, s2: seq[int]): bool =
  for i in 0..min(s1.high, s2.high):
    if s1[i] != s2[i]: return s1[i] < s2[i]
  s1.len < s2.len

proc `<`(s1, s2: Sword): bool =
  if s1.quality != s2.quality: s1.quality < s2.quality
  elif s1.levels != s2.levels: s1.levels < s2.levels
  else: s1.id < s2.id

proc parseSwords(input: string): seq[Sword] =
  for line in input.splitLines:
    let numbers = line.split({':',','}).mapIt(parseInt(it))
    var node= Node(value: numbers[1])
    for num in numbers.toOpenArray(2, numbers.high):
      node.add num
    result &= Sword(id: numbers[0], fishbone: node)

proc solve_part1*(input: string): Solution =
  let swords = parseSwords(input)
  result := swords[0].calcQuality()

proc solve_part2*(input: string): Solution =
  let qualities = parseSwords(input).mapIt(it.calcQuality())
  result := qualities.max - qualities.min

proc solve_part3*(input: string): Solution =
  var swords = parseSwords(input)
  for i in 0..swords.high:
    swords[i].levels = swords[i].getLevels()
    swords[i].quality = swords[i].calcQuality()
  swords.sort(Descending)
  for pos, id in swords.mapit(it.id):
    result.intVal += (pos+1) * id

Full solution at Codeberg: solution.nim

3

I forgot that "weekdays" for a US website means something different for me here in UTC+9.

This was surprisingly fiddly, but I think I managed to do it reasonably neatly.

import Control.Arrow  
import Data.Foldable  
import Data.List (sortBy)  
import Data.List.Split  
import Data.Maybe  
import Data.Ord  

data Fishbone  
  = Fishbone (Maybe Int) Int (Maybe Int) Fishbone  
  | Empty  
  deriving (Eq)  

instance Ord Fishbone where  
  compare = comparing numbers  

readInput :: String -> [(Int, Fishbone)]  
readInput = map readSword . lines  
  where  
    readSword = (read *** build) . break (== ':')  
    build = foldl' insert Empty . map read . splitOn "," . tail  

insert bone x =  
  case bone of  
    (Fishbone l c r next)  
      | isNothing l && x < c -> Fishbone (Just x) c r next  
      | isNothing r && x > c -> Fishbone l c (Just x) next  
      | otherwise -> Fishbone l c r $ insert next x  
    Empty -> Fishbone Nothing x Nothing Empty  

spine (Fishbone _ c _ next) = c : spine next  
spine Empty = []  

numbers :: Fishbone -> [Int]  
numbers (Fishbone l c r next) =  
  (read $ concatMap show $ catMaybes [l, Just c, r])  
    : numbers next  
numbers Empty = []  

quality :: Fishbone -> Int  
quality = read . concatMap show . spine  

part1, part2, part3 :: [(Int, Fishbone)] -> Int  
part1 = quality . snd . head  
part2 = uncurry (-) . (maximum &&& minimum) . map (quality . snd)  
part3 = sum . zipWith (*) [1 ..] . map fst . sortBy (flip compareSwords)  
  where  
    compareSwords =  
      comparing (quality . snd)  
        <> comparing snd  
        <> comparing fst  

main =  
  forM_  
    [ ("everybody_codes_e2025_q05_p1.txt", part1),  
      ("everybody_codes_e2025_q05_p2.txt", part2),  
      ("everybody_codes_e2025_q05_p3.txt", part3)  
    ]  
    $ \(input, solve) -> readFile input >>= print . solve . readInput  
3

What a fiddlybit. I needed records to save me from list-hell on this one.

Scheme/Guile

(import (rnrs io ports (6))
        (rnrs records syntactic))

(define (parse-file file-name)
  (let* ((lines (string-split (string-trim-both (call-with-input-file file-name get-string-all)) #\newline)))
    (map (lambda (line)
       (let* ((colon-split (string-split line #\:))
             (segments (map string->number (string-split (cadr colon-split) #\,)))
             (label (string->number (car colon-split))))
        (cons label segments)))
       lines)))

(define-record-type fishbone-segment (fields middle left right))
(define (construct-fishbone fishbone segments)
  (if (null? segments)
      fishbone
      (let ((fishbone (add-fishbone-segment fishbone (car segments))))
        (construct-fishbone fishbone (cdr segments)))))
(define (add-fishbone-segment fishbone segment)
  (if (null? fishbone)
      (list (make-fishbone-segment segment #f #f))
      (let* ((fish-head (car fishbone))
             (fish-middle (fishbone-segment-middle fish-head))
             (fish-left (fishbone-segment-left fish-head))
             (fish-right (fishbone-segment-right fish-head)))
        (cond
          ((and (< segment fish-middle) (not fish-left)) (cons (make-fishbone-segment fish-middle segment fish-right) (cdr fishbone)))
          ((and (> segment fish-middle) (not fish-right)) (cons (make-fishbone-segment fish-middle fish-left segment) (cdr fishbone)))
          (else (cons fish-head (add-fishbone-segment (cdr fishbone) segment)))))))
(define (score-fishbone fishbone)
  (string->number (string-join (map (compose number->string fishbone-segment-middle) fishbone) "")))

(define-record-type sword (fields id fishbone quality))
(define (parse-swords file-name)
  (map (lambda (sword-line)
          (let ((fishbone (construct-fishbone '() (cdr sword-line))))
            (make-sword (car sword-line) fishbone (score-fishbone fishbone))))
    (parse-file file-name)))

(format #t "P1 Answer: ~a\n\n" (sword-quality (car (parse-swords "notes/everybody_codes_e2025_q05_p1.txt"))))

(let* ((swords (parse-swords "notes/everybody_codes_e2025_q05_p2.txt"))
       (sword-scores (map sword-quality swords)))
  (format #t "P2 Answer: ~a\n\n" (- (apply max sword-scores) (apply min sword-scores))))


(define (segment-score segment)
  (string->number
    (string-join
     (map (lambda (n) (if (eqv? #f n) "" (number->string n)))
      (list (fishbone-segment-left segment) (fishbone-segment-middle segment) (fishbone-segment-right segment)))
     "")))
(define (sort-fishbones a b)
  (if (null? a) '()
  (let ((line-score-a (segment-score (car a)))
        (line-score-b (segment-score (car b))))
    (cond
      ((> line-score-a line-score-b) #t)
      ((< line-score-a line-score-b) #f)
      (else (sort-fishbones (cdr a) (cdr b)))))))
(define (sort-swords a b)
  (cond
    ((> (sword-quality a) (sword-quality b)) #t)
    ((< (sword-quality a) (sword-quality b)) #f)
    (else (let ((fb-sort (sort-fishbones (sword-fishbone a) (sword-fishbone b))))
      (cond
        ((null? fb-sort) (> (sword-id a) (sword-id b)))
        (else fb-sort))))))
(let* ((swords (parse-swords "notes/everybody_codes_e2025_q05_p3.txt"))
       (sorted-swords (sort swords sort-swords))
       (swords-length (length swords)))
  (let loop ((i 1) (total 0) (sorted-swords sorted-swords))
    (if (<= i swords-length)
        (loop (1+ i) (+ total (* i (sword-id (car sorted-swords)))) (cdr sorted-swords))
        (format #t "P2 Answer: ~a\n\n" total))))
2

Uiua

[Works on test data, but fails on Part 3 live data: my checksum is "Correct length and correct first character". Posting for the record while I think about it more.] Turned out to be trailing zeros.

F ← (
  β†˜1βŠ™[0_0_0] # Create empty row
  ∧(
    ◑≑([βŠƒ(β†§βŠƒ(=0βŠ’β—Œ|>βŠ™βŠ‘β‚)|=0βŠ‘β‚β—Œ|β†§βŠƒ(=0βŠ£β—Œ|<βŠ™βŠ‘β‚))]) # Will it fit in each row?
    βŠŸβŠ™(⊒⊚)βŸœβŠ‘βŠ’βŠšβŠΈβ‰‘/β†₯                             # Find first non-zero row and col.
    (βœβŠ‘β‹…βˆ˜)βŠ™βŠƒβ‹…βˆ˜βˆ˜                                # insert
    β₯(ΛœβŠ‚0_0_0)¬≍0_0_0⊸⊣                        # Add padding row back
  )
  β†˜Β―1
)
S     ← β‹•/$"__"⊑1⍉
Part₁ ← S F
Partβ‚‚ ← -βŠƒ/↧/β†₯≑(S F)

Part₃ ← (
  ⬚0βŠΈβ‰‘(βŠ‚βŠƒ(S|≑(Β°βŠ₯β‚β‚€β–½βŠΈ>β‚€β‡Œ))F) # All sword stats
  β‰‘βŠ‚βŠ™(βŠ’β‰)             # Append sword IDs
  βŠβŠΈβ–                 # Sort
  /+Γ—+1Β°βŠβ‰‘βŠ£           # Checksum
)

Part₁ [58 5 3 7 8 9 10 4 5 7 8 8]
Partβ‚‚ [[2 7 9 9 3 8 3 8 8 6 8] [5 2 9 3 8 3 9 5 2 1 4]]
Part₃ [[1 7 1 9 1 6 9 8 3 7 2]
       [2 7 1 9 1 6 9 8 3 7 2]]
2

You reached the end

πŸ¦† Everybody.Codes 2025 Quest 5 Solutions πŸ¦† | Spyke