15 votes

Day 5: Supply Stacks

Today's problem description: https://adventofcode.com/2022/day/5

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

20 comments

  1. tjf
    Link
    Here's my Python solution -- my first this year that uses a defaultdict and uses regex for the input parsing. Part 1 #!/usr/bin/env pypy3 from collections import defaultdict import re import...

    Here's my Python solution -- my first this year that uses a defaultdict
    and uses regex for the input parsing.

    Part 1
    #!/usr/bin/env pypy3
    
    from collections import defaultdict
    import re
    import string
    import sys
    
    def build_stacks(lines):
        stacks = defaultdict(list)
        for line in lines.split('\n')[:-1]:
            for col, char in enumerate(line):
                if char in string.ascii_uppercase:
                    stacks[(col - 1) // 4].insert(0, char)
    
        return stacks
    
    def move_crates(moves, stacks):
        for move in moves.split('\n')[:-1]:
            m = re.match(r'^move ([0-9]+) from ([0-9]+) to ([0-9]+)$', move.strip())
            amount, stack_from, stack_to = map(int, m.groups())
            for _ in range(amount):
                crate = stacks[stack_from - 1].pop()
                stacks[stack_to - 1].append(crate)
    
    def main():
        stack_lines, move_lines = sys.stdin.read().split('\n\n')
        stacks = build_stacks(stack_lines)
        move_crates(move_lines, stacks)
        msg = ''.join(stacks[i][-1] for i in sorted(stacks.keys()))
        print(msg)
    
    if __name__ == '__main__':
        main()
    
    Part 2
    #!/usr/bin/env pypy3
    
    from collections import defaultdict
    import re
    import string
    import sys
    
    def build_stacks(lines):
        stacks = defaultdict(list)
        for line in lines.split('\n')[:-1]:
            for col, char in enumerate(line):
                if char in string.ascii_uppercase:
                    stacks[(col - 1) // 4].insert(0, char)
    
        return stacks
    
    def move_crates(moves, stacks):
        for move in moves.split('\n')[:-1]:
            m = re.match(r'^move ([0-9]+) from ([0-9]+) to ([0-9]+)$', move.strip())
            amount, stack_from, stack_to = map(int, m.groups())
            crates = [stacks[stack_from - 1].pop() for _ in range(amount)][::-1]
            stacks[stack_to - 1] += crates
    
    def main():
        stack_lines, move_lines = sys.stdin.read().split('\n\n')
        stacks = build_stacks(stack_lines)
        move_crates(move_lines, stacks)
        msg = ''.join(stacks[i][-1] for i in sorted(stacks.keys()))
        print(msg)
    
    if __name__ == '__main__':
        main()
    
    4 votes
  2. bhrgunatha
    (edited )
    Link
    Update: added Speedrun. Part 1 Parsing is relatively easy with the wonderful in-slice and a few helper procedures, groups and lines from day 1 and integers (converting the result of regex \d+ to...

    Update: added Speedrun.

    Part 1

    Parsing is relatively easy with the wonderful in-slice and a few helper procedures, groups and lines from day 1 and integers (converting the result of regex \d+ to numbers).

    BUT... I lost so much time because I kept testing my parser with a pre-formatted, hard-coded list, rather than the 2 strings that groups returns. smh

    To see how dumb I am, can anyone spot the difference between

    [Z] [M] [P]\n 1   2   3
    move 1 from 2 to 1\nmove 3 from 1 to 3
    

    and

    [Z] [M] [P]
     1   2   3
    
    move 1 from 2 to 1
    move 3 from 1 to 3
    

    good for you, because apparently I can't.

    My take-away from this is simply not to bother testing your code before you run it.

    (define (part-01 input)
      (define stacks (arrange-crates input reverse))
      (list->string (map first (hash-values stacks))))
    
    (define (arrange-crates input crane)
      (define-values (procedure crates) (parse-stacks (groups input)))
      (for/fold ([crates crates])
                ([is (in-list procedure)])
        (match-define (list amount from to) is)
        (move-with crates amount from to crane)))
    
    (define (move-with crates amount from to move)
      (define-values (lifted left) (split-at (hash-ref crates from) amount))
      (define target (hash-ref crates to))
      (hash-set (hash-set crates from left)
                to
                (append (move lifted) target)))
    
    (define ((adjoin x) xs) (append xs (list x)))
    (define (parse-stacks input)
      (match-define (list stacks procedure) input)
      (define instructions (map integers (lines procedure)))
      (define arrangement
        (for*/fold ([crates (hash)])
                   ([row (in-list (lines stacks))]
                    [(crate i) (in-indexed (in-slice 4 row))])
          (if (eq? (first crate) #\[)
              (hash-update crates (add1 i) (adjoin (second crate)) null)
              crates)))
      (values instructions arrangement))
    

    Initially of course there was no arrange-crates or move-with - it was all hard-coded into part 1 and copy-pasted into part 2, but I feel icky with so much duplication, so I extracted them to post here.

    Part 2

    Part 2 was so easy, just replace the reverse for identity (which in Racket is values)

    (define (part-02 input)
      (define stacks (arrange-crates input values))
      (list->string (map first (hash-values stacks))))
    
    Speedrun

    Before each part took ~2.1 ms (after reading the file into a string) because each part built up the same data structure, total time ~4.3ms

    Attempt 1: ~80% elapsed time
    Removing the duplicated work building data structures, and re-use them for both parts.

    Attempt 2: ~30% elapsed time

    As always, one trick is to build your data structure/s as you read the file, instead of slurping the whole thing in and then running over it several times parsing values.

    Two parsing phases, the drawing and the instructions.
    These are pretty much the same with two significant changes.
    Use a hasheq instead of hash and push each new crate to the front of the stack and reverse all stacks as a final step.

    Racket's hasheq is faster than a regular hash because it uses eq? to test keys. For objects eq? is identity (aka memory location), so super fast. Luckily integers are eq? if their values are =, so hasheq speeds up all hash operations.

    Instead of adjoin appending to the current stack, push the crate onto the existing stack. This means the parsed stacks are reversed so there's a final step to reverse them.

    Change part-01 and part-02 to accept the parsed stacks and instructions.
    The rest is the same as before.

    (define (part-01 procedure stacks)
      (define crates (arrange-crates procedure stacks reverse))
      (list->string (map first (hash-values crates))))
      
    (define (part-02 procedure stacks)
      (define crates (arrange-crates procedure stacks values))
      (list->string (map first (hash-values crates))))
    
    (define (arrange-crates procedure crates crane)
      (for/fold ([crates crates])
                ([is (in-list procedure)])
        (match-define (list amount from to) is)
        (move-with crates amount from to crane)))
    
    (define (move-with crates amount from to move)
      (define-values (lifted left) (split-at (hash-ref crates from) amount))
      (define target (hash-ref crates to))
      (hash-set (hash-set crates from left)
                to
                (append (move lifted) target)))
      
    (define ((adjoin x) xs) (cons x xs))
    
    (define (place-crates stacks line)
      (for/fold ([stacks stacks])
                ([c (in-string line)]
                 [i (in-naturals 1)]
                 #:when (char-upper-case? c))
        (hash-update stacks (ceiling (/ i 4)) (adjoin c) null)))
    
    (define-values (crates procedure)
       (with-input-from-file "day05.input"
         (thunk
          (let ([crates
                 (for/fold ([stacks (hasheq)]
                            #:result (for/hasheq ([(k v) (in-hash stacks)]) (values k (reverse v))))
                           ([line (in-lines)]
                            #:break (string=? line ""))
                   (place-crates stacks line))]
                [procedure (for/list ([line (in-lines)])
                             (integers line))])
            (values crates procedure)))))
    (part-01 procedure crates)
    (part-02 procedure crates)
    
    4 votes
  3. primordial-soup
    (edited )
    Link
    Part 1, in Python-ish (*ls_top, ls_top_last), ls_btm = ls > p | (split_at, X, ~(X == "")) stacks = (ls_top > fe(X.ljust(len(ls_top_last) + 1)) | fe(p | (chunked, X, 4) | fe("".join)) |...
    Part 1, in Python-ish
    (*ls_top, ls_top_last), ls_btm = ls > p | (split_at, X, ~(X == ""))
    stacks = (ls_top
              > fe(X.ljust(len(ls_top_last) + 1))
              | fe(p | (chunked, X, 4) | fe("".join))
              | as_args(zip)
              | fe(fe(X[1])
                   | where(X != " ")
                   | always_reversible | list)
              | tuple)
    for n, i, f in (ls_btm
                    > fe((re.search, r"move (\d+) from (\d+) to (\d+)")
                         | X.groups() | fe(int))):
         for _ in range(n):
              stacks[f - 1].append(stacks[i - 1].pop())
    stacks > fe(X[-1]) | "".join
    
    Python code generated from the above
    from more_itertools import always_reversible
    from more_itertools import chunked
    from more_itertools import split_at
    from pipetools import X
    from pipetools import as_args
    from pipetools import foreach
    from pipetools import pipe
    from pipetools import where
    import re
    import sys
    from pyp import pypprint
    p = pipe
    fe = foreach
    lines = [x.rstrip('\n') for x in sys.stdin]
    ls = lines
    ((*ls_top, ls_top_last), ls_btm) = ls > p | (split_at, X, ~(X == ''))
    stacks = ls_top > fe(X.ljust(len(ls_top_last) + 1)) | fe(p | (chunked, X, 4) | fe(''.join)) | as_args(zip) | fe(fe(X[1]) | where(X != ' ') | always_reversible | list) | tuple
    for (n, i, f) in ls_btm > fe((re.search, 'move (\\d+) from (\\d+) to (\\d+)') | X.groups() | fe(int)):
        for _ in range(n):
            stacks[f - 1].append(stacks[i - 1].pop())
    output = stacks > fe(X[-1]) | ''.join
    if output is not None:
        pypprint(output)
    
    Part 2, in Python-ish
    (*ls_top, ls_top_last), ls_btm = ls > p | (split_at, X, ~(X == ""))
    stacks = (ls_top
              > fe(X.ljust(len(ls_top_last) + 1))
              | fe(p | (chunked, X, 4) | fe("".join))
              | as_args(zip)
              | fe(fe(X[1])
                   | where(X != " ")
                   | always_reversible | list)
              | list)
    for n, i, f in (ls_btm
                    > fe((re.search, r"move (\d+) from (\d+) to (\d+)")
                         | X.groups() | fe(int))):
         stacks[f - 1] += stacks[i - 1][-n:]
         stacks[i - 1] = stacks[i - 1][:-n]
    stacks > fe(X[-1]) | "".join
    
    Python code generated from the above
    from more_itertools import always_reversible
    from more_itertools import chunked
    from more_itertools import split_at
    from pipetools import X
    from pipetools import as_args
    from pipetools import foreach
    from pipetools import pipe
    from pipetools import where
    import re
    import sys
    from pyp import pypprint
    p = pipe
    fe = foreach
    lines = [x.rstrip('\n') for x in sys.stdin]
    ls = lines
    ((*ls_top, ls_top_last), ls_btm) = ls > p | (split_at, X, ~(X == ''))
    stacks = ls_top > fe(X.ljust(len(ls_top_last) + 1)) | fe(p | (chunked, X, 4) | fe(''.join)) | as_args(zip) | fe(fe(X[1]) | where(X != ' ') | always_reversible | list) | list
    for (n, i, f) in ls_btm > fe((re.search, 'move (\\d+) from (\\d+) to (\\d+)') | X.groups() | fe(int)):
        stacks[f - 1] += stacks[i - 1][-n:]
        stacks[i - 1] = stacks[i - 1][:-n]
    output = stacks > fe(X[-1]) | ''.join
    if output is not None:
        pypprint(output)
    

    i have also been challenging myself to solve each puzzle in a single expression. here are my single-expression solutions for today:

    Part 1, in Python-ish
    (ls > p
     | (split_at, X, ~(X == ""))
     | as_args(λ t, b: (t > p
                        | always_reversible | (λ ls: (λ top_width: ls > fe(X.ljust(top_width)))
                                                     (len(first(ls)) + 1))
                        | fe(p | (chunked, X, 4) | fe("".join))
                        | as_args(zip)
                        | fe(fe(X[1])
                             | where(X != " ")
                             | list)
                        | list,
                        b
                        > fe((re.search, r"move (\d+) from (\d+) to (\d+)")
                             | X.groups() | fe(int))))
     | as_args(λ sl, ii: (ii
                          > fe(as_args(λ n, i, f: range(n)
                                                  > fe_do(λ _: sl[f - 1].append(sl[i - 1].pop())))) | tuple,
                          sl > fe(X[-1]) | "".join
                         )[1]))
    
    Python code generated from the above
    from more_itertools import always_reversible
    from more_itertools import chunked
    from more_itertools import first
    from more_itertools import split_at
    from pipetools import X
    from pipetools import as_args
    from pipetools import foreach
    from pipetools import foreach_do
    from pipetools import pipe
    from pipetools import where
    import re
    import sys
    from pyp import pypprint
    p = pipe
    fe = foreach
    fe_do = foreach_do
    lines = [x.rstrip('\n') for x in sys.stdin]
    ls = lines
    output = ls > p | (split_at, X, ~(X == '')) | as_args(lambda t, b: (t > p | always_reversible | (lambda ls: (lambda top_width: ls > fe(X.ljust(top_width)))(len(first(ls)) + 1)) | fe(p | (chunked, X, 4) | fe(''.join)) | as_args(zip) | fe(fe(X[1]) | where(X != ' ') | list) | list, b > fe((re.search, 'move (\\d+) from (\\d+) to (\\d+)') | X.groups() | fe(int)))) | as_args(lambda sl, ii: (ii > fe(as_args(lambda n, i, f: range(n) > fe_do(lambda _: sl[f - 1].append(sl[i - 1].pop())))) | tuple, sl > fe(X[-1]) | ''.join)[1])
    if output is not None:
        pypprint(output)
    
    Part 2, in Python-ish
    (ls > p
     | (split_at, X, ~(X == ""))
     | as_args(λ t, b: (t > p
                        | always_reversible | (λ ls: (λ top_width: ls > fe(X.ljust(top_width)))
                                                     (len(first(ls)) + 1))
                        | fe(p | (chunked, X, 4) | fe("".join))
                        | as_args(zip)
                        | fe(fe(X[1])
                             | where(X != " ")
                             | list)
                        | list,
                        b
                        > fe((re.search, r"move (\d+) from (\d+) to (\d+)")
                             | X.groups() | fe(int))))
     | as_args(λ sl, ii: (ii
                          > fe(as_args(λ n, i, f: range(n)
                                                  > fe_do(λ j: sl[f - 1].append(sl[i - 1].pop(-n + j))))) | tuple,
                          sl > fe(X[-1]) | "".join
                         )[1]))
    
    Python code generated from the above
    from more_itertools import always_reversible
    from more_itertools import chunked
    from more_itertools import first
    from more_itertools import split_at
    from pipetools import X
    from pipetools import as_args
    from pipetools import foreach
    from pipetools import foreach_do
    from pipetools import pipe
    from pipetools import where
    import re
    import sys
    from pyp import pypprint
    p = pipe
    fe = foreach
    fe_do = foreach_do
    lines = [x.rstrip('\n') for x in sys.stdin]
    ls = lines
    output = ls > p | (split_at, X, ~(X == '')) | as_args(lambda t, b: (t > p | always_reversible | (lambda ls: (lambda top_width: ls > fe(X.ljust(top_width)))(len(first(ls)) + 1)) | fe(p | (chunked, X, 4) | fe(''.join)) | as_args(zip) | fe(fe(X[1]) | where(X != ' ') | list) | list, b > fe((re.search, 'move (\\d+) from (\\d+) to (\\d+)') | X.groups() | fe(int)))) | as_args(lambda sl, ii: (ii > fe(as_args(lambda n, i, f: range(n) > fe_do(lambda j: sl[f - 1].append(sl[i - 1].pop(-n + j))))) | tuple, sl > fe(X[-1]) | ''.join)[1])
    if output is not None:
        pypprint(output)
    
    3 votes
  4. asterisk
    (edited )
    Link
    Python from curses.ascii import isdigit import collections, copy, re file = [block.split("\n") for block in open("input.txt").read().split("\n\n")] procedures = [list(map(int, re.findall("\d+",...
    Python
    from curses.ascii import isdigit
    import collections, copy, re
    
    file = [block.split("\n") for block in open("input.txt").read().split("\n\n")]
    procedures = [list(map(int, re.findall("\d+", line))) for line in file[1]]
    crates = collections.defaultdict(list)
    
    for line in file[0][-2::-1]:
        for i, mark in enumerate(file[0][-1]):
            if isdigit(mark) and i < len(line) and line[i] != " ":
                crates[int(mark)].append(line[i])
    
    
    def crateMover(version: int = 9000) -> str:
        cell = copy.deepcopy(crates)
        for move, a, b in procedures:
            moved = [cell[a].pop() for _ in range(move)]
            cell[b].extend(moved if version == 9000 else moved[::-1])
    
        return "".join([cell[i][-1] for i in cell])
    
    
    print(crateMover())  # Part One: QNHWJVJZW
    print(crateMover(9001))  # Part Two: BPCZJLFJW
    
    Update From
    for line in file[0][-2::-1]:
        for i, mark in [(i, int(mark)) for i, mark in enumerate(file[0][-1]) if isdigit(mark)]:
            if i >= len(line):
                break
            elif line[i] != " ":
                crates[mark].append(line[i])
    

    To

    for line in file[0][-2::-1]:
        for i, mark in enumerate(file[0][-1]):
            if isdigit(mark) and i < len(line) and line[i] != " ":
                crates[int(mark)].append(line[i])
    

    Yeah, the parsing took more time than both solutions…

    2 votes
  5. [3]
    Macil
    Link
    Is this the first advent of code challenge where the answer is a string instead of an integer? I had to update the Deno helper library I wrote to deal with this change. I'm considering rewriting...

    Is this the first advent of code challenge where the answer is a string instead of an integer? I had to update the Deno helper library I wrote to deal with this change.

    I'm considering rewriting my parse function to use some parser library in order to practice with them, or doing that for future challenges if the input gets more complicated.

    Multiple parts of my answer were written using Github Copilot. The main reason for some of the comments in my code was to help Copilot figure out what I wanted to do and write what I needed. It's amazing how well it works. It wrote the code to split each line into groups of four characters, the code to create Procedure objects out of a regex match, and both // execute procedure, blocks.

    Typescript (Deno)
    import { assertEquals } from "https://deno.land/std@0.167.0/testing/asserts.ts";
    import { runPart } from "https://deno.land/x/aocd@v1.2.1/mod.ts";
    
    // the first item in the list is the item at the bottom of the stack
    type Stack = string[];
    
    interface Challenge {
      startingStacks: Stack[];
      procedures: Procedure[];
    }
    
    interface Procedure {
      amount: number;
      source: number;
      destination: number;
    }
    
    function parse(input: string): Challenge {
      const challenge: Challenge = {
        startingStacks: [],
        procedures: [],
      };
      type Section = "startingStacks" | "procedures";
      let section: Section = "startingStacks";
      for (const line of input.trimEnd().split("\n")) {
        switch (section) {
          case "startingStacks": {
            if (line.startsWith(" 1")) {
              section = "procedures";
              // stacks were inserted backwards, so flip them now
              for (const stack of challenge.startingStacks) {
                stack.reverse();
              }
            } else {
              // split line into list of strings every 4 characters
              const chunks = line.match(/.{1,4}/g) ?? [];
              // `chunks` looks like ["    ", "[D]"] now
              chunks.forEach((chunk, index) => {
                if (chunk[0] === "[") {
                  const letter = chunk[1];
                  // put the letter into the correct list inside of the challenge object.
                  // we're assembling the stack backwards, so we'll reverse it later.
    
                  if (!challenge.startingStacks[index]) {
                    challenge.startingStacks[index] = [];
                  }
                  challenge.startingStacks[index].push(letter);
                }
              });
            }
            break;
          }
          case "procedures": {
            if (line.length > 0) {
              const match = /^move (\d+) from (\d+) to (\d+)$/.exec(line);
              if (!match) {
                throw new Error("line did not match expected format");
              }
              const procedure: Procedure = {
                amount: Number(match[1]),
                source: Number(match[2]),
                destination: Number(match[3]),
              };
              challenge.procedures.push(procedure);
            }
            break;
          }
        }
      }
      return challenge;
    }
    
    function part1(input: string): string {
      const challenge = parse(input);
      const stacks: Stack[] = structuredClone(challenge.startingStacks);
      for (const procedure of challenge.procedures) {
        // execute procedure, moving 1 item at a time
        for (let i = 0; i < procedure.amount; i++) {
          const item = stacks[procedure.source - 1].pop();
          if (!item) {
            throw new Error("stack was empty");
          }
          stacks[procedure.destination - 1].push(item);
        }
      }
      // return a string made up of the letters at the end of each stack
      return stacks.map((stack) => stack[stack.length - 1]).join("");
    }
    
    function part2(input: string): string {
      const challenge = parse(input);
      const stacks: Stack[] = structuredClone(challenge.startingStacks);
      for (const procedure of challenge.procedures) {
        // execute procedure, moving `procedure.amount` items at a time
        const items = stacks[procedure.source - 1].splice(
          stacks[procedure.source - 1].length - procedure.amount,
          procedure.amount,
        );
        stacks[procedure.destination - 1].push(...items);
      }
      // return a string made up of the letters at the end of each stack
      return stacks.map((stack) => stack[stack.length - 1]).join("");
    }
    
    if (import.meta.main) {
      runPart(2022, 5, 1, part1);
      runPart(2022, 5, 2, part2);
    }
    
    const TEST_INPUT = `\
        [D]
    [N] [C]
    [Z] [M] [P]
     1   2   3
    
    move 1 from 2 to 1
    move 3 from 1 to 3
    move 2 from 2 to 1
    move 1 from 1 to 2
    `;
    
    Deno.test("part1", () => {
      assertEquals(part1(TEST_INPUT), "CMZ");
    });
    
    Deno.test("part2", () => {
      assertEquals(part2(TEST_INPUT), "MCD");
    });
    
    2 votes
    1. primordial-soup
      Link Parent
      day 13 last year had a string answer

      day 13 last year had a string answer

      3 votes
    2. wycy
      Link Parent
      There have been previous years where the answer was not only text, but text based on ASCII renders of text, e.g. ███ ██ ███ █ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ████ █ ███...

      There have been previous years where the answer was not only text, but text based on ASCII renders of text, e.g.

      ███   ██  ███  █     ██  █  █ █  █ █
      â–ˆ  â–ˆ â–ˆ  â–ˆ â–ˆ  â–ˆ â–ˆ    â–ˆ  â–ˆ â–ˆ â–ˆ  â–ˆ  â–ˆ â–ˆ
      █  █ █    █  █ █    █  █ ██   ████ █
      ███  █    ███  █    ████ █ █  █  █ █
      â–ˆ â–ˆ  â–ˆ  â–ˆ â–ˆ    â–ˆ    â–ˆ  â–ˆ â–ˆ â–ˆ  â–ˆ  â–ˆ â–ˆ
      █  █  ██  █    ████ █  █ █  █ █  █ ████
      
      3 votes
  6. Crestwave
    Link
    Nice warmup to some familiar AoC patterns. Part 1 #!/usr/bin/awk -f /[][]/ { input[NR] = $0 } /^ 1/ { for (i = 2; i < length($0); i += 4) { j = substr($0, i, 1) for (k = NR; k > 0; --k) { c =...

    Nice warmup to some familiar AoC patterns.

    Part 1
    #!/usr/bin/awk -f
    /[][]/ { input[NR] = $0 }
    
    /^ 1/ {
    	for (i = 2; i < length($0); i += 4) {
    		j = substr($0, i, 1)
    		for (k = NR; k > 0; --k) {
    			c = substr(input[k], i, 1)
    			if (c != " ") {
    				stack[j, ++stack[j]] = c
    			}
    		}
    	}
    }
    
    /move/ {
    	for (i = 1; i <= $2; ++i) {
    		stack[$6, ++stack[$6]] = stack[$4, stack[$4]--]
    	}
    }
    
    END {
    	for (i = 1; i <= j; ++i) {
    		printf("%s", stack[i, stack[i]])
    	}
    
    	print ""
    }
    
    Part 2
    #!/usr/bin/awk -f
    /[][]/ { input[NR] = $0 }
    
    /^ 1/ {
    	for (i = 2; i < length($0); i += 4) {
    		j = substr($0, i, 1)
    		for (k = NR; k > 0; --k) {
    			c = substr(input[k], i, 1)
    			if (c != " ") {
    				stack[j, ++stack[j]] = c
    			}
    		}
    	}
    }
    
    /move/ {
    	for (i = $2-1; i >= 0; --i) {
    		stack[$6, ++stack[$6]] = stack[$4, stack[$4]-i]
    	}
    
    	stack[$4] -= $2
    }
    
    END {
    	for (i = 1; i <= j; ++i) {
    		printf("%s", stack[i, stack[i]])
    	}
    
    	print ""
    }
    
    2 votes
  7. thorondir
    Link
    Still doesn't feel idiomatic, because I use a bunch of mutation, but it's getting easier to write down what I want to do, so that's a win. Now I just have to not do the unidiomatic thing. xD...

    Still doesn't feel idiomatic, because I use a bunch of mutation, but it's getting easier to write down what I want to do, so that's a win. Now I just have to not do the unidiomatic thing. xD

    Racket:

    Part 1
    (struct move (amount src dst) #:transparent)
    
    (define (parse-stacks stack-lines)
      (define stacks# (length (string-split (last stack-lines))))
      (define stacks (make-vector stacks# '()))
      (for ([stack-line (rest (reverse stack-lines))])
        (define matches (regexp-match* #rx"(...) ?" stack-line))
        (for ([m matches]
              [i (in-range stacks#)])
          (when (not (char=? (string-ref m 1) #\ )) ; only add something if the slot is not empty
            (vector-set! stacks i (cons (string-ref m 1) (vector-ref stacks i))))))
      stacks)
    
    (define (parse-moves move-lines)
      (for/list ([line move-lines])
        (define matches (regexp-match #px"move (\\d+) from (\\d+) to (\\d+)" line))
        (move (string->number (second matches)) ; amount
              (sub1 (string->number (third matches))) ; src
              (sub1 (string->number (fourth matches)))))) ; dst
    
    ;; function to make input usable
    (define (sanitize-input input)
      (define split (string-split input "\n\n"))
      (define stack-def-raw (string-split (first split) "\n"))
      (define proc-def-raw (string-split (second split) "\n"))
    
      (define stacks (parse-stacks stack-def-raw))
      (define moves (parse-moves proc-def-raw))
    
      (values stacks moves))
    
    (define (proc-moves! stacks moves)
      (for ([m moves])
        (for ([i (in-range (move-amount m))])
          (move-container! stacks (move-src m) (move-dst m)))))
    
    (define (move-container! stack src dst)
      (define container (first (vector-ref stack src)))
      (vector-set! stack src (rest (vector-ref stack src)))
      (vector-set! stack dst (cons container (vector-ref stack dst))))
    
    ;; take the small input from the text
    (define raw-small-input (file->string "small-input_05"))
    (define small-answer-1 "CMZ")
    
    ;; workhorse, part 1
    (define (part-1 raw-input)
      (define-values (stacks moves) (sanitize-input raw-input))
      (proc-moves! stacks moves)
      (list->string (vector->list (vector-map first stacks))))
    
    ;; test that "workhorse, part 1" does as it should
    (check-equal? (part-1 raw-small-input) small-answer-1)
    
    (displayln (~a "part 1: " (part-1 raw-aoc-input)))
    

    And Part 2:

    Part 2
    (define (move-multiple-containers! stacks move)
      (define containers (take (vector-ref stacks (move-src move)) (move-amount move)))
      (vector-set! stacks (move-src move) (drop (vector-ref stacks (move-src move)) (move-amount move)))
      (vector-set! stacks (move-dst move) (append containers (vector-ref stacks (move-dst move)))))
    
    (define (proc-moves-2! stacks moves)
      (for ([m moves])
        (move-multiple-containers! stacks m)))
    
    ;; part 2
    (define small-answer-2 "MCD")
    
    (define (part-2 raw-input)
      (define-values (stacks moves) (sanitize-input raw-input))
      (proc-moves-2! stacks moves)
      (list->string (vector->list (vector-map first stacks))))
    
    
    (check-equal? (part-2 raw-small-input) small-answer-2)
    (displayln (~a "part 2: " (part-2 raw-aoc-input)))
    
    2 votes
  8. balooga
    Link
    Here's my TypeScript solution type Crates = string[][]; type Move = [number, number, number]; type Moves = Move[]; interface InputData { crates: Crates; moves: Moves; } // Bit of a pattern change...
    Here's my TypeScript solution
    type Crates = string[][];
    type Move = [number, number, number];
    type Moves = Move[];
    interface InputData {
      crates: Crates;
      moves: Moves;
    }
    
    // Bit of a pattern change for me here, in earlier challenges I formatted the
    // input once and used the result for both parts. This time, because I'm
    // changing arrays in place, I'm calling formatInput() twice, so the second
    // part doesn't begin with the end state of the first part.
    // Kinda hacky but I was in a hurry.
    function formatInput(input: string): InputData {
      const [rawCrates, rawMoves] = input.split('\n\n');
      const crateLines = rawCrates.split('\n').slice(0, -1);
      const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      const crates = new Array((crateLines[0].length + 1) / 4 + 1) as Crates;
      for (const line of crateLines) {
        for (let stack = 1, i = 0; i < line.length; i += 4, stack++) {
          if (alphabet.includes(line[i + 1])) {
            if (!crates[stack]) {
              crates[stack] = [];
            }
            crates[stack].unshift(line[i + 1]);
          }
        }
      }
    
      const moves = rawMoves
        .split('\n')
        .filter(e => !!e)
        .map(e =>
          e
            .split(/[^0-9]/g)
            .filter(f => f !== '')
            .map(f => Number(f))
        ) as Moves;
    
      return {
        crates,
        moves,
      } as InputData;
    }
    
    export function run(input: string): string[] {
    
    Part 1
      const getTopCratesAfterSingleMoves = (rawInput: string): string => {
        const { crates, moves } = formatInput(rawInput);
        for (const move of moves) {
          for (let i = 0; i < move[0]; i++) {
            const crateToMove = crates[move[1]].pop();
            crates[move[2]].push(crateToMove);
          }
        }
        return crates.map(e => e[e.length - 1]).join('');
      };
    
    Part 2
      const getTopCratesAfterMultiMoves = (rawInput: string): string => {
        const { crates, moves } = formatInput(rawInput);
        for (const move of moves) {
          const cratesToMove = [] as string[];
          for (let i = 0; i < move[0]; i++) {
            cratesToMove.unshift(crates[move[1]].pop());
          }
          crates[move[2]].push(...cratesToMove);
        }
        return crates.map(e => e[e.length - 1]).join('');
      };
    
      return [`${getTopCratesAfterSingleMoves(input)}`, `${getTopCratesAfterMultiMoves(input)}`];
    }
    
    2 votes
  9. wycy
    (edited )
    Link
    Rust Rust use std::env; use std::io::{self}; extern crate regex; use regex::Regex; #[macro_use] extern crate lazy_static; lazy_static! { static ref RE_MOVES: Regex = { Regex::new(r"move ([\d]+)...

    Rust

    Rust
    use std::env;
    use std::io::{self};
    
    extern crate regex;
    use regex::Regex;
    
    #[macro_use] extern crate lazy_static;
    lazy_static! {
        static ref RE_MOVES: Regex = {
            Regex::new(r"move ([\d]+) from ([\d]+) to ([\d]+)").unwrap()
        };
    }
    
    #[derive(Debug)]
    struct Move {
        qty: usize,
        from: usize,
        to: usize,
    }
    impl From<&str> for Move {
        fn from(s: &str) -> Self {
            let matches = RE_MOVES.captures(s).unwrap();
            Self {
                qty:  matches[1].parse().unwrap(),
                from: matches[2].parse().unwrap(),
                to:   matches[3].parse().unwrap(),
            }
        }
    }
    
    const MAX_NUM_STACKS: usize = 9;
    
    fn move_last_n_chars(s1: &str, s2: &str, n: usize) -> (String, String) {
        let mut s1_chars: Vec<char> = s1.chars().collect();
        let     s2_chars: Vec<char> = s2.chars().collect();
        
        let mut moved_chars: String = s1_chars.iter().rev().take(n).collect();
        moved_chars = moved_chars.chars().rev().collect();
    
        for _ in 0..n { s1_chars.pop().unwrap(); }
    
        let s1_new = s1_chars.into_iter().collect();
        let mut s2_new: String = s2_chars.into_iter().collect();
        s2_new.push_str(&moved_chars);
    
        (s1_new, s2_new)
    }
    
    fn solve(input: &str) -> io::Result<()> {
        let input_str = std::fs::read_to_string(input).unwrap();
        let input_str = input_str.trim();
        let input: Vec<_> = input_str.split("\n\n").collect();
    
        // Initialize moves
        let moves: Vec<_> = input[1].split("\n").map(Move::from).collect();
    
        // Initialize stacks
        let mut stacks: [String; MAX_NUM_STACKS] = Default::default();
        for line in input[0].lines() {
            if line.chars().nth(1).unwrap() == '1' { continue } // Skip last line indexing the stacks
            for (ix,ch) in line.chars().enumerate() {
                match ix {
                    1  => if ch != ' ' { stacks[0].push_str(&ch.to_string()) },
                    5  => if ch != ' ' { stacks[1].push_str(&ch.to_string()) },
                    9  => if ch != ' ' { stacks[2].push_str(&ch.to_string()) },
                    13 => if ch != ' ' { stacks[3].push_str(&ch.to_string()) },
                    17 => if ch != ' ' { stacks[4].push_str(&ch.to_string()) },
                    21 => if ch != ' ' { stacks[5].push_str(&ch.to_string()) },
                    25 => if ch != ' ' { stacks[6].push_str(&ch.to_string()) },
                    29 => if ch != ' ' { stacks[7].push_str(&ch.to_string()) },
                    33 => if ch != ' ' { stacks[8].push_str(&ch.to_string()) },
                    _  => {},
                }
            }
        }
    
        // Reverse the stacks
        for ix in 0..MAX_NUM_STACKS {
            stacks[ix] = stacks[ix].chars().rev().collect::<String>();
        }
        
        // Copy original state for part 2
        let stacks_copy = stacks.clone();
    
        // Process part 1 moves
        for moved in &moves {
            let (qty,from,to) = (moved.qty,moved.from-1, moved.to-1);
            for _ in 0..qty {
                //(stacks[from],stacks[to]) = move_last_char(&stacks[from],&stacks[to]);
                (stacks[from],stacks[to]) = move_last_n_chars(&stacks[from],&stacks[to],1);
            }
        }
    
        // Part 1 Output
        print!("Part 1: "); // VGBBJCRMN
        for ix in 0..MAX_NUM_STACKS { print!("{}",stacks[ix].chars().last().unwrap()); }
        println!();
    
        // Process part 2 moves
        stacks = stacks_copy;
        for moved in &moves {
            let (qty,from,to) = (moved.qty,moved.from-1, moved.to-1);
            (stacks[from],stacks[to]) = move_last_n_chars(&stacks[from],&stacks[to],qty);
        }
    
        // Part 2 Output
        print!("Part 2: "); // LBBVJBRMH
        for ix in 0..MAX_NUM_STACKS { print!("{}",stacks[ix].chars().last().unwrap()); }
        println!();
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        solve(&filename).unwrap();
    }
    
    2 votes
  10. [3]
    Gyrfalcon
    Link
    Going to be honest, I saw a chance to whip out deque and I just went for it even though nothing in part 1 indicated that it couldn't be handled with a regular Python list. It was probably actually...

    Going to be honest, I saw a chance to whip out deque and I just went for it even though nothing in part 1 indicated that it couldn't be handled with a regular Python list. It was probably actually a hinderance overall, but I still enjoyed getting to play with it. You may also notice that I didn't use regex, which is because I have not really used it in Python and continue to get away with that.

    On the feedback front, if anyone here is familiar with mypy and it's enforcement of static typing, is there any good way to fill or construct a tuple using a loop while enforcing its length? I kept trying to do that with the translated instructions in my input parsing function but eventually gave up and hand wrote the bit since it was only 3 elements anyway.

    Parts 1 and 2
    def parse_input(
        lines: List[str],
    ) -> Tuple[List[deque[str]], List[Tuple[int, int, int]]]:
    
        line_num = 0 
        while lines[line_num] != "\n":
            line_num += 1
    
        num_stacks = int(lines[line_num - 1].split()[-1])
        crates: List[deque[str]] = [deque() for stack in range(num_stacks)]
        for layer in lines[line_num - 2 :: -1]:
            # doing this the lazy way because I think I'll get away with it
            for stack, location in enumerate(range(1, len(layer), 4)):
                if layer[location] == " ":
                    continue
    
                crates[stack].append(layer[location])
    
        instructions = []
        for instruction in lines[line_num + 1 :]: 
            # Is there a better way to do this while still enforcing tuple length?
            breakdown = instruction.strip().split()
            amount: int = int(breakdown[1])
            src: int = int(breakdown[3]) - 1 
            dst: int = int(breakdown[5]) - 1 
            instructions.append((amount, src, dst))
    
        return (crates, instructions)
    
    
    def apply_instruction(crates: List[deque[str]], instruction: Tuple[int, int, int]):
        for _ in range(instruction[0]):
            crates[instruction[2]].append(crates[instruction[1]].pop())
    
    
    def apply_fancy_instruction(
        crates: List[deque[str]], instruction: Tuple[int, int, int]
    ):
    
        hand = []
        for _ in range(instruction[0]):
            hand.append(crates[instruction[1]].pop())
    
        for _ in range(instruction[0]):
            crates[instruction[2]].append(hand.pop())
    
    
    def main(filepath: str) -> Tuple[str, str]:
    
        lines = load_file.load_lines(filepath)
        crates, instructions = parse_input(lines)
        crates_2 = deepcopy(crates)
        for instruction in instructions:
            apply_instruction(crates, instruction)
            apply_fancy_instruction(crates_2, instruction)
    
        return (
            "".join((crate[-1] for crate in crates)),
            "".join((crate[-1] for crate in crates_2)),
        )
    
    2 votes
    1. [2]
      jzimbel
      (edited )
      Link Parent
      Is mypy smart enough to take assert statements into account? You could stick an assert len(breakdown) == 3 in there and see how mypy responds. Edit: Oops, I misread your code. breakdown isn't the...

      Is mypy smart enough to take assert statements into account? You could stick an assert len(breakdown) == 3 in there and see how mypy responds.

      Edit: Oops, I misread your code. breakdown isn't the 3-tuple. But maybe assert could help nonetheless.

      2 votes
      1. Gyrfalcon
        Link Parent
        An interesting idea, but I don't think assertions are accounted for when mypy parses the code. Something like this: instructions = [] indices = (1, 3, 5) for instruction in lines[line_num + 1 :]:...

        An interesting idea, but I don't think assertions are accounted for when mypy parses the code. Something like this:

        instructions = []
        indices = (1, 3, 5)
        for instruction in lines[line_num + 1 :]:
            test = tuple([int(instruction.strip().split()[idx]) for idx in indices])
            assert len(test) == 3
            instructions.append(test)
        

        ...yields this complaint:

        aoc2022/day5/solution.py:43: error: Incompatible return value type (got "Tuple[List[deque[str]], List[Tuple[int, ...]]]", expected "Tuple[List[deque[str]], List[Tuple[int, int, int]]]")  [return-value]
        

        I also tried hinting test as Tuple[int, int, int] but that got a type mismatch on assignment complaint. I think mypy will always assume that a tuple constructed from a loop or comprehension is indeterminate in size :/

        3 votes
  11. kari
    Link
    I think as I learn more nim I'll be able to come back to this one and clean it up a bit, but anyways, it works for now :P nim (both parts) import std/[deques, strutils] proc day05*() = let f =...

    I think as I learn more nim I'll be able to come back to this one and clean it up a bit, but anyways, it works for now :P

    nim (both parts)
    import std/[deques, strutils]
    
    proc day05*() =
      let f = open("inputs/day05.in")
      defer: f.close()
    
      var 
        line = f.readLine()
        stacksP1: seq[Deque[char]]
        crateIdx: int
        topWordP1: string
        topWordP2: string
    
      # Get all of the stacks
      while line != "":
        crateIdx = line.find('[', 0)
        while crateIdx != -1:
          # Make sure stacks is long enough
          while stacksP1.len <= int(crateIdx / 4):
            var deque: Deque[char]
            stacksP1.add(deque)
          stacksP1[int(crateIdx / 4)].addFirst(line[crateIdx + 1])
          crateIdx = line.find('[', crateIdx + 1)
    
        line = f.readLine()
      var stacksP2 = deepCopy(stacksP1)
    
      # Move the crates between stacks
      while (f.readLine(line)):
        let splitLine = line.splitWhitespace()
        let
          count = parseInt(splitLine[1])
          srcStack = parseInt(splitLine[3]) - 1 # My stacks are zero-indexed
          dstStack = parseInt(splitLine[5]) - 1 # but these aren't
        var tmpStack: Deque[char]
    
        for i in 0..<count:
          # CrateMover 9000
          stacksP1[dstStack].addLast(stacksP1[srcStack].popLast())
          # CrateMover 9001
          tmpStack.addFirst(stacksP2[srcStack].popLast())
        while tmpStack.len > 0:
          stacksP2[dstStack].addLast(tmpStack.popFirst)
    
      for stack in stacksP1:
        topWordP1.add(stack.peekLast())
      for stack in stacksP2:
        topWordP2.add(stack.peekLast())
      echo "Part 1: " & topWordP1
      echo "Part 2: " & topWordP2
    
    1 vote
  12. jzimbel
    (edited )
    Link
    Elixir Most of the work for this one was parsing the input, IMO! Both parts defmodule AdventOfCode.Solution.Year2022.Day05 do def part1(input), do: solve(input, &Enum.reverse/1) def part2(input),...

    Elixir

    Most of the work for this one was parsing the input, IMO!

    Both parts
    defmodule AdventOfCode.Solution.Year2022.Day05 do
      def part1(input), do: solve(input, &Enum.reverse/1)
      def part2(input), do: solve(input, &Function.identity/1)
    
      def solve(input, crane_fn) do
        {stacks, instrs} = parse(input)
    
        instrs
        |> Enum.reduce(stacks, &move(&1, &2, crane_fn))
        |> Enum.sort_by(fn {stack_label, _stack} -> stack_label end)
        |> Enum.map(fn {_stack_label, [crate | _]} -> crate end)
        |> to_string()
      end
    
      defp move({source, dest, count}, stacks, crane_fn) do
        {to_move, to_remain} = Enum.split(stacks[source], count)
    
        stacks
        |> Map.put(source, to_remain)
        |> Map.update!(dest, &Enum.concat(crane_fn.(to_move), &1))
      end
    
      defp parse(input) do
        [stack_lines, instr_lines] = String.split(input, "\n\n")
        {parse_stacks(stack_lines), parse_instrs(instr_lines)}
      end
    
      # Assumes crate labels are always 1 char
      # Assumes stack labels are always 1 digit and listed in order: "1 2 ... 9"
      defp parse_stacks(stack_lines) do
        stack_lines
        |> String.split("\n", trim: true)
        # Faster to build lists tail-first
        |> Enum.reverse()
        # Skip last line with stack labels
        |> Enum.drop(1)
        |> Enum.map(&parse_stacks_row/1)
        |> Enum.reduce(fn stacks_row, stacks ->
          Map.merge(stacks, stacks_row, fn _stack_label, stack, [crate] -> [crate | stack] end)
        end)
      end
    
      defp parse_stacks_row(line) do
        line
        |> String.to_charlist()
        |> Enum.drop(1)
        |> Enum.take_every(4)
        |> Enum.with_index(fn crate, i -> {i + 1, [crate]} end)
        |> Enum.reject(&match?({_stack_label, ' '}, &1))
        |> Map.new()
      end
    
      defp parse_instrs(instr_lines) do
        instr_lines
        |> String.split("\n", trim: true)
        |> Enum.map(fn line ->
          ~r/^move (\d+) from (\d) to (\d)$/
          |> Regex.run(line, capture: :all_but_first)
          |> Enum.map(&String.to_integer/1)
          |> then(fn [count, from_label, to_label] -> {from_label, to_label, count} end)
        end)
      end
    end
    
    1 vote
  13. soks_n_sandals
    Link
    Took me a while to get the arrays transposed in bash, but I did it all in native bash which is a big step up in the last few days! Both solutions run at or less than 0.1s, which I will happily...

    Took me a while to get the arrays transposed in bash, but I did it all in native bash which is a big step up in the last few days! Both solutions run at or less than 0.1s, which I will happily take. It was my first time working with associative arrays in bash.

    Part 1+2
    #!/usr/bin/env bash
    
    file1=day5_1.dat
    file2=day5_2.dat
    part=1
    
    # if [[ ${part} -eq 1 ]]; then
    # /////////////////////////////////////////////////////////////////////////////
    #part 1 0.114 VCTFTJQCG
    #part 2 0.07s GCFGLDNJZ
    mapfile -t stackedRows < $file1
    
    # # shopt -s extglob #necessary for match below
    # # stackedRows=("${stackedRows[@]//[[:blank:]]}")
    
    #   -- reorder rows into columns
    length=${#stackedRows[@]}
    i=0
    j=1
    n=0
    cols=()
    declare -A "stacks" #make assosciative array
    #   -- outer loop gets width of line
    while [[ $i -le $length ]]; do 
        width=${#stackedRows[$i]}
    #   -- next loop (j) holds our horiztonal position
        while [[ $j -lt $width ]]; do
            k=0
    #       -- now, for this horizontal position, loop through vertically
            while [[ $k -lt $length ]]; do
                # echo line $(($k+1)) col $(($j+1)) #debug print
                # echo ${stackedRows[k]:j:1}
                cols+=("${stackedRows[k]:j:1}")
                (( k = k + 1))
            done
        ((j = j + 4))
        done
        noSpace="${cols[@]:n:$length}"
        stacks["stack${i}"]=${noSpace// /}
        # echo "${stacks["stack${i}"]}"
        ((n = n + $length))
        ((i = i + 1))
    done
    
    #   --access to stacks 
    # col=stack1
    # echo "${stacks[$col]}"
    # echo "${stacks[stack1]}"
    # or
    # echo "${stacks["stack1"]}"
    
    
    while read -ra line || [ -n "$line" ]; do
        m=0
        move=${line[1]}
        from=${line[3]}
        to=${line[5]}
    
    #   -- zero offset
        ((move = move -1 ))
        ((from = from -1 ))
        ((to = to -1))
    
        # echo move $move from $from to $to
        while [[ m -le $move ]]; do
    #       -- get column we're taking from
            colA="stack${from}"
            fromCol=("${stacks[$colA]}")
    #       -- get column we're adding to
            colB="stack${to}"
            toCol=("${stacks[$colB]}")
    #       -- get the single container we're moving, unless it's 3 at a time...
            if [[ $move -gt 0 && ${part} -eq 2 ]]; then
                ((numCrates=move + 1))
            else
                numCrates=1
            fi
            movingContainer=${fromCol:0:$numCrates}
    
    # # #       -- debug print
            #   echo moving $movingContainer to $toCol
    
    #       -- move to the new column
            toCol="${movingContainer}${toCol}"
    #       -- removing from old one
            z=${#fromCol}
            fromCol=${fromCol:$numCrates:z}
    
    # # #       -- debug print
            # echo after moving we have: $fromCol and $toCol
    
    #       -- update assosciate array 'stacks'
            stacks["$colA"]=${fromCol}
            stacks["$colB"]=${toCol}
    # #       -- debug print
            # echo just checking, we have: ${stacks[$colA]} and ${stacks[$colB]}
    
            ((m = m + 1 ))
    
            if [[ $numCrates -gt 1 ]]; then
                m=$(($move+1))
            fi
    
        done
            # echo next move
    
    done < $file2
    echo Final crates: ${stacks[stack0]:0:1}${stacks[stack1]:0:1}${stacks[stack2]:0:1}${stacks[stack3]:0:1}${stacks[stack4]:0:1}${stacks[stack5]:0:1}${stacks[stack6]:0:1}${stacks[stack7]:0:1}${stacks[stack8]:0:1}
    # /////////////////////////////////////////////////////////////////////////////
    
    1 vote
  14. MeckiSpaghetti
    (edited )
    Link
    Ruby Part 1 require "active_support/all" s,i = File .read("input.txt") .split("\n\n") s = s .split("\n")[..-2] .reverse .map{ |r| (1..r.size).step(4).map{ |e| r[e] } } .transpose .map{ |r|...

    Ruby

    Part 1
    require "active_support/all"
    
    s,i = File
            .read("input.txt")
            .split("\n\n")
    s = s
            .split("\n")[..-2]
            .reverse
            .map{ |r| (1..r.size).step(4).map{ |e| r[e] } }
            .transpose
            .map{ |r| r.delete_if(&:blank?) }
    
    i = i.split("\n")
    
    i.each do |line|
      a, f, t = line.scan(/\d+/).map(&:to_i) 
      a.times { s[t-1] << s[f-1].pop }
    end
    
    p s.map(&:last).join
    
    Part 2
    require "active_support/all"
    
    s,i = File
            .read("input.txt")
            .split("\n\n")
    s = s
            .split("\n")[..-2]
            .reverse
            .map{ |r| (1..r.size).step(4).map{ |e| r[e] } }
            .transpose
            .map{ |r| r.delete_if(&:blank?) }
    
    i = i.split("\n")
    
    i.each do |line|
      a, f, t = line.scan(/\d+/).map(&:to_i) 
      s[t-1] << s[f-1].pop(a)
      s[t-1].flatten!
    end
    
    p s.map(&:last).join
    
    1 vote
  15. Toric
    Link
    90% of the work today was just parsing the input. Once I had that, implementing the moves was pretty straightforward. Driver mod part1; mod part2; mod utilities; fn main() { let _input =...

    90% of the work today was just parsing the input. Once I had that, implementing the moves was pretty straightforward.

    Driver
    mod part1;
    mod part2;
    mod utilities;
    
    fn main() {
        let _input = include_str!("./input.txt");
        let _structured_input = utilities::parse(_input);
    
        println!("Part One");
        println!("Result: {:?}", part1::part1(&_structured_input));
    
        println!("Part Two");
        println!("Result: {:?}", part2::part2(&_structured_input));
    }
    
    Utilities
    use once_cell::sync::Lazy;
    use regex::Regex;
    #[derive(Debug, PartialEq, Eq, Clone)]
    pub struct Move {
        pub to: usize,
        pub from: usize,
        pub number: u8,
    }
    
    #[derive(Debug, PartialEq, Eq, Clone)]
    pub struct WorkArea {
        stacks: Vec<Vec<char>>,
    }
    
    impl WorkArea {
        pub fn new(stacks: Vec<Vec<char>>) -> Self {
            Self { stacks }
        }
        pub fn apply_move_cratemover9000(&mut self, action: &Move) {
            for _ in 0..action.number {
                let cargo = self.stacks.get_mut(action.from - 1).unwrap().pop().unwrap();
                self.stacks.get_mut(action.to - 1).unwrap().push(cargo);
            }
        }
        pub fn apply_move_cratemover9001(&mut self, action: &Move) {
            let mut crane_holder: Vec<char> = Vec::new();
            for _ in 0..action.number {
                let cargo = self.stacks.get_mut(action.from - 1).unwrap().pop().unwrap();
                crane_holder.push(cargo);
            }
            for cargo in crane_holder.iter().rev() {
                self.stacks.get_mut(action.to - 1).unwrap().push(*cargo);
            }
        }
        pub fn get_stacks(&self) -> &Vec<Vec<char>> {
            &self.stacks
        }
    }
    
    pub fn parse(input: &str) -> (WorkArea, Vec<Move>) {
        let mut input = input.split("\n\n");
        let work_area = parse_work_area(input.next().unwrap());
        let moves = parse_moves(input.next().unwrap());
        (work_area, moves)
    }
    
    pub fn parse_work_area(input: &str) -> WorkArea {
        //decode those bottom index numbers
        let index_row = input.lines().rev().next().unwrap();
        //some ascii math and array math to get the second to last char and convert it into a number.
        let index_max: usize = (index_row.as_bytes()[index_row.len() - 2] - b'0') as usize;
        //initalize the work area:
        let mut work_area: Vec<Vec<char>> = Vec::new();
        for _ in 0..index_max {
            work_area.push(Vec::new())
        }
    
        //now parse the rest
        for line in input.lines().rev() {
            for (y, cargo_crate) in line.as_bytes().chunks(4).enumerate() {
                let cargo = cargo_crate[1] as char;
                //easiest way to filter out that last line is just to filter out digits.
                if cargo != ' ' && !cargo.is_ascii_digit() {
                    work_area.get_mut(y).unwrap().push(cargo)
                }
            }
        }
    
        WorkArea::new(work_area)
    }
    
    pub fn parse_moves(input: &str) -> Vec<Move> {
        static PARSE_MOVES_REGEX: Lazy<Regex> =
            Lazy::new(|| Regex::new(r"^move (\d+) from (\d+) to (\d+)$").unwrap());
        input
            .lines()
            .map(|line| {
                let cap = PARSE_MOVES_REGEX.captures(line).unwrap();
                Move {
                    to: cap.get(3).unwrap().as_str().parse().unwrap(),
                    from: cap.get(2).unwrap().as_str().parse().unwrap(),
                    number: cap.get(1).unwrap().as_str().parse().unwrap(),
                }
            })
            .collect()
    }
    
    Part 1
    use crate::utilities::*;
    
    pub fn part1(input: &(WorkArea, Vec<Move>)) -> Vec<char> {
        let (mut work_area, moves) = input.to_owned();
        for r#move in moves {
            work_area.apply_move_cratemover9000(&r#move)
        }
        work_area
            .get_stacks()
            .iter()
            .map(|stack| stack.last().unwrap().to_owned())
            .collect()
    }
    
    Part 2
    use crate::utilities::*;
    
    pub fn part2(input: &(WorkArea, Vec<Move>)) -> Vec<char> {
        let (mut work_area, moves) = input.to_owned();
        for r#move in moves {
            work_area.apply_move_cratemover9001(&r#move)
        }
        work_area
            .get_stacks()
            .iter()
            .map(|stack| stack.last().unwrap().to_owned())
            .collect()
    }
    
    1 vote
  16. whispersilk
    Link
    I'm using Rust this year, and trying to keep it std-only throughout the month. Part 1 use std::error::Error; use std::fs::File; use std::io::Read; fn main() -> Result<(), Box<dyn Error>> { let mut...

    I'm using Rust this year, and trying to keep it std-only throughout the month.

    Part 1
    use std::error::Error;
    use std::fs::File;
    use std::io::Read;
    
    fn main() -> Result<(), Box<dyn Error>> {
    	let mut file = File::open("input_5.txt")?;
    	let mut contents = String::new();
    	file.read_to_string(&mut contents)?;
    	let mut sections = contents.splitn(2, "\n\n");
    	let stacks = sections.next().unwrap();
     	let instructions = sections.next().unwrap();
    	let mut stacks = get_stacks(stacks);
    	move_boxes(&mut stacks, instructions);
    	let mut top_boxes = String::with_capacity(stacks.len());
    	for stack in stacks.iter() {
    		top_boxes.push(*stack.last().unwrap());
    	}
    	println!("{top_boxes}");
    	Ok(())
    }
    
    fn get_stacks(stack_section: &str) -> Vec<Vec<char>> {
    	let num_stacks = (stack_section.lines().last().unwrap().len() + 1) / 4;
    	let mut stacks = Vec::with_capacity(num_stacks);
    	for _ in 0..num_stacks {
    		stacks.push(Vec::new());
    	}
    	for line in stack_section.lines().rev().skip(1) {
    		for idx in 0..num_stacks {
    			let c = line.chars().nth(1 + 4 * idx).unwrap();
    			if c != ' ' {
    				stacks[idx].push(c);
    			}
    		}
    	};
    	stacks
    }
    
    fn move_boxes(stacks: &mut Vec<Vec<char>>, instructions: &str) {
    	for line in instructions.lines() {
    		let mut pieces = line.split(' ').skip(1).step_by(2);
    		let iterations = pieces.next().unwrap().parse::<usize>().unwrap();
    		let from_stack = pieces.next().unwrap().parse::<usize>().unwrap() - 1;
    		let to_stack = pieces.next().unwrap().parse::<usize>().unwrap() - 1;
    		for _ in 0..iterations {
    			let c = stacks[from_stack].pop().unwrap();
    			stacks[to_stack].push(c);
    		}
    	}
    }
    
    Part 2
    use std::error::Error;
    use std::fs::File;
    use std::io::Read;
    
    fn main() -> Result<(), Box<dyn Error>> {
    	let mut file = File::open("input_5.txt")?;
    	let mut contents = String::new();
    	file.read_to_string(&mut contents)?;
    	let mut sections = contents.splitn(2, "\n\n");
    	let stacks = sections.next().unwrap();
     	let instructions = sections.next().unwrap();
    	let mut stacks = get_stacks(stacks);
    	move_boxes(&mut stacks, instructions);
    	let mut top_boxes = String::with_capacity(stacks.len());
    	for stack in stacks.iter() {
    		top_boxes.push(*stack.last().unwrap());
    	}
    	println!("{top_boxes}");
    	Ok(())
    }
    
    fn get_stacks(stack_section: &str) -> Vec<Vec<char>> {
    	let num_stacks = (stack_section.lines().last().unwrap().len() + 1) / 4;
    	let mut stacks = Vec::with_capacity(num_stacks);
    	for _ in 0..num_stacks {
    		stacks.push(Vec::new());
    	}
    	for line in stack_section.lines().rev().skip(1) {
    		for idx in 0..num_stacks {
    			let c = line.chars().nth(1 + 4 * idx).unwrap();
    			if c != ' ' {
    				stacks[idx].push(c);
    			}
    		}
    	};
    	stacks
    }
    
    fn move_boxes(stacks: &mut Vec<Vec<char>>, instructions: &str) {
    	for line in instructions.lines() {
    		let mut pieces = line.split(' ').skip(1).step_by(2);
    		let iterations = pieces.next().unwrap().parse::<usize>().unwrap();
    		let from_stack = pieces.next().unwrap().parse::<usize>().unwrap() - 1;
    		let to_stack = pieces.next().unwrap().parse::<usize>().unwrap() - 1;
    		let start_index = stacks[from_stack].len() - iterations;
    		for _ in 0..iterations {
    			let c = stacks[from_stack].remove(start_index);
    			stacks[to_stack].push(c);
    		}
    	}
    }
    
    1 vote