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/
You must log in or register to comment.
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() }Nim
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
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 . readInputDoes 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))))



