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/
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]]Does anybody miss old-school Lisp mutable data structures where you’re kinda a functional language but you still have to worry about the difference between values and object identity? I’m not sure anybody does, but this is a retrocomputing house.
(ql:quickload :str) (defun parse-line (line) (let* ((id-and-nums (str:split ":" line)) (id (parse-integer (car id-and-nums))) (nums (mapcar #'parse-integer (str:split "," (cadr id-and-nums))))) (cons id nums))) (defun read-inputs (filename) (mapcar #'parse-line (uiop:read-file-lines filename))) (defun fishbone-push (fb n) (if (null fb) (list (vector nil n nil)) (let ((rib (car fb))) (cond ((and (null (aref rib 0)) (< n (aref rib 1))) (setf (aref rib 0) n) fb) ((and (null (aref rib 2)) (> n (aref rib 1))) (setf (aref rib 2) n) fb) ((null (cdr fb)) (nconc fb (fishbone-push (cdr fb) n))) (t (fishbone-push (cdr fb) n) fb))))) (defun make-fishbone (ns) (reduce #'fishbone-push ns :initial-value nil)) (defun quality (ns) (let ((fb (make-fishbone ns))) (parse-integer (apply #'str:concat (mapcar #'(lambda (rib) (write-to-string (aref rib 1))) fb))))) (defun main-1 (filename) (quality (cdar (read-inputs filename)))) (defun main-2 (filename) (let ((qualities (mapcar #'quality (mapcar #'cdr (read-inputs filename))))) (- (apply #'max qualities) (apply #'min qualities)))) (defun complex-quality (idx ns) (let* ((fb (make-fishbone ns)) (quality (parse-integer (apply #'str:concat (mapcar #'(lambda (rib) (write-to-string (aref rib 1))) fb)))) (rib-qualities (mapcar #'(lambda (rib) (parse-integer (apply #'str:concat (mapcar #'write-to-string (remove-if #'null (coerce rib 'list)))))) fb))) (list (cons :quality quality) (cons :rib-qualities rib-qualities) (cons :index idx)))) (defun list> (ns1 ns2) (cond ((null ns1) nil) ((null ns2) t) ((> (car ns1) (car ns2)) t) ((< (car ns1) (car ns2)) nil) (t (list> (cdr ns1) (cdr ns2))))) (defun cq> (cq1 cq2) (let ((q1 (cdr (assoc :quality cq1))) (q2 (cdr (assoc :quality cq2)))) (cond ((> q1 q2) t) ((< q1 q2) nil) (t (let ((rq1 (cdr (assoc :rib-qualities cq1))) (rq2 (cdr (assoc :rib-qualities cq2)))) (cond ((list> rq1 rq2) t) ((list> rq2 rq1) nil) (t (> (cdr (assoc :index cq1)) (cdr (assoc :index cq2)))))))))) (defun checksum (idxs) (loop for idx in idxs for n from 1 to (length idxs) sum (* idx n))) (defun main-3 (filename) (let ((inputs (read-inputs filename)) (sword-qualities (make-hash-table))) (loop for idx-ns in inputs do (setf (gethash (car idx-ns) sword-qualities) (complex-quality (car idx-ns) (cdr idx-ns)))) (let ((sorted-idxs (sort (mapcar #'car inputs) #'(lambda (idx1 idx2) (cq> (gethash idx1 sword-qualities) (gethash idx2 sword-qualities)))))) (checksum sorted-idxs))))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))))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() }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 . readInputNim
Nothing fancy. Simple iterative tree construction and sort, using the
std/algorithmand 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) * idFull solution at Codeberg: solution.nim





