Rules: no spoilers.

The other rules are made up as we go along.

Share code by link to a forge, home page, pastebin (Eric Wastl has one here) or code section in a comment.

      • froztbyte@awful.systems
        link
        fedilink
        English
        arrow-up
        4
        ·
        1 year ago

        honestly I considered writing a parser-based approach

        then I was too tired and thought “hmm, doing this with grok would be funny”, but I didn’t have logstash handy and fuck dealing with containers at midnight

        I will, however, do that today

        • froztbyte@awful.systems
          link
          fedilink
          English
          arrow-up
          4
          ·
          1 year ago

          Status report: grok allows for non-greedy matching but still captures greedily for term assignment. I think I have a workaround that might work (recursively pipe data back to itself, gated on length for action), need to test later

          This particular flavour of parsecrime is near guaranteed to be of interest to very few, but I want to see if I can make it work nonetheless. That’s just how my brainworms work.

  • 200fifty@awful.systems
    link
    fedilink
    English
    arrow-up
    7
    ·
    edit-2
    1 year ago

    day 1

    part 1

    perl
    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use 5.010;
    
    my $total = 0;
    
    for my $line (<>) {
        my @nums = ($line =~ /\d/g);
        $total += $nums[0] * 10 + $nums[-1];
    }
    
    say $total;
    

    part 2

    perl
    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use v5.010;
    
    my %nums = (one => 1, two => 2, three => 3, four => 4, five => 5, six => 6, seven => 7, eight => 8, nine => 9);
    $nums{$_} = $_ for 1..9;
    
    my $regex = join "|", keys %nums;
    
    my $total = 0;
    
    for my $line (<>) {
        $line =~ /($regex)/;
        my $first_num = $nums{$1};
    
        my $window = 1;
        my $sub = substr $line, -1;
        while ($sub !~ /($regex)/) {
            $window ++;
            $sub = substr $line, -$window;
        }
    
        $sub =~ /($regex)/;
        my $second_num = $nums{$1};
    
        $total += $first_num * 10 + $second_num;
    }
    
    say $total;
    

    Part 2 gave me a surprising amount of trouble. I resolved it by looking at longer and longer substrings from the end of the line in order to find the very last word even if it overlapped, which you can’t do with normal regex split. I doubt this is the most efficient possible solution.

    Also Lemmy is eating my < characters inside code blocks, which seems wrong. Pretend the “&lt;>” part says “<>”, lol

    • 200fifty@awful.systems
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago

      day 2

      perl
      #!/usr/bin/env perl
      
      use strict;
      use warnings;
      use v5.010;
      use List::Util qw/ max /;
      
      # Parse the input
      
      my %games = ();
      
      for my $line (&lt;>) {
          $line =~ /Game (\d+): (.+)/;
          my $game_id = $1;
          my $game_str = $2;
      
          my @segments = split '; ', $game_str;
          my @game = ();
          for my $segment (@segments) {
              my @counts = split ', ', $segment;
      
              my %colors = (red => 0, blue => 0, green => 0);
              for my $count (@counts) {
                  $count =~ /(\d+) (\w+)/;
                  $colors{$2} = $1;
              }
      
              push @game, { %colors };
          }
      
          $games{$game_id} = [ @game ];
      }
      
      # Part 1
      
      my $part1 = 0;
      
      game: for my $game_id (keys %games) {
          for my $segment (@{$games{$game_id}}) {
              next game if $segment->{red} > 12 || $segment->{green} > 13 || $segment->{blue} > 14;
          }
      
          $part1 += $game_id;
      }
      
      say "Part 1: $part1";
      
      # Part 2
      
      my $part2 = 0;
      
      for my $game (values %games) {
          my ($red, $green, $blue) = (0, 0, 0);
      
          for my $segment (@$game) {
              $red = max $segment->{red}, $red;
              $green = max $segment->{green}, $green;
              $blue = max $segment->{blue}, $blue;
          }
      
          $part2 += $red * $green * $blue;
      }
      
      say "Part 2: $part2";
      

      Found this much easier than day 1 honestly…

  • datarama@awful.systems
    link
    fedilink
    English
    arrow-up
    5
    ·
    edit-2
    1 year ago

    Day 4: Scratchcards

    Scheme code and explanations

    This problem feels a bit more Scheme-shaped. To keep things interesting, I have chosen to stick to pure functional programming.

    A card consists of three components – a card number (which turned out to be completely unnecessary), a list of winning numbers, and a list of numbers. Here’s a function to parse a card from a string, turning it into a 3-element list:

    (define (parse-card card)
      (let* ((parts (string-split card ":"))
             (numlists (string-split (cadr parts) "|")))
        (list (string->number (string-drop (car parts) 5))
              (map string->number (string-split (car numlists) " "))
              (map string->number (string-split (cadr numlists) " "))
    

    The value of a card is easy enough to compute. The first matched winning number sets the value to 1, every subsequent one doubles it.

    (define (card-value card)
      (let* ((card-parts (parse-card card))
             (winning-numbers (cadr card-parts)))
        (let loop ((nums (caddr card-parts)) (val 0))
          (cond ((null? nums) val)
                ((and (= val 0) (memq (car nums) winning-numbers)) (loop (cdr nums) 1))
                ((memq (car nums) winning-numbers) (loop (cdr nums) (* val 2)))
                (else (loop (cdr nums) val))))))
    

    Solving part 1 is now straightforward:

    (define (solve-part-1 cards)
      (apply + (map card-value cards)))
    

    But oh no! Turns out I (or the elf) got the rules wrong, and apparently the game requires so many dead trees to play that I think it was probably designed by Jair Bolsonaro.

    A function to count the card matches (eg. how many numbers match a winning number) is easily adapted from the above:

    (define (card-matches card)
      (let* ((card-parts (parse-card card))
             (winning-numbers (cadr card-parts)))
        (let loop ((nums (caddr card-parts)) (val 0))
          (cond ((null? nums) val)
                ((memq (car nums) winning-numbers) (loop (cdr nums) (+ val 1)))
                (else (loop (cdr nums) val))))))
    

    My strategy here is to have two lists: One of the card matches of all cards, the other of how many instances I have of each card. The second list will start as (1 1 1 1 ... ), and I need to be able to add some number x to the first n cards in the list. For example, if I have 4 instances of the first card, and it has 2 matches, then I’ll want to add 4 to however many instances I have of the next 2 cards. Here’s a function that reaches into a list and does just that:

    (define (reach x n l)
      (if (or (= n 0) (null? l))
        l
        (cons (+ x (car l)) (reach x (- n 1) (cdr l)))))
    

    Using this, here’s a recursive function that counts cards, given the two aforementioned lists:

    (define (count-cards matches cards)
      (if (null? cards) 0
        (+ (car cards)
           (count-cards (cdr matches)
                        (reach (car cards) (car matches) (cdr cards))))))
    

    Solving part 2 is now easy:

    (define (solve-part-2 lines)
      (count-cards (map card-matches lines) (map (lambda (x) 1) lines)))
    

    There you have it. It’d have been shorter if I had allowed myself some side effects, but pure functional programming is terrific fun.

  • datarama@awful.systems
    link
    fedilink
    English
    arrow-up
    5
    ·
    1 year ago

    Day 3: Gear Ratios

    Scheme code and explanations

    To avoid annoying special cases for dealing with reading past the edges of the schematic, I start by adding one cell of padding on all sides.

    (define (pad-schematic lines)
      (let* ((width (string-length (car lines)))
             (empty-line (make-string (+ width 2) #\.)))
        (append (list empty-line)
                (map (lambda (str) (string-append "." str ".")) lines)
                (list empty-line))))
    

    I’m also going to define a few utility functions. The schematic-pos function reads what’s on the x,y position of the schematic, and is here to compensate for Scheme being awkward for dealing with two-dimensional structures. The two others check whether some position is a symbol (other than .) or a digit.

    (define (schematic-pos s x y)
      (string-ref (list-ref s y) x))
    
    (define (pos-symbol? s x y)
      (let ((chr (schematic-pos s x y)))
        (and (not (char=? chr #\.))
             (not (char-set-contains? char-set:digit chr)))))
    
    (define (pos-digit? s x y)
      (let ((chr (schematic-pos s x y)))
        (char-set-contains? char-set:digit chr)))
    

    I want a list of all positions of symbols in the schematic, so I can check them for adjacent numbers. I could also have gone with a list of numbers instead, but symbols seemed easier to find adjacencies for.

    (define (symbol-positions s)
      (let ((width (string-length (car s)))  (height (length s)))
        (let loop ((x 0) (y 0) (syms '()))
          (cond ((>= y height) syms)
                ((>= x width) (loop 0 (+ y 1) syms))
                ((pos-symbol? s x y)
                 (loop (+ x 1) y (cons (cons x y) syms)))
                (else (loop (+ x 1) y syms))))))
    

    This produces a list of (x y) pairs, one for each symbol. Finding adjacent numbers is a bit tricky: Since there are three relevant positions above and below the symbol’s own position, there could be one or two numbers above or below, and if there are two, then I might be reading the same number twice. I accept this, and keep the numbers in an lset (which means that a duplicate simply won’t be inserted). The number reader itself produces triples on the form (x y n), where x is the position of the first character in the number, and n is a string containing the number itself. The coordinates are stored to ensure that if there are several instances of the same number in different positions in the schematic, they get treated separately.

    (define (get-number-pos s x y)
      (letrec
        ((num_l (lambda (x) (if (pos-digit? s x y) (num_l (- x 1)) (+ x 1))))
         (num_r (lambda (x) (if (pos-digit? s x y) (num_r (+ x 1)) x))))
        (list (num_l x) y (string-copy (list-ref s y) (num_l x) (num_r x)))))
    
    (define (get-adjacent-numbers s xpos ypos)
      (let loop ((x (- xpos 1)) (y (- ypos 1)) (nums '()))
        (cond
          ((> y (+ ypos 1)) nums)
          ((> x (+ xpos 1)) (loop (- xpos 1) (+ y 1) nums))
          ((pos-digit? s x y)
           (loop (+ x 1) y (lset-adjoin equal? nums (get-number-pos s x y))))
          (else (loop (+ x 1) y nums)))))
    

    From here, solving the two parts is easy. For part 1, we get all the lists of number adjacencies and unpack them into a single list:

    (define (get-part-numbers s)
      (let ((sym-nums (map (lambda (c) (get-adjacent-numbers s (car c) (cdr c)))
                           (symbol-positions s))))
        (let loop ((curr (car sym-nums)) (syms (cdr sym-nums)) (nums '()))
          (cond ((and (null? curr) (null? syms)) nums)
                ((null? curr) (loop (car syms) (cdr syms) nums))
                (else (loop (cdr curr) syms (cons (car curr) nums)))))))
    
    (define (solve-part-1 schematic)
      (apply + (map (lambda (x) (string->number (caddr x)))
                    (get-part-numbers schematic))))
    

    For part 2, we need a list of gears. We can just get all the symbols, filter out everything that isn’t a *, and then filter out everything that doesn’t have exactly two adjacencies.

    (define (gears s)
      (filter (lambda (x) (= (length x) 2))
              (map (lambda (x) (get-adjacent-numbers s (car x) (cdr x)))
                   (filter
                     (lambda (x) (char=? (schematic-pos s (car x) (cdr x)) #\*))
                     (symbol-positions s)))))
    
    (define (solve-part-2 s)
      (apply + (map (lambda (y) (* (string->number (caddr (car y)))
                                   (string->number (caddr (cadr y)))))
                    (gears s))))
    

    I could probably have written nicer code if I weren’t still a bit sick (though I’m getting better!). However, the moment I realized this one would be about mucking around with two-dimensional structures, I knew Scheme was going to be a bit awkward. But here you have it.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago
      1 a,b:

      as a professional software engineer, I know that googling regex syntax and getting it right will take longer than manually writing string comparisons, so… here’s all you need to see of my final solution.

        int? check3(String line, int index) {
          if (line.length &lt; index + 3) {
            return null;
          }
      
          String sub = line.substring(index, index + 3);
          return sub == "one"
              ? 1
              : sub == "two"
                  ? 2
                  : sub == "six"
                      ? 6
                      : null;
        }
      
      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        4
        ·
        1 year ago

        I’ll be honest: so far, Dart is pretty rubbish for this kind of exercise for the simple reason that their Strings aren’t just arrays of chars. There’s no native isDigit, for example. Otherwise, I’ve been using it with Flutter and have been happy with my experience.

        I’m only posting code when I think I’ve done something interesting or if there’s a notable language feature to show off, but so far, no dice.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      1 year ago
      3 a

      I read this wrong initially and thought that you only needed to check adjacency on the same line. Whoops! Then I wrote a bad algorithm that finds numbers THEN searches for symbols. That alg isn’t inherently bad, except…

      3 b

      If I had chosen a symbol first approach, I would not have had as much pain as I did here. Also, I probably under and overthought this one. I went with my first idea, which was guaranteed to work.

      The approach this time was:

      1. iterate through the characters to find a * symbol
      2. Search the characters around it for a digit.
      3. Get the value of the number associated with that digit by searching backwards until you find the start of a number
      4. Use union-find to track whether or not you’ve seen this number before (because you can’t assume that the same value is the same number)

      A simpler approach would consider that you only have two numbers on the same line for the same gear if the character in the gear column is a non-digit; otherwise, if a number is adjacent to a gear, there is only one on that row. Union-find is completely overkill, but I like using it even when I don’t need to.

      Anyway, upon reflecting on this, while the general approach is fine, I didn’t think too hard about the implementation and just ended up with globs of overly memoized spaghetti. I probably should check if Dart has a python-like tuple object or similar. Whatever. Behold!

      void day3s() {
        List lines = [
          for (String? line = stdin.readLineSync();
              line != null;
              line = stdin.readLineSync())
            line
        ];
      
        List> digs = [for (int i = 0; i &lt; lines.length; i++) Map()];
        int? isDigitMem(int r, int c) {
          return digs[r].putIfAbsent(c, () => isDigit(lines[r][c]));
        }
      
        // first entry is parentc, second is size
        List>> uf = List.generate(
            lines.length, (i) => List.generate(lines[0].length, (j) => [j, 1, -1]));
      
        int find(int r, int c) {
          if (uf[r][c][0] != c) {
            uf[r][c][0] = find(r, uf[r][c][0]);
            return uf[r][c][0];
          }
          return uf[r][c][0];
        }
      
        void union(int r, int cp, int c) {
          cp = find(r, cp);
          c = find(r, c);
      
          if (c == cp) {
            return;
          }
      
          if (uf[r][cp][1] >= uf[r][c][1]) {
            uf[r][c][0] = cp;
            uf[r][cp][1] += uf[r][c][1];
          } else {
            uf[r][cp][0] = c;
            uf[r][c][1] += uf[r][cp][1];
          }
        }
      
        int stoi(int row, int col) {
          int acc = 0;
          for (int i = col; i &lt; lines[0].length; i++) {
            int? d = isDigitMem(row, i);
            if (d != null) {
              acc = (acc * 10) + d;
              union(row, col, i);
            } else {
              break;
            }
          }
          return acc;
        }
      
        int? stoiSearch(int row, int col) {
          assert(row >= 0 &amp;&amp; col >= 0 &amp;&amp; row &lt; lines.length &amp;&amp; col &lt; lines[0].length);
          if (isDigitMem(row, col) == null) {
            return null;
          }
          int i = col - 1;
          while (i >= 0) {
            if (isDigitMem(row, i) == null) {
              return stoi(row, i + 1);
            }
            i--;
          }
          return stoi(row, 0);
        }
      
        List> s2i = [for (int i = 0; i &lt; lines.length; i++) Map()];
        int? stoiSearchMem(int row, int col) {
          return s2i[row].putIfAbsent(col, () => stoiSearch(row, col));
        }
      
        int count = 0;
        for (int i = 0; i &lt; lines.length; i++) {
          for (int j = 0; j &lt; lines[0].length; j++) {
            if (lines[i][j] != "*") {
              continue;
            }
      
            List gearVals = List.empty(growable: true);
            for (int x = -1; x &lt;= 1; x++) {
              if (i + x &lt; 0 || i + x > lines.length) {
                continue;
              }
      
              Set parents = {};
              for (int y = -1; y &lt;= 1; y++) {
                if (j + y &lt; 0 || j + y > lines[0].length) {
                  continue;
                }
      
                int? cur = stoiSearchMem(i + x, j + y);
      
                int parent = find(i + x, j + y);
                if (parents.contains(parent)) {
                  continue;
                }
      
                parents.add(parent);
      
                if (cur != null) {
                  gearVals.add(cur);
                }
              }
            }
      
            if (gearVals.length == 2) {
              count += gearVals[0] * gearVals[1];
            }
          }
        }
      
        print(count);
      }
      
      • swlabr@awful.systems
        link
        fedilink
        English
        arrow-up
        3
        ·
        edit-2
        1 year ago
        3b redux

        I took out the union find, the code is simpler and more readable now. I also leaned in to using null values, which is gross but whatever, it works.

        void day3s() {
          List lines = [
            for (String? line = stdin.readLineSync();
                line != null;
                line = stdin.readLineSync())
              line
          ];
        
          // lazy processing + memoization
          List> digs = [for (int i = 0; i &lt; lines.length; i++) Map()];
          int? isDigitMem(int r, int c) {
            if (r &lt; 0 || r > lines.length || c &lt; 0 || c > lines[0].length) return null;
            return digs[r].putIfAbsent(c, () => isDigit(lines[r][c]));
          }
        
          int stoi(int row, int col) {
            int acc = 0;
            for (int i = col; i &lt; lines[0].length; i++) {
              int? d = isDigitMem(row, i);
              if (d != null) {
                acc = (acc * 10) + d;
              } else {
                break;
              }
            }
            return acc;
          }
        
          int? stoiSearch(int row, int col) {
            assert(row >= 0 &amp;&amp; col >= 0 &amp;&amp; row &lt; lines.length &amp;&amp; col &lt; lines[0].length);
            if (isDigitMem(row, col) == null) {
              return null;
            }
            int i = col - 1;
            while (i >= 0) {
              if (isDigitMem(row, i) == null) {
                return stoi(row, i + 1);
              }
              i--;
            }
            return stoi(row, 0);
          }
        
          List> s2i = [for (int i = 0; i &lt; lines.length; i++) Map()];
          int? stoiSearchMem(int row, int col) {
            if (row &lt; 0 || row >= lines.length) return null;
            if (col &lt; 0 || col >= lines[0].length) return null;
            return s2i[row].putIfAbsent(col, () => stoiSearch(row, col));
          }
        
          int count = 0;
          for (int i = 0; i &lt; lines.length; i++) {
            for (int j = 0; j &lt; lines[0].length; j++) {
              if (lines[i][j] != "*") {
                continue;
              }
        
              List gearVals = List.empty(growable: true);
              for (int x = -1; x &lt;= 1; x++) {
                int? left = stoiSearchMem(i + x, j - 1);
                int? mid = stoiSearchMem(i + x, j);
                int? right = stoiSearchMem(i + x, j + 1);
        
                if (isDigitMem(i + x, j) == null) {
                  if (left != null) {
                    gearVals.add(left);
                  }
        
                  if (right != null) {
                    gearVals.add(right);
                  }
                } else if (left != null) {
                  gearVals.add(left);
                } else if (mid != null) {
                  gearVals.add(mid);
                } else if (right != null) {
                  gearVals.add(right);
                }
              }
        
              if (gearVals.length == 2) {
                count += gearVals[0] * gearVals[1];
              }
            }
          }
        
          print(count);
        }
        
    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      edit-2
      1 year ago
      2 a,b:

      This one is just a test of whether or not your language lets you split strings.

      Only novel thing I did here was recognise that red, green and blue end in different letters, so no need to string match the whole world, just check the last letter of the colour.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      1 year ago
      4 a

      Not so bad today. I bit the bullet and tried to see if dart has tuples or similar. It does, by the name of “records”. Now instead of pretending I’m writing in C/C++, I can pretend I’m writing in python.

      Anyway, a) is a pretty straightforward job-interview style question, except AoC doesn’t care about efficiency. Still, we all have our own versions of pride, so I did it with a set (Though whether or not dart’s native Set is tree or hash based is not known to me right now).

      code
      int matches(String line) {
        ({List wn, List n}) card = getNumbers(line);
        Set wn = Set.from(card.wn);
      
        return card.n.fold(0, (prev, e) => prev + (wn.contains(e) ? 1 : 0));
      }
      
      void day4a(List lines) {
        print(lines.fold(0, (acc, line) {
          int m = matches(line);
          return acc + (m != 0 ? (1 &lt;&lt; (m - 1)) : 0);
        }));
      }
      
      4b

      b) was a little harder, and definitely a possible trap for overthinking. I think the easiest way to think about solving this is as if it is a dynamic programming problem (though it kinda isn’t).

      So the general approach is to formulate it like this:

      T_n(total cards won by card n) = M_n(total matches for card n) + CW_n(total cards won by the copies that card n wins).

      and

      CW_n =

      • 0 if M_n = 0
      • sum of T_i, where i = n + 1 … n + M_n

      Caching T_n is the DP trick to making this performant (though once again, it does not need to be)

      Anyway, the above approach is the top-down version of the DP; the bottom-up version is what I actually started with in my head. I gave up on that approach because I felt like the logic was too hard for me to figure out.

      code:
      void day4b(List lines) {
        List totalMatches = lines.map((e) => matches(e)).toList();
      
        // total copies won, including copies won from copies.
        List cachedWins = List.filled(totalMatches.length, -1);
        int totalWins(int i) {
          // return cached result if it exists
          if (cachedWins[i] > 0) {
            return cachedWins[i];
          }
      
          int count = totalMatches[i];
          // count the copies won from the subsequent copied cards
          for (int j = 1; j &lt;= totalMatches[i]; j++) {
            count += totalWins(i + j);
          }
      
          // cache the result
          cachedWins[i] = count;
          return count;
        }
      
        int totalCards = totalMatches.length; // count all the originals
        // count all the wins
        for (int i = 0; i &lt; totalMatches.length; i++) {
          totalCards += totalWins(i);
        }
        print(totalCards);
      }
      
    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      1 year ago
      1. Not my best work. No code in this comment, just check 5.dart if you want to see it in the github repo linked above.
      General

      I used a class this time because it looked like it might be helpful. I don’t think it turned out to be that useful. Still, you can see Dart’s interesting constructor and getter syntax on display.

      a.

      Pretty straightforward, though I didn’t read the format correctly and had the destination/source data reversed. Oops! Luckily, my performance here will in no way affect my future career.

      b.

      I didn’t read the prompt correctly, which tripped me up. Also, while my solution is correct, it assumes that the input could be trickier than what the problem threw at me. Specifically, the edge case I had in mind was a range of values split into many subintervals and needing to track those mappings. I threw in some print statements to discover that intervals were never split beyond two subintervals, which was disappointing. Oh well- being correct is the best feeling you can have if you are otherwise empty inside.

      Other than processing the input in the form of intervals, I don’t think there were any notable tricks at play here, so this was more of an exercise in implementing code cleanly, which I struggle with.

    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      2
      ·
      edit-2
      1 year ago
      6 a,b

      Very easy today. You don’t need any programming to solve this; it’s a series of inequalities. That said, the brute force calculation is fast enough if you don’t want to think and is quite honestly faster than solving this mathematically.

      So that being said, here’s the mathematic solution:

      The inequality to solve is just: x = time holding the button

      x^2 - tx + d < 0

      which occurs when x is between (t/2) +/- (t^2 - 4d). The total ways are the total integers between those two numbers, exclusive.

      Writing that all in code is a little tricky thanks to int/double rounding/truncation intricacies, so the brute force solution is “better”.

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago

      Perl: https://github.com/gustafe/aoc2023/blob/main/d01-Trebuchet.pl

      thoughts

      I found this really tough for a day 1 problem. Because I don’t really know regex, I did not know the secret to finding overlapping patterns. I quickly figured out that “twone” was a possibility, because it was in the example, but then I had to find other examples and hardcode them into my split pattern.

      This isn’t general, my input doesn’t have ‘sevenine’ for example.

      • datarama@awful.systems
        link
        fedilink
        English
        arrow-up
        4
        ·
        1 year ago

        I don’t have a Github account and don’t want one. I’d like to share some comments and thoughts about the regexes in my solution - is it OK if I wrap them in spoiler tags?

        • gerikson@awful.systemsOP
          link
          fedilink
          English
          arrow-up
          4
          ·
          1 year ago

          Abso-fucking-lutly. I just find sharing a link easier than wrangling code blocks.

          There are also hipster “forges” like Sourcehut, if that’s your jam.

        • self@awful.systemsM
          link
          fedilink
          English
          arrow-up
          3
          ·
          1 year ago

          absolutely — removing my dependency on GitHub has actually been a blocker on me releasing code lately, and it’s something I want to tackle when we launch that open source community. if it helps collaboration, I can provide some ultra-janky git hosting on awful.systems with the same service that hosts our infrastructure code, though this’d be just basic git and gitweb with ssh public key auth

          • datarama@awful.systems
            link
            fedilink
            English
            arrow-up
            4
            ·
            edit-2
            1 year ago

            I made one solution in Python, and another in Scheme. Here are the regex-related parts.

            Python:

            spoiler

            Here is a regex:·

            rx = "(?=(one|two|three|four|five|six|seven|eight|nine|1|2|3|4|5|6|7|8|9))"
            

            The leading ?= is a lookahead assertion. If we can read any of the subexpressions one,two,…, it produces the empty string as a match, with the actual matched string as a capturing group. Python’s re module has a findall function which finds all non-overlapping matches in a string … but since it’s the matches (not the capturing groups) that have to be non-overlapping, and lookahead produces empty strings as matches, we have re.findall(rx,"twone") == ["two","one"] and re.findall(rx,"sevenineight") == ["seven","nine","eight"]. Empty string doesn’t overlap!

            So here’s the core of the solution:

            def solve(s):
                m = [numeralize(t) for t in re.findall(rx, s)]
                return int(m[0] + m[len(m)-1])
            

            Where numeralize is a simple function that converts “eight” into “8”. Not to the number 8, since we want to do string concatenation on them rather than addition. :-)

            I haven’t written any Perl in ages, so I have no idea if Perl has an analogue to findall.


            Scheme:

            spoiler

            …but I do know that Scheme, at least CHICKEN, doesn’t. Here is the best my currently badly fever-addled brain could come up with:

            I’m using the irregex library. I start by building an irregex out of a SRE (Scheme Regular Expression):

            (define ir (sre->irregex '(seq bos (or "one" "two" "three" "four" "five" "six" "seven" "eight" "nine"·
                                                   "1" "2" "3" "4" "5" "6" "7" "8" "9"))))
            

            This matches from the beginning of the string (hence bos), and doesn’t use any fancy lookahead. It only matches one string.

            Here is a little recursive function that finds all matches using the above:

            (define (all-matches s)
              (if (= (string-length s) 0)
                '()
                (let ((matches (irregex-search ir s)))
                  (if (not matches)
                    (all-matches (string-drop s 1))
                    (cons (irregex-match-substring matches)
                          (all-matches (string-drop s 1)))))))
            

            It simply tries making a match from the beginning, and then continues making one from a substring with the first character hacked off, until it ends up with the empty string. Not exactly my proudest Scheme code, but it works.

            Using that, the analogue of the solution I did for the Python version is:

            (define (solve s)
              (let ((m (all-matches s)))
                (string->number
                  (string-append (numeralize (list-ref m 0))
                                 (numeralize (list-ref m (- (length m) 1)))))))
            

            Again, not my proudest Scheme code! I am currently nursing a fever and trying not to sneeze and cough my lungs to pieces.

            • gerikson@awful.systemsOP
              link
              fedilink
              English
              arrow-up
              4
              ·
              1 year ago

              Re Perl findall, I used this regex in my “clean” solution which I didn’t post because I figure it’s more honest to submit what worked, not the smart stuff you found out later.

              regex

              # find all numerals and number in English from the string $line and put them into the array @spelled my @spelled = ( $line =~ (m/(?=(\d{1}|one|two|three|four|five|six|seven|eight|nine))/g );

            • self@awful.systemsM
              link
              fedilink
              English
              arrow-up
              4
              ·
              1 year ago

              fuck yes scheme. you might have just inspired me to write some Lisp Machine Lisp solutions, since I might need a Lisp Machine codebase to test one of my side projects

              • datarama@awful.systems
                link
                fedilink
                English
                arrow-up
                4
                ·
                1 year ago

                I have a toy Lisp implemenation I used to work on, but I’ve lost the motivation.

                Not that I think one more little virtual machine project makes a material difference either way, but I really don’t feel like spending my free time writing nontrivial code for loathsome AI companies.

  • zogwarg@awful.systems
    link
    fedilink
    English
    arrow-up
    4
    ·
    1 year ago

    Have been mostly using jq for fun.

    Day 1

    Part 1
    #!/usr/bin/env jq -n -R -f
    
    # Get and reduce every "pretty" line
    reduce inputs as $line (
      0;
      # Add extracted number
      . + ( $line / "" | [ .[] | tonumber? ] | [first * 10 , last] | add )
    )
    

    First part was easy, and very suited to jq

    Part 2
    #!/usr/bin/env jq -n -R -f
    
    # Define string to num value map
    {
      "one":   1,  "1": 1,
      "two":   2,  "2": 2,
      "three": 3,  "3": 3,
      "four":  4,  "4": 4,
      "five":  5,  "5": 5,
      "six":   6,  "6": 6,
      "seven": 7,  "7": 7,
      "eight": 8,  "8": 8,
      "nine":  9,  "9": 9
    } as $to_num |
    
    # Get and reduce every "pretty" line
    reduce inputs as $line (
      0;
      . + (
        $line |
        # Try two capture two numbers
        capture("(^.*?(?(one|two|three|four|five|six|seven|eight|nine|[1-9])).*(?(one|two|three|four|five|six|seven|eight|nine|[1-9])).*?$)?") |
        # If no capture, get one number twice
        if .f == "" then $line | capture("^.*?(?(one|two|three|four|five|six|seven|eight|nine|[1-9]))") | .l = .f else . end |
        # Add extracted number
        $to_num[.f] * 10 + $to_num[.l]
      )
    )
    

    Second part was harder than expected, i had to resort to regex.

    Day 2

    Part 1
    #!/usr/bin/env jq -n -R -f
    
    # For each game: Is 12 red cubes, 13 green cubes, and 14 blue cubes possible ?
    # Line Format =
    # Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
    [
      # Splitting input game id and content
      inputs / ": " |
      # Saving id
      (.[0] / " " | .[1] | tonumber ) as $id |
      # Parsing game
      .[1] / "; " | [
        .[] / ", " | [ .[] / " " | {(.[1]): .[0] | tonumber} ] | add |
        # Is given sample possible ?
        .red &lt;= 12 and .green &lt;= 13 and .blue &lt;= 14
      ] |
      # If all samples possible, return id, else 0
      if all then $id else 0 end
    ] |
    
    # Return sum of all possible game ids
    add
    

    Not too much trickery in this example.

    Part 2
    #!/usr/bin/env jq -n -R -f
    
    # Line Format =
    # Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
    [
      # Splitting input game id and content
      inputs / ": " |
      # Parsing game
      .[1] / "; " |
        [ .[] / ", " | [ .[] / " " | {(.[1]): .[0] | tonumber} ] | add ] |
        # Getting minimum required mumber for each color,
        # and computing the power
        {
          r: ([.[].red]   | max),
          g: ([.[].green] | max),
          b: ([.[].blue]  | max)
        } | .r * .g * .b
    ] |
    
    # Return sum of all powers
    add
    

    Satisifyingly straightfoward edit form part one.

    Day 3

    Part 1
    #!/usr/bin/env jq -n -R -f
    
    # Getting input with padding, and padded width
    [ "." + inputs + "." ] as $inputs | ( $inputs[0] | length ) as $w |
    
    # Working with flattened string, convert all symbols to '#'
    [
      ([range($w) | "."]|join("")), # Padding
      $inputs[],
      ([range($w) | "."]|join(""))  # Padding
    ] | join("") | gsub("[^0-9.]";"#") as $inputs |
    
    reduce (
      # Get all indices for symbols, in box pattern around symbols
      $inputs | indices("#")[] |
      . - $w -1  , . - $w , . - $w + 1 ,
      . - 1      , empty  , . + 1      ,
      . + $w - 1 , . + $w , . + $w + 1
    ) as $i (
      # Numbers containes bounding indices,
      # of numbers bordering symbols
      {numbers: []};
    
      # Test if current index isn't included in any found number
      def new_number($i): [ .numbers[] | .[0] &lt;= $i and $i &lt;= .[1] ] | any | not ;
      # Make "number" as bounding indices, by extending left and right
      def make_number($i):
        {a: $i, b: ($i+1 )}
          | until( $inputs[.a:.b] | test("^[^0-9]"); .a -= 1 )
          | until( $inputs[.a:.b] | test("[^0-9]$"); .b += 1 )
          | [ .a +1 , .b -1 ]
      ;
    
      # Add numbers if bordering symbol and new
      if ($inputs[$i:$i+1] | test("[0-9]")) and new_number($i) then .numbers += [ make_number($i) ] else . end
    ) |
    
    # Output sum of all found numbers
    [ .numbers[] | $inputs[.[0]:.[1]] | tonumber ] | add
    

    Took More time than i expected, glad i had the idea early to search by the indices of the symbols and not the digits. Not super well suited to jq, unless I’m missing a better solution.

    Part 2
    #!/usr/bin/env jq -n -R -f
    
    # Getting input with padding, and padded width
    [ "." + inputs + "." ] as $inputs | ( $inputs[0] | length ) as $w |
    
    # Working with flattened string, only keep gear '*' symbols
    [
      ([range($w) | "."]|join("")), # Padding
      $inputs[],
      ([range($w) | "."]|join(""))  # Padding
    ] | join("") | gsub("[^0-9*]";".") as $inputs |
    
    # Iterate over index positions of all gears
    reduce ($inputs | indices("*")[]) as $i (
      0;
      # Re-use part-1 functions
      def new_number($i):
        [ .numbers[] | .[0] &lt;= $i and $i &lt;= .[1] ] | any | not
      ;
      def make_number($i):
        {a: $i, b: ($i+1 )}
          | until( $inputs[.a:.b] | test("^[^0-9]"); .a -= 1 )
          | until( $inputs[.a:.b] | test("[^0-9]$"); .b += 1 )
          | [ .a +1 , .b -1 ]
      ;
      # Reset and add numbers for each "box" ids
      def add_numbers($box_idx):
        reduce $box_idx[] as $i ({numbers:[]};
          if ($inputs[$i:$i+1] | test("[0-9]")) and new_number($i) then
            .numbers += [ make_number($i) ]
          else
            .
          end
        )
      ;
      add_numbers([
        $i - $w -1 , $i - $w , $i -$w + 1 ,
        $i - 1     , empty   , $i + 1     ,
        $i + $w - 1, $i + $w , $i + $w + 1
      ]).numbers as $numbers |
    
      if $numbers | length == 2 then
        # Add product if exactly two bordering numbers
        . += ( $numbers | map($inputs[.[0]:.[1]]|tonumber) | .[0] * .[1] )
      else
        .
      end
    )
    

    Not too far of an edit from part one.

  • datarama@awful.systems
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    1 year ago

    I will mostly be using Scheme. Comments and questions are very welcome.

    Day 2: Cube Conundrum

    Scheme Code and Explanation

    The string format of a game is, for example: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green". I’ll transform a game string into a list, such that the above example becomes (1 (4 0 3) (1 2 6) (0 2 0)).

    I start by simply splitting on : and chopping off "Game" from the first string to get the game number. The second string consists of a sequence of draws, which need a bit further parsing effort.

    (define (parse-game game)
      (let* ((parts (string-split game ":"))
             (number (string->number (string-drop (car parts) 5)))
             (draws (map parse-draws (string-split (cadr parts) ";"))))
        (cons number draws)))
    

    Parsing a draw is done by first splitting it on ;, to yield strings on the form "3 blue, 4 red". Each such string represents a cubeset, which I represent as lists on the form (r g b). Since the input strings don’t have any well-defined ordering of cube colours, I split on , and iterate on the resulting list of strings until I find one that has the proper colour name as its suffix, in the order red -> green -> blue. If none exists, I return 0 for that colour.

    (define (parse-draws cstr)
      (let ((cubes (map string-trim (string-split cstr ","))))
        (list (get-col cubes "red")
              (get-col cubes "green")
              (get-col cubes "blue"))))
    
    (define (get-col cubeset col)
      (cond ((null? cubeset) 0)
            ((string-suffix? col (car cubeset))
             (string->number (car (string-split (car cubeset) " "))))
            (else (get-col (cdr cubeset) col))))
    

    To determine whether a game is possible given some numbers of different colours, I need to find the maxima of each colour for all draws in a game:

    (define (maxima cubesets)
      (let loop ((cubesets cubesets) (r 0) (g 0) (b 0))
        (if (null? cubesets)
          (list r g b)
          (loop (cdr cubesets)
                (max r (caar cubesets))
                (max g (cadar cubesets))
                (max b (caddar cubesets))))))
    

    The maxima of the draw ((4 0 3) (1 2 6) (0 2 0)) is (4 2 6). With that, it’s easy to determine whether a game is possible:

    (define (game-possible? game r g b)
      (let ((mx (maxima (cdr game))))
        (and (>= r (car mx))
             (>= g (cadr mx))
             (>= b (caddr mx)))))
    

    The solution to part 1 is now simple:

    (define (solve-part1 lines)
      (let solve ((sum 0) (games (map parse-game lines)))
        (cond ((null? games) sum)
              ((game-possible? (car games) 12 13 14)
               (solve (+ sum (caar games)) (cdr games)))
              (else (solve sum (cdr games))))))
    

    …where lines is all the lines of the input file.

    Part 2 is simple, given that I already have a way to get the maxima of the draws in a game. All I need to do is to multiply them together:

    (define (power-cubeset cubeset)
      (* (car cubeset) (cadr cubeset) (caddr cubeset)))
    
    (define (solve-part2 lines)
      (let solve ((sum 0) (games (map parse-game lines)))
        (cond ((null? games) sum)
              (else (solve (+ sum (power-cubeset (maxima (cdar games))))
                           (cdr games))))))
    
    • swlabr@awful.systems
      link
      fedilink
      English
      arrow-up
      1
      ·
      1 year ago

      I believe you’ve posted in the wrong thread. This is the awful systems thread, not the awesome systems thread.

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      3
      ·
      1 year ago

      nice cleanser after yesterday

      spoiler

      it would have taken me longer to figure out the algebra than to just mush the inputs together and get the solution that way (16s runtime)

    • zogwarg@awful.systems
      link
      fedilink
      English
      arrow-up
      4
      ·
      edit-2
      1 year ago

      I liked the slight trickiness of part 2, that the naive implementation would never complete in time.

      As always doing a JQ implementation:

      Part 1
      #!/usr/bin/env jq -n -R -f
      
      # Get seeds
      input | [ match("\\d+"; "g").string | tonumber ] as $seeds |
      
      # Collect maps
      reduce inputs as $line ({};
        if $line == "" then
          .
        elif $line | test(":") then
          .k = ( $line / " " | .[0] )
        else
          .[.k] += [[ $line | match("\\d+"; "g").string | tonumber ]]
        end
      )
      
      # For each map, apply transformation to all seeds.
      # seed -> ... -> location
      | reduce ( to_entries[] | select(.key != "k") .value) as $map ({s:$seeds};
        .s[] |= (
          # Only attempt transform if element is in one of the ranges
          [ . as $e | $map[] | select(. as  [$d,$s,$l] | $e >= $s and $e$s + $l) ] as $range |
          if ($range | length ) > 0 then
            $range[0] as [$d,$s] |
            . - $s + $d
          else
            .
          end
        )
      )
      
      # Get lowest location
      | .s | min
      

      Some comments:

      • A nice use of input first to get the seeds, then inputs to get remaining lines.
      Part 2
      #!/usr/bin/env jq -n -R -f
      
      # Utility function
      def group_of($n):
        ( length / $n ) as $l |
        . as $arr |
        range($l) | $arr[.*$n:.*$n+$n]
      ;
      
      # Get all seed ranges
      input | [ match("\\d+"; "g").string | tonumber ] | [group_of(2)] as $seeds |
      
      # Collect maps
      reduce inputs as $line ({};
        if $line == "" then
          .
        elif $line | test(":") then
          .k = ( $line / " " | .[0] )
        else
          .[.k] += [[ $line | match("\\d+"; "g").string | tonumber ]]
        end
      )
      
      # For each map, apply transformation to all seeds ranges.
      # Producing new seed ranges if applicable
      # seed -> ... -> location
      | reduce (to_entries[] | select(.key != "k") .value) as $map ({s:$seeds};
        .s |= [
          # Only attempt transform if seed range and map range instersect
          .[] | [.[0], add, .[1] ] as [$ea, $eb, $el] | [
            $map[] | select(.[1:] | [.[0], add ] as [$sa,$sb] |
              ( $ea >= $sa and $ea$sb ) or
              ( $eb >= $sa and $eb$sb ) or
              ( $sa >= $ea and $sa$eb )
            )
          ] as $range |
          if $range | length > 0 then
            $range[0] as [$d,$s,$l] |
            # ( only end ) inside map range
            if $ea$s and $eb$s + $l then
              [$ea, $s - $ea], [$d, $eb - $s ]
            # ( both start, end ) outside map range
            elif $ea$s then
              [$ea, $s - $ea], [$d, $l], [ $s + $l, $eb ]
            # ( only start ) inside map range
            elif $eb$s + $l then
              [$ea + $d - $s, $l - $ea + $s ], [$s + $l, $eb - $s - $l]
            # ( both start, end ) inside map range
            else
              [$ea + $d - $s , $el]
            end
          else
            .
          end
        ]
      )
      
      # Get lowest location
      | [.s[][0]] | min
      

      Some comments:

      • Since iterating across all seeds naively would take forever, iterating over seed ranges instead.
      • It’s nice that JQ can neatly produce extra elements: [1,2,3] | [ .[] | if . == 2 then . * 10 + 1 , . * 10 + 2 else . end ] -> [1, 21, 22, 3]
      • There is probably a more compact way of expressing all the conditions and produced outputs.

      Replaced less-than (and greater-than for symmetry) symbols with full-width version, since lemmy apparently doesn’t handle them well within a code block: replacing less than with &lt;

        • zogwarg@awful.systems
          link
          fedilink
          English
          arrow-up
          2
          ·
          1 year ago

          The main catch is it would often be faster to use a “real” programming langage ^^, both in writing the code, and in execution time for some loop heavy examples: equivalent code that completes say in 1 second in python, completing in 1 minute in jq. Also missing a way to call native libraries, to do stuff like say “md5” (relevant) in past years advents-of-code.

          That being said i like the general “pipe”, map-reduce feel of the language. Like bash one-liners It can make for very terse implementations. I like to add comments, and indentation to make it readable though.

    • froztbyte@awful.systems
      link
      fedilink
      English
      arrow-up
      3
      ·
      edit-2
      1 year ago

      okay yeah so

      p1 part 1 submitted, runs fine. part 2 says “wrong answer for you but right for someone else”. doing a debug-print-everything run on the intermediate stages of the data after my regex all seems correct, but it’s also 23h50 and it’s been a looooong week. so I guess I’ll take a look a fresh look at that one in the morning over coffee

      part1, badly:

      spoiler
      import re
      
      pattern = '\d'
      cmp = re.compile(pattern)
      
      # extremely lazy, of the ungood kind
      datapath = 'data'
      lines = open(datapath, 'r').read().split('\n')
      candidates = []
      values = []
      for l in lines:
          if l != '':
              r = cmp.findall(l)
              candidates.append(r)
              values.append(int(''.join([r[0], r[-1]])))
      
      print(candidates)
      print(values)
      print(sum(values))
      

      (I really wasn’t kidding about the badly)

      part2:

      spoiler

      missed the eightwo case

      changes:

      mapping = {...} # name/int pairs
      pattern = f'(?=(\d|{"|".join(mapping.keys())}))'
      lines = open(datapath, 'r').read().split('\n').remove('')
      values = []
      for l in lines:
          r = cmp.findall(l)
          equivs = [str(mapping.get(x, x)) for x in r]
          head, tail = [equivs[0], equivs[-1]]
          values.append(int(f"{head}{tail}"))
      print(sum(values))
      
  • zogwarg@awful.systems
    link
    fedilink
    English
    arrow-up
    3
    ·
    1 year ago

    Back to a more straightfoward day, do they make them harder on the weekends?

    Day 4 Scratchcards

    Part 1
    #!/usr/bin/env jq -n -R -f
    [
      inputs
      # Split winning numbers | card
      | split(" | ")
      # Get numbers, remove game id
      | .[] |= [ match("\\d+"; "g").string | tonumber ] | .[0] |= .[1:]
      # Get score for each line
      | .[1] - (.[1] - .[0]) | length | select(. > 0) | pow(2; . - 1)
    ]
    
    # Output total score sum
    | add
    

    Very suited to JQ, extra trick learned using: [ match("\\d+"; "g").string | tonumber ] as a parse all ints in line.

    Part 2
    #!/usr/bin/env jq -n -R -f
    [
      inputs
      # Split winning numbers | card
      | split(" | ")
      # Get numbers, remove game id
      | .[] |= [ match("\\d+"; "g").string | tonumber ] | .[0] |= .[1:]
      # Set number of cards to 1, and further cards count
      | .[1] - (.[1] - .[0]) | [ 1, length ]
    ]
    
    | { cards: ., i: 0, l: length } | until (.i == .l;
      # Get number for current card
      .cards[.i][0] as $num
      # Increase range of futher cards, by current number
      | .cards[.i + range(.cards[.i][1]) + 1 ][0] += $num
      | .i += 1
    )
    
    # Output total sum of cards
    | [ .cards[][0] ] | add
    

    Not too much of an edit compared to part one, being able to easily do operations on range of indices is convenient.

    • gerikson@awful.systemsOP
      link
      fedilink
      English
      arrow-up
      4
      ·
      1 year ago

      Historically problems on Sat/Sun have been more challenging than weekdays. However given that the first 7 days are usually “warmup” problems, I’d say this years edition of AoC is more challenging than at least since 2019.