14 votes

Day 10: Adapter Array

Today's problem description: https://adventofcode.com/2020/day/10


Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c.

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>

17 comments

  1. [4]
    PapaNachos
    Link
    I completely missed the start because I got distracted by Cyberpunk 2077. Whoo! But I took a nice break from shooting baddies to do some math and coding. Day 10 Part A – Python For part A I...
    • Exemplary

    I completely missed the start because I got distracted by Cyberpunk 2077. Whoo! But I took a nice break from shooting baddies to do some math and coding.

    Day 10 Part A – Python

    For part A I figured I would do something different and use a dataframe and take advantage of some of the built in functions. It worked pretty well.

    import numpy as np
    import pandas as pd
    
    data = test_data_2.split('\n')
    #data = input_data.split('\n')
    
    data = list(map(int, data))
    data.append(0)
    data.append(max(data) + 3)
    data.sort()
    
    df = pd.DataFrame(data)
    df['diff'] = df.diff()
    df['diff'].value_counts()
    
    #print(df)
    
    Day 10 Part B – Python

    After bashing my head against it for like half an hour, I ended up scrapping my dataframe so that I could do the computations I needed. Basically I look for the length of each 'streak' of consecutive adapters. These are ones that could possibly have one or more of them removed. I find the length of the different streaks and use it to to some multiplication at the end. I discovered that a streak of length '2' would give 2 options. A streak of length 3 would have 2^2 = 4 options, and a streak of length 4 would give 2^3 - 1 = 7 options. The minus 1 represents a jump of > 3, which our adapters don't allow, so it gets dropped. Thankfully we didn't have to deal with any streaks of length > 3.

    import numpy as np
    import pandas as pd
    
    #data = test_data.split('\n')
    #data = test_data_2.split('\n')
    data = input_data.split('\n')
    
    data = list(map(int, data))
    data.append(0)
    data.append(max(data) + 3)
    data.sort()
    
    streaks = []
    streak_len = 0
    current_row = 0
    for row in data:
        diff = row - current_row
        if diff == 1:
            streak_len = streak_len + 1
        else:
            streaks.append(streak_len)
            streak_len = 0
        current_row = row
    print(streaks)
    print(max(streaks))
    answer = 1
    answer = answer * 7**streaks.count(4)
    answer = answer * 4**streaks.count(3)
    answer = answer * 2**streaks.count(2)
    
    print(answer)
    
    
    Tips and Commentary
    • See if however you're storing your data has a built in way to sort it

    • For the love of god, do not try to brute force part B. It will take a LONG time. My answer was on the order of 24 trillion and yours might be higher if you're unlucky

    • Prime factorization was what helped me figure out part B. Wolfram Alpha can do prime factorization for you

    • 2 = 2^1, 4 = 2^2, 8 = 2^3

    • 8 - 1 = 7. Yes, this is actually a hint. I'm serious.

    6 votes
    1. [3]
      3d12
      (edited )
      Link Parent
      I finally got my recursive solution running, after realizing I can optimize away returning the array of all the combinations (because that ran through memory like you wouldn't believe) in favor of...

      I finally got my recursive solution running, after realizing I can optimize away returning the array of all the combinations (because that ran through memory like you wouldn't believe) in favor of returning a total of the combinations. I kicked it off not long ago, and it's just passing 57 million combinations.

      I cheated and peeked at your solution instead of doing the math myself, and I think my penance will be to leave this running on my crappy single-core 1.6gHz dev machine instead of refactoring. I hope it will be done before the competition is over...

      edit: It won't. After running approximately 48 hours, I stopped it around sequence 24,062,200,000 -- at that rate, there's no way I'm brute-forcing that answer by the 25th. Plan B for penance: Work out the maths myself, instead of implementing someone else's answer. :)

      5 votes
      1. wycy
        Link Parent
        There was a problem in AoC 2018 that required spoilerslinked lists and I didn’t know that and didn’t know how to use them at the time, so I wrote a naive array solution that ended up having a run...

        There was a problem in AoC 2018 that required

        spoilerslinked lists
        and I didn’t know that and didn’t know how to use them at the time, so I wrote a naive array solution that ended up having a run time of 32 hours despite being -O3 Fortran. Still got the right answer though 🤙

        I actually ended up writing a proper solution before it finished running after researching how to do it, but still. Array solution worked eventually.

        All that said, 57 million is a tiny fraction of a trillion. Hope it finishes 😬

        4 votes
      2. PapaNachos
        Link Parent
        That's both terrifying and impressive. I'm curious to know how long it will take

        That's both terrifying and impressive. I'm curious to know how long it will take

        3 votes
  2. weemadarthur
    Link
    Using functools.lru_cache almost feels like cheating (both parts finish in 300 ms). Part 2 ratings = set([int(r) for r in data.split('\n')]) max_rating = max(ratings) builtin_rating = max_rating +...

    Using functools.lru_cache almost feels like cheating (both parts finish in 300 ms).

    Part 2
        ratings = set([int(r) for r in data.split('\n')])
        max_rating = max(ratings)
        builtin_rating = max_rating + 3
    
        @functools.lru_cache
        def find_next_adapter(current):
            if current == max_rating:
                return 1
            count = 0
            for difference in (1, 2, 3):
                if (current + difference) in ratings:
                    count += find_next_adapter(current + difference)
            return count
    
        print(find_next_adapter(0))
    
    5 votes
  3. [2]
    Comment deleted by author
    Link
    1. Crespyl
      Link Parent
      I got the first sample for part 2 to work, but haven't finished working out the math to make my solution work correctly for the second sample and my real input. discussion By looping through the...

      I got the first sample for part 2 to work, but haven't finished working out the math to make my solution work correctly for the second sample and my real input.

      discussion By looping through the list of adapters three at a time, it's possible to find and remove all the unnecessary adapters (any time you see `a, b, c` where the difference between `a` and `c` is less/equal to 3, you can remove `b` from the set.

      This gets you the minimal valid solution, and a list/count of removable items. I believe it should be possible to use this information to directly compute the number of permutations very quickly, but I'm still trying to remember/derive the math for that.

      It's also possible that I'm chasing a dead end and am completely off base.

      2 votes
  4. Crespyl
    (edited )
    Link
    Ruby This one took me a long while, first part was easy enough, but I ended up getting stuck on the second part. Part 1 def find_adapter(available, target) available .map { |x| [x - target, x] }...

    Ruby

    This one took me a long while, first part was easy enough, but I ended up getting stuck on the second part.

    Part 1
    def find_adapter(available, target)
      available
        .map { |x| [x - target, x] }
        .sort
        .first
    end
    
    def compute_p1(input)
      adapters = input.lines.map(&:to_i).sort
      adapters << adapters.max + 3
    
      target = 0
      diff_counts = Hash.new(0)
      while ! adapters.empty?
        diff, a = find_adapter(adapters, target)
        #puts "%i -> %i (%i)" % [target, a, diff]
        diff_counts[diff] += 1
        adapters.delete(a)
        target = a
      end
    
      #puts diff_counts
      return diff_counts[1] * diff_counts[3]
    end
    
    Part 2 I started off trying to find the set of removable adapters and figure the permutations directly from there, but that wasn't working and I banged my head on that wall for a while, before stepping back and looking at each *group* of removable items. Any run of 2 or more single-steps brings a number of choices that grows in a predictable sequence.

    Put generally we have: 0 -> 1 (n/a no action); 1 -> 1 (not removable); 2 -> 2 (you can remove either); 3 -> 4 (remove any or all); 4 -> 7 (remove any, all, or two sets of three). This was enough to build out the rest of the sequence, with each new number being the sum of the last three. I ended up pre-computing the first 10 numbers in the sequence, but I'm pretty sure these inputs only actually need the values for 1-4.

    def compute_p2(input)
      input = input.lines.map(&:to_i)
      adapters = [0] + input.sort + [input.max + 3]
    
      groups = adapters[...-1].zip(adapters[1...])
                 .reduce([]) { |steps, pair| steps << pair[1]-pair[0] }
                 .reduce([]) do |groups, n|
                   if groups.last && groups.last.last == n
                     groups.last << n
                   else
                     groups << [n]
                   end
                   groups
                 end
    
      group_counts = groups
                       .find_all { |g| g.first == 1 }
                       .map { |g| g.size }
                       .each_with_object(Hash.new(0)) { |count, h| h[count] += 1 }
    
      # options per group size scale with sum of previous 3, so we precompute table of options per group size
      table = [1, 1, 2]
      while table.size < 10
        table << table[-3..].sum
      end
    
      return group_counts.reduce(1) { |product, kv| product * table[kv[0]] ** kv[1] }
    end
    

    Inspired by a streamer I like to watch VODs of after I finish each puzzle, I added a little test suite that checks my implementation against the provided sample cases before running it against the full input. It also adds benchmarking, but so far everything's been fast enough for that not to matter much. You can see it in the full implementation here.

    4 votes
  5. clone1
    Link
    Mit scheme. Part 1 & 2 For part two I started out with a recursive memoized approach but then after messing with it I realized only that last three values could matter, so I keep a list of three...

    Mit scheme.

    Part 1 & 2 For part two I started out with a recursive memoized approach but then after messing with it I realized only that last three values could matter, so I keep a list of three values corresponding to the adapters (-3, -2, -1) relative of the current adapter. I shift it at the beginning of each iteration to correct it if the difference between the current and previous adapter is greater than one, and at the end by one.
    (define (get-lines parse-line f)
      (with-input-from-file f
        (lambda ()
          (let loop ((line (read-line))
                     (lines '()))
            (if (eof-object? line)
                (reverse lines)
                (loop (read-line)
                      (cons (parse-line line) lines)))))))
    
    (define (one adapters)
      (let loop ((3-count 1)
                 (1-count 0)
                 (prev 0)
                 (remaining adapters))
        (if (null? remaining) (* 3-count 1-count)
            (let* ((x (car remaining))
                   (remaining (cdr remaining))
                   (diff (- x prev)))
              (case diff
                ((1) (loop 3-count (+ 1-count 1) x remaining))
                ((3) (loop (+ 3-count 1) 1-count x remaining))
                (else (loop 3-count 1-count x remaining)))))))
    
    (define (cdr-n l n)
      (if (= n 0) l
          (cdr-n (cdr l) (- n 1))))
    
    (define (sum lst)
      (fold + 0 lst))
    
    (define (two adapters)
      (let loop ((adapters adapters)
                 ;; List of the amount of paths for the last 3 adapters
                 (prev-list '(0 0 1))
                 (prev-val 0))
        (if (null? adapters)
            ;; The plug is 3 away so the answer is the last item in the list
            (caddr prev-list)
            (let* ((x (car adapters))
                   (shift-val (- x prev-val 1))
                   (prev-list (cdr-n prev-list shift-val))
                   (x-val (sum prev-list)))
              (loop (cdr adapters)
                    (append (cdr prev-list)
                            (iota shift-val 0 0)
                            (list x-val))
                    x)))))
    
    (define (run lines)
      (let ((lines (sort lines <)))
        (display "One: ") (display (one lines)) (newline)
        (display "Two: ") (display (two lines)) (newline)))
    
    (run (get-lines string->number "input"))
    
    4 votes
  6. pnutzh4x0r
    Link
    Python Repo Link Part 1 This was relatively straightforward once I understood it. Simply sort the adapters (including the 0 port and the max(adapters) + 3 device port) and then count the...

    Python

    Repo Link

    Part 1

    This was relatively straightforward once I understood it. Simply sort the adapters (including the 0 port and the max(adapters) + 3 device port) and then count the differences between each adapter.

    import collections
    import sys
    
    # Functions
    
    def count_differences(adapters):
        return collections.Counter(
            adapters[i] - adapters[i - 1] for i in range(1, len(adapters))
        )
    
    # Main Execution
    
    def main():
        adapters    = [int(a) for a in sys.stdin]
        adapters    = sorted([0] + adapters + [max(adapters) + 3])
        differences = count_differences(adapters)
    
        print(differences[1] * differences[3])
    
    if __name__ == '__main__':
        main()
    
    Part 2

    This took me a bit longer because I didn't want to simply brute-force it. I knew there was a dynamic programming solution, but I had to draw it out on a piece of paper before I saw the appropriate relationship.

    The idea is that for each adapter, you simply need to look backwards and consider how many ways you could have arrived at that joltage. So the recurrence relationship is: counts(adapter) = sum(counts(adapter - d) where d is 1, 2, or 3).

    import sys
    
    # Functions
    
    def count_arrangements(adapters):
        counts = {0: 1}
    
        for adapter in adapters[1:]:
            counts[adapter] = sum(counts.get(adapter - d, 0) for d in (1, 2, 3))
    
        return counts
    
    # Main Execution
    
    def main():
        adapters     = [int(a) for a in sys.stdin]
        adapters     = sorted([0] + adapters + [max(adapters) + 3])
        arrangements = count_arrangements(adapters)
    
        print(arrangements[max(adapters)])
    
    if __name__ == '__main__':
        main()
    
    4 votes
  7. jzimbel
    (edited )
    Link
    Elixir Always fun when you can solve the whole thing without a single branching expression (if, cond, case). Thanks, useful standard library functions! This one also runs pretty fast—65 μs for...

    Elixir

    Always fun when you can solve the whole thing without a single branching expression (if, cond, case). Thanks, useful standard library functions!

    This one also runs pretty fast—65 μs for part 1 and 80 μs for part 2.

    Elixir, parts 1 and 2
    defmodule AdventOfCode.Day10 do
      def part1(args) do
        args
        |> parse_adapters()
        |> Enum.chunk_every(2, 1, :discard)
        |> Enum.frequencies_by(fn [a, b] -> b - a end)
        |> Map.take([1, 3])
        |> Map.values()
        |> Enum.reduce(&Kernel.*/2)
      end
    
      def part2(args) do
        args
        |> parse_adapters()
        # Skip the 0-jolt outlet and just init the accumulator with its (known)
        # result since it would require a special case in the reducer function.
        |> Enum.drop(1)
        |> Enum.reduce({1, %{0 => 1}}, fn n, {_, prev_counts} ->
          path_count =
            prev_counts
            |> Map.take([n - 3, n - 2, n - 1])
            |> Map.values()
            |> Enum.sum()
    
          {path_count, Map.put(prev_counts, n, path_count)}
        end)
        |> elem(0)
      end
    
      defp parse_adapters(input) do
        input
        |> String.split()
        |> Enum.map(&String.to_integer/1)
        |> add_outlet_and_builtin()
        |> Enum.sort()
      end
    
      defp add_outlet_and_builtin(adapters) do
        [0, Enum.max(adapters) + 3 | adapters]
      end
    end
    
    4 votes
  8. Gyrfalcon
    Link
    Language: Julia Repository The first part seemed too easy, the second part I will admit I didn't have a good way to get the runtime down until I let myself follow in the footsteps of @weemadarthur...

    The first part seemed too easy, the second part I will admit I didn't have a good way to get the runtime down until I let myself follow in the footsteps of @weemadarthur and implemented memoization. I did try to parallelize first but I definitely did it wrong and spawned so many processes my computer locked up!

    Part 1
    function main()
    
        adapters = []
        open("Day10/input.txt") do fp
            adapters = parse.(Int, readlines(fp))
        end
    
        # Is this as easy as I think it is?
        adapters = [0, adapters..., maximum(adapters) + 3]
        sort!(adapters)
        differences = diff(adapters)
        numOnes = count(differences .== 1)
        numThrees = count(differences .== 3)
    
        result_1 = numOnes * numThrees
    
        # It is
        println("Result 1 is ", result_1)
    
    end
    
    main()
    
    Part 2 diff
    @@ -1,3 +1,5 @@
    +using Memoize
    +
     function main()
     
         adapters = []
    @@ -17,6 +19,29 @@ function main()
         # It is
         println("Result 1 is ", result_1)
     
    +    result_2 = arrange_adapters(Tuple(adapters))
    +
    +    println("Result 2 is ", result_2)
    +
     end
     
    -main()
    \ No newline at end of file
    +# Just do this recursively and cache, math is hard
    +@memoize function arrange_adapters(adapters)
    +    if length(adapters) == 1
    +        return 1
    +    elseif length(adapters) < 4
    +        num_ways = 0
    +        next_steps = sum(adapters[2:end] .<= adapters[1] + 3)
    +        for idx = (1:next_steps) .+ 1
    +            num_ways += arrange_adapters(adapters[idx:end])
    +        end
    +        return num_ways
    +    else
    +        num_ways = 0
    +        next_steps = sum(adapters[2:4] .<= adapters[1] + 3)
    +        for idx = (1:next_steps) .+ 1
    +            num_ways += arrange_adapters(adapters[idx:end])
    +        end
    +        return num_ways
    +    end
    +end
    

    I don't think I'll keep up with the performance comparisons, as I don't think they do much interesting. This one does chug a little bit though, I get the sense that my penchant for loops in recursive functions does a number on the Julia compiler. Either way, they are easy to come up with and perform well enough here.

    4 votes
  9. Nuolong
    Link
    This problem was really confusing for me to understand at first, though what it was actually asking wasn't complicated. Shows how much I need to improve my reading comprehension. Repo link Part 1...

    This problem was really confusing for me to understand at first, though what it was actually asking wasn't complicated. Shows how much I need to improve my reading comprehension.

    Repo link

    Part 1 Standard, manual counting
    #!/usr/bin/env python3
    
    import sys
    
    def find_jolts(nums):
        diff_1 = 0
        diff_3 = 1  # for last-to built-in joltage
        start = 0
    
        while nums:
            curr = nums.pop(0)
            if start + 1 == curr:
                diff_1 += 1
                start = curr
            elif start + 3 == curr:
                diff_3 += 1
                start = curr
    
        return diff_1 * diff_3
    
    
    def main():
        nums = []
        for num in sys.stdin:
            nums.append(int(num))
    
        nums.sort()
        print(find_jolts(nums))
    
    if __name__ == '__main__':
        main()
    
    Part 2 Solution using memoization.
    #!/usr/bin/env python3
    
    import sys
    
    def count_seq(n, nums):
        mem = [0] * (n + 1)
        mem[0] = 1
    
        for i in nums:
            mem[i] = mem[i - 1] + mem[i - 2] + mem[i - 3]
    
        return mem[n]
    
    def main():
        nums = []
        for num in sys.stdin:
            nums.append(int(num))
    
        nums.sort()
        print(count_seq(max(nums), nums))
    
    if __name__ == '__main__':
        main()
    
    3 votes
  10. tomf
    Link
    Welp, this one is very spreadsheet friendly. Sheets! Part 1 =ARRAYFORMULA( COUNTIF( {MIN(A1:A)-0; IF(ISBLANK(INDIRECT("A1:A"&MAX(FILTER(ROW(A1:A),A1:A<>"")))),,...

    Welp, this one is very spreadsheet friendly.

    Sheets!

    Part 1
    =ARRAYFORMULA(
       COUNTIF(
        {MIN(A1:A)-0;
         IF(ISBLANK(INDIRECT("A1:A"&MAX(FILTER(ROW(A1:A),A1:A<>"")))),,
         SORT({FILTER(A1:A,A1:A<>MIN(A1:A));MAX(A1:A)+3})-
         SORT({A1:A;MAX(A1:A)+3}))},1)*
       COUNTIF({MIN(A1:A)-0;
        IF(ISBLANK(A1:A109),,
        SORT({FILTER(A1:A,A1:A<>MIN(A1:A));MAX(A1:A)+3})-
        SORT({A1:A;MAX(A1:A)+3}))},3))
    
    Part 2
    With this part, I have `={0;SORT(A:A)}` in G1:G -- not the prettiest.
    
    Then over in H I run this from 4 on, taking off one piece each for the previous rows
    
    

    =IF(G4-G3<=3,H3,0)+
    IF(G4-G2<=3,H2,0)+
    IF(G4-G1<=3,H1,0)

    </details>
    
    I really wanted to do the second with one formula, but I just couldn't swing it. I have a feeling I could do it with something like MMULT --- but I'm not certain of it. 
    
    3 votes
  11. JRandomHacker
    Link
    Oh hey this is a good excuse to start contributing. Hi to Tildes! C# Part 1 var input = new List<int>(); using (var fs = new StreamReader(Inputs.Year2020.Inputs2020.Input10)) { string line; while...

    Oh hey this is a good excuse to start contributing. Hi to Tildes!
    C#

    Part 1
    var input = new List<int>();
    using (var fs = new StreamReader(Inputs.Year2020.Inputs2020.Input10))
    {
    	string line;
    	while ((line = fs.ReadLine()) != null)
    	{
    		input.Add(int.Parse(line));
    	}
    }
    input.Add(0);
    input.Sort();
    input.Add(input.Last() + 3);
    
    var processed = new List<int>();
    for (int i = 1; i < input.Count; i++)
    {
    	processed.Add(input[i] - input[i - 1]);
    }
    var ones = processed.Count(i => i == 1);
    var threes = processed.Count(i => i == 3);
    
    return $"{ones} ones and {threes} threes = {ones * threes}";
    

    Pretty boilerplate - not even super necessary to actually build the diffs array, but it means I can just throw LINQ at it and type faster.

    Part 2
    var input = new List<int>();
    using (var fs = new StreamReader(Inputs.Year2020.Inputs2020.Input10))
    {
    	string line;
    	while ((line = fs.ReadLine()) != null)
    	{
    		input.Add(int.Parse(line));
    	}
    }
    input.Add(0);
    input.Sort();
    input.Add(input.Last() + 3);
    input.Reverse();
    
    var partials = new long[input.Count];
    partials[0] = 1;
    
    for (int i = 1; i < input.Count; i++)
    {
    	bool valid1back = i - 1 >= 0 && input[i - 1] - input[i] <= 3;
    	bool valid2back = i - 2 >= 0 && input[i - 2] - input[i] <= 3;
    	bool valid3back = i - 3 >= 0 && input[i - 3] - input[i] <= 3;
    
    	partials[i] = (valid1back ? partials[i - 1] : 0) + (valid2back ? partials[i - 2] : 0) + (valid3back ? partials[i - 3] : 0);
    }
    
    return $"{partials.Last()} possible connections";
    

    I believe this was my first use of dynamic programming since my algorithms class. This was a satisfying one - I'm pretty sure it was 10 minutes of thinking/whiteboard-sketching, then 5 minutes of typing and watching it work first time.

    3 votes
  12. nothis
    (edited )
    Link
    Today's was a good example of a puzzle that's a fun challenge without being tedious. Took me a while to figure out part 2 but thankfully, it spared me the hardcore combinatorics once I figured out...

    Today's was a good example of a puzzle that's a fun challenge without being tedious. Took me a while to figure out part 2 but thankfully, it spared me the hardcore combinatorics once I figured out the joke.

    Part 1 seemed suspiciously easy, you could just sort the numbers and measure the gaps (the length of the explanation, suggesting something horribly complicated, was a bit mean, though). I'm pretty sure it was supposed to be a "hint" for part 2 and a rather good one. The moment I read "trillions" I knew the name of the game: looking for ways to cut the problem into smaller parts.

    I noticed that all 3-gaps had to be traversed since you couldn't fill them with alternate routes. Further, there seemed to be no gaps of 2 and no sequences of 1-gaps longer than 4. Also a single 1-gap between two 3-gaps (or the start or the end of your list) didn't allow any alternative routes, either.

    So this came down to counting the number of possible routes within those 2, 3 and 4-sequences of 1-gaps, which just happened to be (I counted manually) 2, 4 and 7. A sequence of two 1-gaps followed by a sequence four 1-gaps would allow 2 * 7 different routes and so on. All I had to do was count all those sequences and multiply by the amount of different routes each of them allows.

    Part 1+2 (Python)
    with open("input.txt") as inputFile:
        adapters = list(map(int, inputFile.read().split("\n")))
    
    
    def diffList(adapters):
        adapters.sort()
        diffs = []
        last = 0
        for adapter in adapters:
            diffs.append(adapter - last)
            last = adapter
        diffs.append(3)
        return diffs
    
    
    def testAdapters(adapters):
        diffs = diffList(adapters)
        print("1-jolt differences * 3-jolt differences: ",
              diffs.count(1) * diffs.count(3))
    
    
    def combinations(adapters):
        diffs = diffList(adapters)
        repeats = 0
        factors = [1, 1, 2, 4, 7]
        combinationsCount = 1
        for diff in diffs:
            if diff == 1:
                repeats += 1
            else:
                combinationsCount *= factors[repeats]
                repeats = 0
        print("Possible combination count:", combinationsCount)
    
    
    testAdapters(adapters)
    combinations(adapters)
    
    2 votes
  13. wycy
    (edited )
    Link
    Rust Yikes. I went with a recursive solution for this but needed to implement caching to get the runtime down. I wanted to use the cached crate but struggled massively with Rust's borrow checker...

    Rust

    Yikes. I went with a recursive solution for this but needed to implement caching to get the runtime down. I wanted to use the cached crate but struggled massively with Rust's borrow checker to get that working. Finally figured out I could use lazy_static to get cached to work. Considering how atrocious my solution attempts originally looked, it cleaned up nicely.

    Solution
    use std::env;
    use std::io::{prelude::*, BufReader};
    use std::fs::File;
    
    #[macro_use] extern crate lazy_static;
    lazy_static! {
        static ref ADAPTERS: Vec<i64> = {
            let args: Vec<String> = env::args().collect();
            let file = File::open(&args[1]).expect("Input file not found.");
            let reader = BufReader::new(file);
            let input: Vec<_> = match reader.lines().collect() {
                Err(err) => panic!("Unknown error reading input: {}", err),
                Ok(result) => result,
            };
        
            let mut adapters: Vec<_> = input.iter().map(|x| x.parse::<i64>().unwrap()).collect();
            adapters.sort();
            adapters.insert(0,0);
            adapters.push(adapters.iter().max().unwrap()+3);
            adapters
        };
    }
    
    use cached::proc_macro::cached;
    #[cached]
    fn num_configurations(adapters: &'static [i64], from: i64, to: i64) -> i64 {
        let mut sum = 0;
        for next in &[from+1, from+2, from+3] {
            if next == &to { 
                sum += 1;
            } else if adapters.iter().filter(|x| x == &next).count() == 1 {
                sum += num_configurations(&adapters, *next, to);
            }
        }
        sum
    }
    
    fn main() {
    
        // Part 1
        let diffs1jolt = &ADAPTERS.windows(2)
            .filter(|pair| pair[1]-pair[0] == 1)
            .count();
        let diffs3jolt = &ADAPTERS.windows(2)
            .filter(|pair| pair[1]-pair[0] == 3)
            .count();
        println!("Part 1: {}", diffs1jolt * diffs3jolt); // 1836
    
        // Part 2
        let plug = &ADAPTERS.iter().max().unwrap();
        println!("Part 2: {}", num_configurations(&ADAPTERS, 0, **plug)); // 43406276662336
    }
    
    2 votes
  14. 3d12
    Link
    This was, by far, the least amount of fun I've had on one of these problems so far. Apart from first grappling with Javascript betraying my assumption of pass-by-value-by-default which led to...

    This was, by far, the least amount of fun I've had on one of these problems so far. Apart from first grappling with Javascript betraying my assumption of pass-by-value-by-default which led to recursive debugging hell, the combinatorics involved in actually divining the answer went completely over my head. After having burned literally 8 entire hours of brainpower at this point on this problem, I finally gave up and implemented the solution that @PapaNachos and @nothis outlined. Thanks for posting your tips and code, both of you. (And everyone else, of course!)

    For the curious, my recursive solution ran for 48 hours on a single-core 1.6 gHz machine at 90%+ cpu usage and got to roughly 24 billion combinations. My answer using combinatorics math was just north of 124 trillion. It would have taken my computer 28.3 years (assuming constant speed and none of the parts burning out) to calculate that result.

    I left both of my recursive implementations in my part 2 code, for the curious, or as a learning tool for new programmers. The first one I did (function named findSequences) was meant to return an array of all the combinations themselves, of which I could take .length to get the answer. This ran out of memory (4GB on my machine) after roughly 2.8 million combinations. The second one (function named findSequences2) instead foregoes storing the combinations in favor of just totaling them. If you'd like, you can see how fast it runs for you!

    Part 1
    const fs = require('fs');
    const readline = require('readline');
    
    let adapterArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		adapterArray.push(line);
    	}
    }
    
    function joltDifference(array, index) {
    	let currentAdapter = array[index];
    	let nextAdapter = array[index+1];
    	return nextAdapter - currentAdapter;
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	// sort into ascending order, to make sure no adapters are skipped
    	adapterArray.sort((a,b) => { return a - b; });
    	let differences = [];
    	let tempIndex = 0;
    	// outlet -> first adapter
    	differences.push(1);
    	// calculate differences
    	for (; tempIndex < (adapterArray.length - 1); tempIndex++) {
    		differences.push(joltDifference(adapterArray, tempIndex));
    	}
    	// last adapter -> device's adapter
    	differences.push(3);
    	// separate the two types of differences
    	let oneJolt = differences.filter(element => element == 1);
    	let threeJolt = differences.filter(element => element == 3);
    	console.log("Found it! Multiplying " + oneJolt.length + " by " + threeJolt.length + " is " + (oneJolt.length * threeJolt.length));
    })();
    
    Part 2
    const fs = require('fs');
    const readline = require('readline');
    
    let adapterArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		adapterArray.push(line);
    	}
    }
    
    function joltDifference(array, index) {
    	let currentAdapter = array[index];
    	let nextAdapter = array[index+1];
    	return nextAdapter - currentAdapter;
    }
    
    function possibleAdapters(array, voltage) {
    	let eligibleAdapters = [];
    	for (const adapter of array) {
    		let difference = (adapter - voltage);
    		if (difference <= 3 && difference > 0) {
    			eligibleAdapters.push(adapter);
    		}
    	}
    	return eligibleAdapters;
    }
    
    // returning sequences as an int total, rather than an array of arrays, to save memory?
    function findSequences2(array, voltage, sequences = 0, currentSequence = []) {
    	// copy the current sequence so we don't add the new values by reference when we recurse
    	// ^^ this is what had me stuck for a few hours ^^
    	let tempSequence = currentSequence.slice();
    	// break condition
    	if (array.length == 1) {
    		tempSequence.push(voltage);
    		sequences++;
    		if (sequences % 100000 == 0) {
    			console.log("Found sequence " + sequences);
    		}
    		return sequences;
    	} else {
    		// find possible next branches
    		let possibilities = possibleAdapters(array, voltage);
    		// if any branches exist to go to
    		if (possibilities.length >= 1) {
    			// push the current branch onto the stack and recurse over them
    			tempSequence.push(voltage);
    			for (const possibility of possibilities) {
    				// trim remaining array to save processing time by eliminating elements that won't matter
    				let trimmedArray = array.slice(array.indexOf(possibility));
    				sequences = findSequences2(trimmedArray, possibility, sequences, tempSequence);
    			}
    		}
    		return sequences;
    	}
    }
    
    // this works, but runs out of memory
    function findSequences(array, voltage, sequences = [], currentSequence = []) {
    	// copy the current sequence so we don't add the new values by reference when we recurse
    	// ^^ this is what had me stuck for a few hours ^^
    	let tempSequence = currentSequence.slice();
    	// break condition
    	if (array.length == 1) {
    		tempSequence.push(voltage);
    		sequences.push(tempSequence);
    		if ((sequences.length + 1) % 10000 == 0) {
    			console.log("Found sequence " + (sequences.length + 1));
    		}
    		return sequences;
    	} else {
    		// find possible next branches
    		let possibilities = possibleAdapters(array, voltage);
    		// if any branches exist to go to
    		if (possibilities.length >= 1) {
    			// push the current branch onto the stack and recurse over them
    			tempSequence.push(voltage);
    			for (const possibility of possibilities) {
    				// trim remaining array to save processing time by eliminating elements that won't matter
    				let trimmedArray = array.slice(array.indexOf(possibility));
    				sequences = findSequences(trimmedArray, possibility, sequences, tempSequence);
    			}
    		}
    		return sequences;
    	}
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	// adding the outlet voltage
    	adapterArray.push(0);
    	// adding the device voltage
    	adapterArray.push(adapterArray.reduce((a,b) => { return Math.max(a,b); }) + 3);
    	// sort into ascending order, to make sure no adapters are skipped
    	adapterArray.sort((a,b) => { return a - b; });
    	// vv This works, but runs out of memory vv
    	//let result = findSequences(adapterArray, adapterArray[0]);
    	//console.log("Distinct arrangements: " + result.length);
    	// vv This works, but runs out of time vv
    	//let result = findSequences2(adapterArray, adapterArray[0]);
    	//console.log("Distinct arrangements: " + result);
    	
    	// Credit to PapaNachos and nothis on Tildes for tips and solutions that led me to this code, which I probably never would have figured out alone...
    	let differences = [];
    	let sequenceMultiplier = [1,1,2,4,7];
    	let sequenceIndex = 0;
    	let total = 1;
    	let problemString = "";
    	for (let i = 0; i < (adapterArray.length - 1); i++) {
    		differences.push(joltDifference(adapterArray, i));
    	}
    	for (const difference of differences) {
    		if (difference == 1) {
    			sequenceIndex++;
    		} else {
    			problemString += "*" + sequenceMultiplier[sequenceIndex];
    			total *= sequenceMultiplier[sequenceIndex];
    			sequenceIndex = 0;
    		}
    	}
    	console.log(problemString);
    	console.log("Total combinations: " + total);
    })();
    
    2 votes