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/

  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    3 days ago

    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]]
    
  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    2
    ·
    3 days ago

    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))))
    
  • vole@lemmy.world
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    4 days ago

    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))))
    
  • hades@programming.devOPM
    link
    fedilink
    arrow-up
    3
    ·
    8 days ago

    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()
    }
    
  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    3
    ·
    8 days ago

    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  
    
  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    3
    ·
    8 days ago

    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