8 votes

Day 13: Distress Signal

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

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>

7 comments

  1. [4]
    tjf
    Link
    My Python solutions. A somewhat easier problem is welcome on a Monday night! Part 1 #!/usr/bin/env pypy3 import ast import sys def ordered(l, r): if isinstance(l, int) and isinstance(r, int):...

    My Python solutions. A somewhat easier problem is welcome on a Monday night!

    Part 1
    #!/usr/bin/env pypy3
    
    import ast
    import sys
    
    def ordered(l, r):
        if isinstance(l, int) and isinstance(r, int):
            return l < r or (False if l > r else None)
        elif isinstance(l, list) and isinstance(r, list):
            for ll, rr in zip(l, r):
                if (result := ordered(ll, rr)) is not None:
                    return result
            return len(l) < len(r) or (False if len(r) < len(l) else None)
        else:
            return ordered([l], r) if isinstance(l, int) else ordered(l, [r])
    
    def main():
        pairs = [map(ast.literal_eval, section.split('\n')[:2])
                 for section in sys.stdin.read().split('\n\n')]
        total = sum(pairs.index(p) + 1 for p in pairs if ordered(*p))
        print(total)
    
    if __name__ == '__main__':
        main()
    

    Initially I wanted to use Python's sorted() built-in, which optionally takes a key function (though key must take one argument instead of two). Instead I just rolled my own bubble sort. 🤷

    Part 2
    #!/usr/bin/env pypy3
    
    import ast
    import math
    import sys
    
    DIVIDER_PACKETS = [[[2]], [[6]]]
    
    def ordered(l, r):
        if isinstance(l, int) and isinstance(r, int):
            return l < r or (False if l > r else None)
        elif isinstance(l, list) and isinstance(r, list):
            for ll, rr in zip(l, r):
                if (result := ordered(ll, rr)) is not None:
                    return result
            return len(l) < len(r) or (False if len(r) < len(l) else None)
        else:
            return ordered([l], r) if isinstance(l, int) else ordered(l, [r])
    
    def sort_packets(packets):
        n = len(packets)
        for i in range(n - 1):
            for j in range(n - i - 1):
                if not ordered(packets[j], packets[j + 1]):
                    packets[j], packets[j + 1] = packets[j + 1], packets[j]
    
    def main():
        packets = [ast.literal_eval(line.strip()) for line in sys.stdin
                   if line != '\n'] + DIVIDER_PACKETS
        sort_packets(packets)
        key = math.prod(packets.index(p) + 1 for p in DIVIDER_PACKETS)
        print(key)
    
    if __name__ == '__main__':
        main()
    
    4 votes
    1. [3]
      bhrgunatha
      Link Parent
      Would adding the markers and using zip(l, l[1:]) work? I suppose you'd have to alter ordered to use a tuple instead of 2 arguments.

      which optionally takes a key function (though key must take one argument instead of two)

      Would adding the markers and using zip(l, l[1:]) work?
      I suppose you'd have to alter ordered to use a tuple instead of 2 arguments.

      1 vote
      1. [2]
        tjf
        Link Parent
        I think the best way to adapt my comparison function would be to return one of {1, 0, -1} instead of {True, None, False}, and then wrap it in functools.cmp_to_key. I saw this in someone else's...

        I think the best way to adapt my comparison function would be to return one of {1, 0, -1} instead of {True, None, False}, and then wrap it in functools.cmp_to_key. I saw this in someone else's solution, but I didn't know about cmp_to_key before so it didn't occur to me when solving.

        1 vote
  2. bhrgunatha
    (edited )
    Link
    Part 1 This is the end of quite a nasty logical struggle. I knew exactly what I needed to do but slipped up so many times on the way. I'm sorry tildes, I've cheated! I didn't parse anything, I let...
    Part 1

    This is the end of quite a nasty logical struggle. I knew exactly what I needed to do but slipped up so many times on the way.

    I'm sorry tildes, I've cheated!
    I didn't parse anything, I let Racket read the lists - after getting rid of those garbage ,s. Why won't other languages see the light that list items are space separated?

    (define (part-01 input)
      (for/fold ([ordered 0])
                ([pair (in-slice 2 (in-list (read-packets input)))]
                 [packet (in-naturals 1)])
        (+ ordered (if (apply packet<? pair) packet 0))))
    
    (define (read-packets input)
      (let next-packet
          ([p (open-input-string (string-replace input "," " "))]
           [xs null])
        (define x (read p))
        (cond [(eof-object? x)
               (close-input-port p)
               (reverse xs)]
              [(next-packet p (cons x xs))])))
    
    (define (packet<? l r)
      (= 1 (compare-packets l r)))
    
    (define (compare-packets l r)
      (match* (l r)
        [((? number? l) (? number? r)) (cmp/num l r)]
        [((? number? l) (? list? r)) (compare-packets (list l) r)]
        [((? list? l) (? number? r)) (compare-packets l (list r))]
        [((? list? l) (? list? r)) (cmp/list l r)]
        [(_ _) (error 'compare-packets "HWAT ~a\n~a" l r)]))
    
    (define (cmp/list l r)
      (or
       (for/first ([nl (in-list l)]
                   [nr (in-list r)] #:unless (zero? (compare-packets nl nr)))
         (compare-packets nl nr))
       (cmp/num (length l) (length r))))
    
    (define (cmp/num l r)
      (cond [(< l r) 1]
            [(> l r) -1]
            [else 0]))
    
    Part 2

    No need to sort, just count the packets less than the 2 and 6 dividers. I am a bit sneaky though since I'm assuming there is at least 1 item less than the 2 divider, so start counting them at 1 and 2 respectively.

    (define (part-02 input)
      (define div2 '((2)))
      (define div6 '((6)))
      (for/fold ([less/2 1]
                 [less/6 2]
                 #:result (* less/2 less/6))
                ([l (in-list (read-packets input))])
        (values (if (packet<? l div2) (add1 less/2) less/2)
                (if (packet<? l div6) (add1 less/6) less/6))))
    
    Refactor

    Unhappy with read-packets checking for eof-object? like some kind of feral beast, then I remembered in-port

    (define (read-packets input)
      (with-input-from-string (regexp-replace* #rx"[,\n]" input " ")
        (thunk (for/list ([p (in-port)]) p))))
    

    much nicer.
    It works because (of course) Racket's read already knows very well how to deal with lists! You can use () [] or even {} for lists.


    Although this is (surprisingly to me) slower I prefer this reworked version of packet<? because SICP ❤️‍🔥

    (define (packet<? l r)
      (match* (l r)
        [((? number? l) (? number? r)) (if (= l r) 'equal (< l r))]
        [((? number? l) (? list? r))   (packet<? (list l) r)]
        [((? list? l)   (? number? r)) (packet<? l (list r))]
        [('() '())  'equal]
        [('() _)     #t]
        [(_ '())     #f]
        [(_ _) (case (packet<? (first l) (first r))
                 [(equal) (packet<? (rest l) (rest r))]
                 [(#t) #t]
                 [(#f) #f])]))
    
    2 votes
  3. jzimbel
    Link
    Elixir Pattern matching + multiple function clauses helped me separate the different cases out in my recursive in_order?/2 function. The puzzle's comparison method is actually very close to...

    Elixir

    Pattern matching + multiple function clauses helped me separate the different cases out in my recursive in_order?/2 function. The puzzle's comparison method is actually very close to Elixir/Erlang's default term ordering for lists—elements are compared in order left to right until a "tie-breaker" is found. I almost could have gotten away with simply comparing the lists with l < r! But the case where you wrap a number in a list when comparing it to another list prevented me from getting to cheat that way, unfortunately. Oh well :)

    In a similar vein, all atoms except nil (aka :nil) are truthy, so having the comparator return :tie when two lists are "equal" results in the correct sorting when it's passed to Enum.sort/2.

    I used Code.eval_string/1 to convert the input lines into lists (after doing a basic check that the string is composed only of [ ] , and digits), which saved some time.

    Both parts
    defmodule AdventOfCode.Solution.Year2022.Day13 do
      def part1(input) do
        input
        |> String.split("\n", trim: true)
        |> Enum.map(&parse_packet/1)
        |> Enum.chunk_every(2)
        |> Enum.with_index(1)
        |> Enum.filter(fn {[l, r], _i} -> in_order?(l, r) end)
        |> Enum.map(fn {_pair, i} -> i end)
        |> Enum.sum()
      end
    
      @divider_packets [[[2]], [[6]]]
    
      def part2(input) do
        input
        |> String.split("\n", trim: true)
        |> Enum.map(&parse_packet/1)
        |> Enum.concat(@divider_packets)
        |> Enum.sort(&in_order?/2)
        |> Enum.with_index(1)
        |> Enum.filter(fn {packet, _i} -> packet in @divider_packets end)
        |> Enum.map(fn {_packet, i} -> i end)
        |> Enum.product()
      end
    
      defp parse_packet(line) do
        if Regex.match?(~r/^[\[\],\d]+$/, line),
          do: elem(Code.eval_string(line), 0),
          else: raise("I ain't eval-ing that")
      end
    
      # out of values to compare; should only occur in nested lists
      defp in_order?([], []), do: :tie
    
      # lists are different lengths and we've reached the end of one
      defp in_order?([], [_ | _]), do: true
      defp in_order?([_ | _], []), do: false
    
      # both values are integers
      defp in_order?([n | ltail], [n | rtail]) when is_integer(n), do: in_order?(ltail, rtail)
      defp in_order?([ln | _], [rn | _]) when is_integer(ln) and is_integer(rn), do: ln < rn
    
      # both values are lists
      defp in_order?([ll | ltail], [rl | rtail]) when is_list(ll) and is_list(rl) do
        case in_order?(ll, rl) do
          :tie -> in_order?(ltail, rtail)
          result -> result
        end
      end
    
      # one value is an integer
      defp in_order?([n | ltail], r) when is_integer(n), do: in_order?([[n] | ltail], r)
      defp in_order?(l, [n | rtail]) when is_integer(n), do: in_order?(l, [[n] | rtail])
    end
    
    1 vote
  4. wycy
    Link
    Python Python #!/usr/bin/env python3 import sys from typing import List from typing import Tuple from itertools import zip_longest from functools import cmp_to_key RAN_OUT = -9999999999 def...

    Python

    Python
    #!/usr/bin/env python3
    import sys
    from typing import List
    from typing import Tuple
    from itertools import zip_longest
    from functools import cmp_to_key
    
    RAN_OUT = -9999999999
    
    def get_data1(filename: str) -> List[str]:
        with open(filename,'r') as f:
            lines = f.read()
        lines = lines.split("\n\n")
        return lines
    
    def get_data2(filename: str) -> List[str]:
        with open(filename,'r') as f:
            lines = f.read().splitlines()
        return [l for l in lines if l != '']
    
    def compare_packets(p1,p2) -> int:
        for v1,v2 in zip_longest(p1,p2,fillvalue=RAN_OUT):
            if v1 == RAN_OUT: return -1
            if v2 == RAN_OUT: return +1
            if type(v1) == int and type(v2) == int:
                if v1 < v2: return -1
                if v1 > v2: return +1
            elif type(v1) == list and type(v2) == list:
                outcome = compare_packets(v1,v2)
                if outcome is not None: return outcome
            elif type(v1) == list and type(v2) == int:
                outcome = compare_packets(v1,[v2])
                if outcome is not None: return outcome
            elif type(v1) == int and type(v2) == list:
                outcome = compare_packets([v1],v2)
                if outcome is not None: return outcome
            else:
                quit(f"Error: Unknown types: v1={type(v1)}, v2={type(v2)}")
    
    def main():
    
        # Part 1
        input = get_data1(sys.argv[1])
        part1 = 0
        for ix,pair in enumerate(input):
            parts = pair.split("\n")
            first,second = parts[0], parts[1]
    
            first  = eval(first)
            second = eval(second)
    
            if compare_packets(first,second) == -1:
                part1 += ix + 1
     
        print(f"Part 1: {part1}") # 5760
    
        # Part 2
        input = get_data2(sys.argv[1])
        input.append("[[2]]")
        input.append("[[6]]")
        packets = [eval(p) for p in input]
        packets = sorted(packets, key=cmp_to_key(compare_packets))
        
        part2 = 1
        for ix,packet in enumerate(packets):
            if packet == [[2]]: part2 *= (ix+1)
            if packet == [[6]]: part2 *= (ix+1)
        print(f"Part 2: {part2}") # 26670
    
    
    if __name__=="__main__":
        main()
    
    1 vote