11 votes

Day 9: Encoding Error

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


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. PapaNachos
    (edited )
    Link
    This one got a little bit weird. I've been writing up tips and thoughts on previous posts. I hope y'all are enjoying them. I also decided to stop calling them 'Tips for Beginners' since that feels...

    This one got a little bit weird. I've been writing up tips and thoughts on previous posts. I hope y'all are enjoying them. I also decided to stop calling them 'Tips for Beginners' since that feels a bit patronizing.

    Day 9 Part A – Python I built a buffer that I just loaded my preamble into. As I moved along, I just pushed the old values off the front and added the new ones on to the back. Then I built a check_sums method that searched the buffer for a sum that worked. Assuming there would never be a duplicate in my buffer, which ended up being correct. But I was a bit worried if that wasn't as safe assumption
    #preamble_length = 5
    preamble_length = 25
    #data = test_data.split('\n')
    data = input_data.split('\n')
    
    data = list(map(int, data))
    preamble = data[:preamble_length]
    
    def check_sums(preamble, target):
        for val1 in preamble:
            for val2 in preamble:
                if val1 != val2:
                    if val1 + val2 == target:
                        return True
                    else:
                        pass
        return False
    
    for row in data[preamble_length:]:
        if check_sums(preamble, row):
            pass
            
        else:
            print(f'{row} did not validate')
        preamble.pop(0)
        preamble.append(row)    
    
    Day 9 Part B – Python I stepped through the entire data set. For each row, I moved onward from it adding numbers and checking if it hits the target value. I don't like how it keeps checking even after I've found or exceeded the target, but for a dataset of this size it does the job. I might go back and improve this one
    target_val = 20874512
    data = input_data.split('\n')
    #target_val = 127
    #data = test_data.split('\n')
    
    data = list(map(int, data))
    
    answer = None
    
    for index, row in enumerate(data):
        running_sum = 0
        summed_vals = []
        for val in data[index:]:
            running_sum = running_sum + val
            summed_vals.append(val)
            if running_sum == target_val and val != target_val:
                print(summed_vals)
                print(f'Min: {min(summed_vals)}')
                print(f'Max: {max(summed_vals)}')
                print(f'Min + Max: {max(summed_vals)+min(summed_vals)}')
    
    Tips and Commentary
    • For part A you'll probably want a buffer, which stores a bunch of data for you. An easy way to manipulate your buffer is through push and pop which allow you to manipulate specific locations within the buffer. They might go by different names, like in Python I used a combination of pop() and append()

    • My part B solution is hellishly inefficient, I might want to redo it to make it terminate early upon completion or determination that a given chain is no longer viable. In mine I loop through a bunch of data looking for valid combinations. But I keep running even after I've found my answer. It spends for more time and energy than is necessary. which is why I say it's inefficient. It should stop searching once it finds a satisfactory result

    • You can actually quantify efficiency in computer science. The term is Big O notation. For example, my search could be represented as O(n^2) which means that the computational power used scales exponentially with the data set. Which is not good.

    6 votes
  2. [2]
    pnutzh4x0r
    (edited )
    Link
    Python Repo Link Part 1 Basically, I just checked if the current number was in one of the combinations of the current window of numbers. I used Python's itertools.combinations to generate all the...

    Python

    Repo Link

    Part 1

    Basically, I just checked if the current number was in one of the combinations of the current window of numbers. I used Python's itertools.combinations to generate all the combinations of the current window (maintained with a fixed-sized collections.deque)

    import collections
    import itertools
    import sys
    
    # Constants
    
    WINDOW_SIZE = 25
    
    # Functions
    
    def combinations(numbers):
        return [sum(c) for c in itertools.combinations(numbers, 2)]
    
    # Main Execution
    
    def main():
        numbers = collections.deque(maxlen=WINDOW_SIZE)
        for index, number in enumerate(map(int, sys.stdin)):
            if index >= WINDOW_SIZE and number not in combinations(numbers):
                print(number)
            numbers.append(number)
    
    if __name__ == '__main__':
        main()
    
    Part 2

    I'm pretty sure there is a more efficient way to find the contiguous array, but I was lazy and just brute-forced it.

    import sys
    
    # Constants
    
    TARGET = 1492208709 # From Part A
    
    # Functions
    
    def find_contiguous_array(numbers, target):
        for start in range(len(numbers)):
            for end in range(start, len(numbers)):
                if sum(numbers[start:end]) == target:
                    return numbers[start:end]
        return []
    
    # Main Execution
    
    def main():
        numbers = [int(n) for n in sys.stdin]
        array   = find_contiguous_array(numbers, TARGET)
    
        print(min(array) + max(array))
    
    if __name__ == '__main__':
        main()
    
    6 votes
    1. thorondir
      Link Parent
      Your solution for task one is so elegant. Just goes to show that I don't know enough about the tools available in python to make my life easier. xD

      Your solution for task one is so elegant.
      Just goes to show that I don't know enough about the tools available in python to make my life easier. xD

      2 votes
  3. [2]
    Nuolong
    (edited )
    Link
    Python The problem was a little boring in my opinion today... but backstory made it interesting nonetheless! Repo Link Part 2 #!/usr/bin/env python3 import sys def find_ctg(window, invalid_num):...

    Python
    The problem was a little boring in my opinion today... but backstory made it interesting nonetheless!

    Repo Link

    Part 2
    #!/usr/bin/env python3
    
    import sys
    
    def find_ctg(window, invalid_num):
        curr_sum = window[0]
        start = 0
        end = 1
    
        while end < len(window) - 1:
            # front window value is removed from window
            while curr_sum > invalid_num and start < end:
                curr_sum -= window[start]
                start += 1
    
            # contiguous subarray found
            if curr_sum == invalid_num:
                sub_arr = window[start:end+1]
                return max(sub_arr) + min(sub_arr)
            # new value added to end of window
            else:
                curr_sum += window[end]
                end += 1
    
    
    def abide(window, n):
        for i, w_n in enumerate(window):
            diff = n - w_n
            if diff in window[i + 1:]:
                return True
        return False
    
    def find_rulebreaker(nums, preamble_len):
        window = nums[0:preamble_len]
        strt_remain = nums[preamble_len:]
    
        for i, n in enumerate(strt_remain):
            if (abide(window, n)):
                window.append(n)
                window.pop(0)
            else:
                return i, n
    
    def main():
        nums = []
        for num in sys.stdin:
            nums.append(int(num))
    
        i, invalid_num = find_rulebreaker(nums, 25)
        print(find_ctg(nums[:i], invalid_num))
    
    
    if __name__ == '__main__':
        main()
    
    6 votes
    1. pnutzh4x0r
      Link Parent
      Awesome linear time solution for find_ctg. I've updated my solution for Part B to use a variation of your approach.

      Awesome linear time solution for find_ctg. I've updated my solution for Part B to use a variation of your approach.

      4 votes
  4. Crestwave
    Link
    This was fun, kind of like a sequel to day 1's problem; my part 1 solution is almost exactly the same. Part 1 #!/usr/bin/awk -f BEGIN { pre = 25 } { data[NR] = $0 } NR > pre { for (i = NR-pre; i...

    This was fun, kind of like a sequel to day 1's problem; my part 1 solution is almost exactly the same.

    Part 1
    #!/usr/bin/awk -f
    BEGIN { pre = 25 }
    { data[NR] = $0 }
    
    NR > pre {
            for (i = NR-pre; i <= NR; ++i)
                    for (j = NR-pre; j <= NR; ++j)
                            if (data[i] + data[j] == data[NR])
                                    next
    
            print data[NR]
    }
    
    Part 2
    #!/usr/bin/awk -f
    BEGIN { pre = 25 }
    { data[NR] = $0 }
    
    NR > pre {
            for (i = NR-pre; i <= NR; ++i)
                    for (j = NR-pre; j <= NR; ++j)
                            if (data[i] + data[j] == data[NR])
                                    next
    
            for (i = 1; sum != data[NR]; ++i) {
                    sum = 0
                    for (j = i; j <= NR && sum < data[NR]; ++j)
                            sum += data[j]
            }
    
            for (i -= 1; i < j-1; ++i) {
                    if (data[i] > highest)
                            highest = data[i]
                    if (data[i] < lowest || ! lowest)
                            lowest = data[i]
            }
    
            print highest + lowest
    }
    
    5 votes
  5. tomf
    (edited )
    Link
    Sheets! Both of these formulas were drag-fills, which I am not happy with -- but the fact that I can still do anything in a spreadsheet is great. Below are the formulas for the answer rows. I...

    Sheets!

    Both of these formulas were drag-fills, which I am not happy with -- but the fact that I can still do anything in a spreadsheet is great.

    Below are the formulas for the answer rows. I ditched the others since they're pointless to run.

    I really didn't think I'd be able to do anything after yesterday's puzzle. So far as I can see, Sheets can't do that sort of stuff. I am hopeful that every odd day from here on out (except for the 25th) won't require that intputer business. We'll see how it goes.

    This method is pretty hacky.

    Part 1
    
    =ARRAYFORMULA(
      IF(
       COUNTA(
        IFERROR(
         QUERY(
          SPLIT(
           FLATTEN(
            A540:A564&"|"&
            TRANSPOSE(A540:A564)&"|"&
            A540:A564+
             TRANSPOSE(A540:A564)),
           "|"),
          "select * 
           where 
            not Col1 = Col2 and 
            Col3 is not null and 
            Col3 = "&A565&"")))>0,,
       A565))
    
    
    Part 2
        =ARRAYFORMULA("A"&ROW(A1:A)&":A"&ROW(A1:A)+L2)
    

    and then in a second column I used

    
    =IF(SUM(INDIRECT(I449))=$F$2,MIN(INDIRECT(I449))+MAX(INDIRECT(I449)),)
    
    

    This generates ranges with an offset. Not the prettiest thing, but it does work.

    In L2 I had the offset. I had to hunt a bit, but I lucked out by starting at 15 and struck gold at 16. At the top of the sheet I ran a filter to get the answer.

    Witness the magic

    5 votes
  6. archevel
    (edited )
    Link
    Todays problem seems suited for python and numpy to make a triumphant return :) Managed to get part two down to just a few lines which was nice! Part 1 import numpy as np import sys nums =...

    Todays problem seems suited for python and numpy to make a triumphant return :)

    Managed to get part two down to just a few lines which was nice!

    Part 1
    import numpy as np
    import sys
    
    nums = np.array([int(s) for s in sys.stdin.readlines() if s != ""])
    preambleLength = 25
    def match(target, preamble):
        combos = np.array(np.meshgrid(preamble, preamble)).T.reshape(-1,2)
        summed = np.sum(combos[combos[:,0] != combos[:,1]], axis=1)
        return target in summed
    
    for offset in range(len(nums) - preambleLength):
        preamble, target = nums[offset:offset+preambleLength], nums[offset+preambleLength]
        if not match(target, preamble): 
            print(target)
            exit(0)
    
    Part 2
    import numpy as np
    import sys
    
    nums = np.array([int(s) for s in sys.stdin.readlines() if s != ""])
    
    for offset in range(len(nums)):
        matches = np.where(np.array(np.meshgrid(nums[offset:], nums[offset:])).cumsum() == 400480901)
        if len(matches[0]) > 0:    
            weakness = nums[offset:offset + matches[0][0] + 1]
            print(min(weakness), max(weakness), min(weakness) + max(weakness))
            exit(0)
    
    4 votes
  7. raevnos
    Link
    Chicken Scheme #!/usr/local/bin/csi -s (import (chicken format) (chicken io) (srfi 1) (srfi 158) (srfi 193)) (declare (fixnum-arithmetic) (block)) (define (sums-of-pairs numbers)...
    Chicken Scheme
    #!/usr/local/bin/csi -s
    (import (chicken format)
            (chicken io)
            (srfi 1)
            (srfi 158)
            (srfi 193))
    (declare (fixnum-arithmetic) (block))
    
    (define (sums-of-pairs numbers)
      (make-coroutine-generator
       (lambda (yield)
         (pair-for-each
          (lambda (pair)
            (let ((num1 (car pair)))
              (for-each (lambda (num2) (yield (+ num1 num2))) (cdr pair))))
          numbers))))
    
    (define (solve1 numbers window-length)
      (let-values (((preamble numbers) (split-at numbers window-length))
                   ((wl-1) (sub1 window-length)))
        (let loop ((numbers numbers)
                   (preamble (reverse! preamble)))
          (if (null? numbers)
              #f
              (let ((candidate (car numbers)))
                (if (generator-find (cut = <> candidate) (sums-of-pairs preamble))
                    (loop (cdr numbers) (cons candidate (take! preamble wl-1)))
                    candidate))))))
    
    (define (numbers-to-total numbers goal)
      (let loop ((numbers numbers)
                 (total 0)
                 (acc '()))
        (cond
         ((= total goal) (reverse! acc))
         ((or (> total goal) (null? numbers)) #f)
         (else
          (loop (cdr numbers) (+ (car numbers) total) (cons (car numbers) acc))))))
    
    (define (minmax lst #!optional (less-than? <))
      (let loop ((minval (car lst))
                 (maxval (car lst))
                 (lst (cdr lst)))
        (if (null? lst)
            (values minval maxval)
            (loop (if (less-than? (car lst) minval) (car lst) minval)
                  (if (less-than? maxval (car lst)) (car lst) maxval)
                  (cdr lst)))))
    
    (define (solve2 numbers key)
      (let loop ((numbers numbers))
        (cond
         ((null? numbers) #f)
         ((numbers-to-total numbers key) =>
          (lambda (range) (call-with-values (lambda () (minmax range)) +)))
         (else (loop (cdr numbers))))))
    
    ;;; Take an optional window size from the first command line argument
    (define window-size
      (let ((cl (command-line)))
        (if (>= (length cl) 2)
            (string->number (cadr cl))
            25)))
    (define input (read-list))
    (define key (solve1 input window-size))
    (printf "Part 1: ~A~%" key)
    (printf "Part 2: ~A~%" (solve2 input key))
    

    Only interesting thing here is using a generator to lazily calculate the sums of pairs of the preamble.

    3 votes
  8. nothis
    Link
    Posting mine again for today. I find it a nice distraction but can't be bothered with cleanup. I haven't checked but if past days are any indication, someone in here will have solved this in two...

    Posting mine again for today. I find it a nice distraction but can't be bothered with cleanup. I haven't checked but if past days are any indication, someone in here will have solved this in two lines of creative map() uses when it took me over 40 lines of oldschool for loops with indexes. Overall this wasn't so hard, though.

    Part 1+2
    with open("input.txt") as inputFile:
        numbers = list(map(int, inputFile.read().split("\n")))
    
    
    def check(pos, numbers, depth):
        for n in range(pos - depth, pos):
            for m in range(pos - depth, pos):
                if not n == m and numbers[n] + numbers[m] == numbers[pos]:
                    return True
        return False
    
    
    def findSequence(target, numbers):
        for n in range(0, len(numbers)):
            smallest = numbers[n]
            largest = numbers[n]
            sum = numbers[n]
            m = n + 1
            while sum < target and m < len(numbers):
                if numbers[m] > largest:
                    largest = numbers[m]
                elif numbers[m] < smallest:
                    smallest = numbers[m]
                sum += numbers[m]
                if sum == target:
                    print("Weakness:", smallest + largest)
                    return
                m += 1
    
    
    def haxxOriz3(numbers, depth):
        firstInvalid = -1
        for i in range(depth, len(numbers)):
            if not check(i, numbers, depth):
                firstInvalid = numbers[i]
                break
        print("First invalid number:", firstInvalid)
        findSequence(firstInvalid, numbers)
    
    
    haxxOriz3(numbers, 25)
    
    3 votes
  9. Gyrfalcon
    Link
    Language: Julia Repository This one was not too bad, and I liked revisiting day 1 with a twist. Part 1 function main() input = [] open("Day09/input.txt") do fp input = parse.(Int, readlines(fp))...

    This one was not too bad, and I liked revisiting day 1 with a twist.

    Part 1
    function main()
    
        input = []
        open("Day09/input.txt") do fp
            input = parse.(Int, readlines(fp))
        end
    
        error_location = find_encoding_error(input, 25)
        result_1 = input[error_location]
    
        println("Result 1 is ", result_1)
    end
    
    # Loops through to find which value is broken
    function find_encoding_error(code, preamble_len)
    
        for idx = preamble_len+1:length(code)
            valid = encoding_check(code[idx-preamble_len:idx])
            if !valid
                return idx
            end
        end
        return nothing
    end
    
    # Checks if any of the values in the preceding numbers can sum to the target
    function encoding_check(code)
        target = code[end]
        values = code[1:end-1]
        remainders = target .- values
        possibilities = remainders .+ values'
        return any(x -> x in values, remainders)
    
    end
    
    main()
    
    Part 2 diff
    @@ -9,6 +9,10 @@ function main()
         result_1 = input[error_location]
     
         println("Result 1 is ", result_1)
    +
    +    result_2 = encoding_cracker(input, result_1)
    +
    +    println("Result 2 is ", result_2)
     end
     
     # Loops through to find which value is broken
    @@ -33,4 +37,24 @@ function encoding_check(code)
     
     end
     
    +# Loops through to crack encoding
    +function encoding_cracker(code, target)
    +
    +    for idx = 1:length(code)-1
    +        jdx = idx + 1
    +        sum = code[idx] + code[jdx]
    +        while sum < target
    +            jdx += 1
    +            sum += code[jdx]
    +        end
    +        
    +        if sum == target
    +            return minimum(code[idx:jdx]) + maximum(code[idx:jdx])
    +        else
    +            continue
    +        end
    +    end
    +    return nothing
    +end
    +
     main()
    
    PC vs Pi Performance
    # PC
    BenchmarkTools.Trial: 
      memory estimate:  3.57 MiB
      allocs estimate:  4485
      --------------
      minimum time:     750.561 μs (0.00% GC)
      median time:      971.974 μs (0.00% GC)
      mean time:        1.165 ms (6.17% GC)
      maximum time:     4.285 ms (33.86% GC)
      --------------
      samples:          4289
      evals/sample:     1
    
    # Pi
    BenchmarkTools.Trial: 
      memory estimate:  3.57 MiB
      allocs estimate:  4485
      --------------
      minimum time:     6.893 ms (0.00% GC)
      median time:      7.262 ms (0.00% GC)
      mean time:        7.827 ms (6.69% GC)
      maximum time:     15.549 ms (42.16% GC)
      --------------
      samples:          639
      evals/sample:     1
    
    3 votes
  10. 3d12
    Link
    This was an interesting one, although I agree with @nothis in saying that I definitely did this one the "oldschool" way... I found that it helped keep my mind straight as far as the iterators,...

    This was an interesting one, although I agree with @nothis in saying that I definitely did this one the "oldschool" way... I found that it helped keep my mind straight as far as the iterators, though, and being able to quickly subset the array that way, instead of trying to use javascript's .filter() or .splice() :)

    Part 1
    const fs = require('fs');
    const readline = require('readline');
    
    let xmasArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		xmasArray.push(line);
    	}
    }
    
    function validateNumber(prevArray, number) {
    	for (let firstIndex = 0; firstIndex < prevArray.length; firstIndex++) {
    		for (let secondIndex = (firstIndex+1); secondIndex < prevArray.length; secondIndex++) {
    			let firstNumber = parseInt(prevArray[firstIndex]);
    			let secondNumber = parseInt(prevArray[secondIndex]);
    			if (firstNumber + secondNumber == number) {
    				return true;
    			}
    		}
    	}
    	return false;
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	let compareArray = [];
    	let compareAmount = 25;
    	// load the first numbers into the compare array
    	for (let i = 0; i < compareAmount; i++) {
    		compareArray.push(xmasArray[i]);
    	}
    	for (let i = compareAmount; i < xmasArray.length; i++) {
    		if (!validateNumber(compareArray, xmasArray[i])) {
    			console.log("Found it! First vulnerable number is " + xmasArray[i]);
    			return;
    		}
    		compareArray.splice(0, 1);
    		compareArray.push(xmasArray[i]);
    	}
    })();
    
    Part 2
    const fs = require('fs');
    const readline = require('readline');
    
    let xmasArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		xmasArray.push(line);
    	}
    }
    
    function validateNumber(prevArray, number) {
    	for (let firstIndex = 0; firstIndex < prevArray.length; firstIndex++) {
    		for (let secondIndex = (firstIndex+1); secondIndex < prevArray.length; secondIndex++) {
    			let firstNumber = parseInt(prevArray[firstIndex]);
    			let secondNumber = parseInt(prevArray[secondIndex]);
    			if (firstNumber + secondNumber == number) {
    				return true;
    			}
    		}
    	}
    	return false;
    }
    
    function contiguousList(array, targetNumber) {
    	for (let firstIndex = 0; firstIndex < array.length; firstIndex++) {
    		let contiguousArray = [];
    		let firstNumber = parseInt(array[firstIndex]);
    		contiguousArray.push(firstNumber);
    		for (let secondIndex = (firstIndex+1); secondIndex < array.length; secondIndex++) {
    			let secondNumber = parseInt(array[secondIndex]);
    			contiguousArray.push(secondNumber);
    			let currentSum = contiguousArray.reduce((a,b) => { return a+b; });
    			if (currentSum > targetNumber) {
    				break;
    			}
    			if (currentSum == targetNumber) {
    				return contiguousArray;
    			}
    		}
    	}
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	let compareArray = [];
    	let compareAmount = 25;
    	let vulnerableNumber = 0;
    	// load the first numbers into the compare array
    	for (let i = 0; i < compareAmount; i++) {
    		compareArray.push(xmasArray[i]);
    	}
    	for (let i = compareAmount; i < xmasArray.length; i++) {
    		if (!validateNumber(compareArray, xmasArray[i])) {
    			vulnerableNumber = xmasArray[i];
    			console.log("Found it! First vulnerable number is " + vulnerableNumber);
    			break;
    		}
    		compareArray.splice(0, 1);
    		compareArray.push(xmasArray[i]);
    	}
    	let contiguous = contiguousList(xmasArray, vulnerableNumber);
    	let smallest = contiguous.reduce((a,b) => { return Math.min(a,b); });
    	let largest = contiguous.reduce((a,b) => { return Math.max(a,b); });
    	console.log("Success! The range is:");
    	console.log(contiguous);
    	console.log("The smallest number of the range is " + smallest + " and the largest is " + largest);
    	console.log("The sum of these numbers is: " + (smallest + largest));
    })();
    
    3 votes
  11. jzimbel
    (edited )
    Link
    This one was only difficult because I tried to do a bunch of things to avoid unnecessary computation. Elixir's Enum functions are generally better suited to working through the whole enumerable—in...

    This one was only difficult because I tried to do a bunch of things to avoid unnecessary computation. Elixir's Enum functions are generally better suited to working through the whole enumerable—in cases where you need to both maintain some kind of state while iterating through the enumerable and stop short once you've found what you're looking for, it can get a little messy.

    I think there might be a cleaner way to accomplish what happens in the Enum.reduce_while calls using Stream functions; might revisit this later.

    Edit: There was indeed a cleaner (or at least shorter) way using Stream functions. Added the new definitions in the second expander.

    Parts 1 and 2, Elixir
    defmodule AdventOfCode.Day09 do
      @preamble_length 25
    
      def part1(args, preamble_length \\ @preamble_length) do
        {preamble, nums} =
          args
          |> parse_nums()
          |> split_preamble(preamble_length)
    
        find_invalid(nums, preamble, preamble_length)
      end
    
      def part2(args, preamble_length \\ @preamble_length) do
        full_nums = parse_nums(args)
    
        {preamble, nums} = split_preamble(full_nums, preamble_length)
    
        target = find_invalid(nums, preamble, preamble_length)
    
        addends = find_contiguous_addends(full_nums, target)
    
        Enum.min(addends) + Enum.max(addends)
      end
    
      defp parse_nums(input) do
        input
        |> String.split()
        |> Enum.map(&String.to_integer/1)
      end
    
      defp split_preamble(nums, preamble_length) do
        {preamble, nums} = Enum.split(nums, preamble_length)
    
        {Enum.reverse(preamble), nums}
      end
    
      defp find_invalid(nums, preamble, preamble_length) do
        Enum.reduce_while(nums, preamble, fn n, prev ->
          prev
          |> Stream.take(preamble_length)
          |> addends_exist?(n)
          |> case do
            true -> {:cont, [n | prev]}
            false -> {:halt, n}
          end
        end)
      end
    
      defp addends_exist?(nums, target) do
        nums
        |> Enum.reduce_while(MapSet.new(), fn n, found ->
          cond do
            (target - n) in found -> {:halt, :exists}
            true -> {:cont, MapSet.put(found, n)}
          end
        end)
        |> case do
          :exists -> true
          _ -> false
        end
      end
    
      defp find_contiguous_addends(nums, target) do
        2..length(nums)
        |> Stream.map(&find_contiguous_addends(nums, target, &1))
        |> Stream.reject(&is_nil/1)
        |> Enum.at(0)
      end
    
      defp find_contiguous_addends(nums, target, window_size) do
        nums
        |> Stream.chunk_every(window_size, 1)
        |> Enum.find(&(Enum.sum(&1) == target))
      end
    end
    
    Cleaner/shorter solution using Stream functions
    defmodule AdventOfCode.Day09 do
      defp find_invalid(nums, preamble, preamble_length) do
        nums
        |> Stream.transform(preamble, fn n, prev ->
          {[{Stream.take(prev, preamble_length), n}], [n | prev]}
        end)
        |> Stream.reject(fn {prev, n} -> addends_exist?(prev, n) end)
        |> Enum.at(0)
        |> elem(1)
      end
    
      defp addends_exist?(nums, target) do
        nums
        |> Stream.transform(MapSet.new(), fn n, found ->
          {[{n, found}], MapSet.put(found, n)}
        end)
        |> Enum.any?(fn {n, found} -> (target - n) in found end)
      end
    end
    
    3 votes
  12. Crespyl
    (edited )
    Link
    Ruby I suspect there's some kind of sliding-window iterator available somewhere in ruby that would make the first part cleaner, but at least I have combination to make searching the "preamble" for...

    Ruby

    I suspect there's some kind of sliding-window iterator available somewhere in ruby that would make the first part cleaner, but at least I have combination to make searching the "preamble" for valid numbers easier. Ruby is a bit like Python in that it's very "batteries included" and saves a lot of time on puzzles like this.

    Part 1
    #!/usr/bin/env ruby
    
    PREAMBLE_LENGTH = 25
    
    def valid?(preamble, number)
      preamble.combination(2).map { |a,b| a + b }.any?(number)
    end
    
    puts "Part 1"
    
    input = File.read(ARGV[0] || "test.txt").lines.map(&:to_i)
    buffer = input[0...PREAMBLE_LENGTH]
    target = nil
    
    input[PREAMBLE_LENGTH...].each do |n|
      if ! valid?(buffer, n)
        target = n
        break;
      else
        buffer.shift
        buffer << n
      end
    end
    
    puts target
    
    Part 2
    puts "Part 2"
    
    def check_contiguous(list, target)
      sum = list[0]
      list[1...].each_with_index do |n, i|
        sum += n
        return [true, i] if sum == target
        return [false, nil] if sum > target
      end
    end
    
    input.each_with_index do |n, i|
      match, len = check_contiguous(input[i...], target)
      if match
        span = input[i..i+len]
        puts span.min() + span.max()
        break
      end
    end
    
    2 votes
  13. blitz
    Link
    I had a lot of trouble figuring out how I could take an iterator in Rust and split it across two loops, since most of the methods on iterators take ownership of the iterator. Thankfully, I...

    I had a lot of trouble figuring out how I could take an iterator in Rust and split it across two loops, since most of the methods on iterators take ownership of the iterator. Thankfully, I stumbled upon by_ref!

    Also, I know that I'm parsing the lines to nums twice, for some reason takeing after collecting and recreating the iterator wasn't working, and I'm too sleepy to figure out why.

    lib.rs
    use std::collections::VecDeque;
    
    pub struct DataWindow(VecDeque<u64>);
    
    impl From<Vec<u64>> for DataWindow {
        fn from(vec: Vec<u64>) -> DataWindow {
            DataWindow( VecDeque::from(vec) )
        }
    }
    
    impl DataWindow {
        fn is_sum(&self, num: u64) -> bool {
            let DataWindow( dq ) = self;
    
            for x in 0..dq.len() {
                for y in x..dq.len() {
                    if dq[x] + dq[y] == num {
                        return true;
                    }
                }
            }
    
            return false;
        }
    
        pub fn read_next(&mut self, num: u64) -> bool {
            let ret = self.is_sum(num);
            let DataWindow( dq ) = self;
    
            dq.pop_front();
            dq.push_back(num);
    
            ret
        }
    }
    
    pub fn compute_min_max_sum(mut nums_acc: Vec<u64>) -> u64 {
        nums_acc.sort();
    
        nums_acc.first().unwrap() + nums_acc.last().unwrap()
    }
    
    main.rs
    use std::io;
    use std::io::prelude::*;
    use std::env;
    
    use day9::*;
    
    fn part1(lines: &Vec<String>, preamble_len: usize) -> u64 {
        let mut nums = lines.iter().map(|x| x.parse::<u64>().unwrap());
    
        let preamble: Vec<u64> = nums.by_ref().take(preamble_len).collect::<Vec<u64>>();
        let mut dw = DataWindow::from(preamble);
    
        for n in nums {
            if !dw.read_next(n) {
                return n;
            }
        }
    
        panic!();
    }
    
    fn part2(lines: &Vec<String>, target: u64) -> u64 {
        let nums: Vec<u64> = lines
            .iter()
            .map(|x| x.parse::<u64>().unwrap())
            .collect();
    
        for (i, x) in nums.iter().enumerate() {
            let mut target_acc = *x;
            let mut nums_acc = Vec::new();
    
            for j in 1..(nums.len() - i) {
                let num = nums[i + j];
                target_acc += num;
                nums_acc.push(num);
    
                if target_acc == target {
                    return compute_min_max_sum(nums_acc);
                }
            }
        }
    
        panic!();
    }
    
    fn main() {
        let stdin = io::stdin();
        let lines: Vec<String> = stdin
            .lock()
            .lines()
            .collect::<Result<_, _>>()
            .unwrap();
    
        let args: Vec<String> = env::args().collect();
        let preamble_len = args[1].parse::<usize>().unwrap();
    
        let part1_ans = part1(&lines, preamble_len);
        let part2_ans = part2(&lines, part1_ans);
    
        println!("Part 1: {}", part1_ans);
        println!("Part 2: {}", part2_ans);
    }
    
    2 votes
  14. clone1
    (edited )
    Link
    MIT Scheme. Could have made part one a lot more efficient but decided to just use my two sum function. Part 1 & 2 (define (get-lines parse-line file) (with-input-from-file file (lambda () (let...

    MIT Scheme. Could have made part one a lot more efficient but decided to just use my two sum function.

    Part 1 & 2
    (define (get-lines parse-line file)
      (with-input-from-file file
        (lambda ()
          (let loop ((line (read-line))
                     (lines '()))
            (if (eof-object? line)
                (reverse lines)
                (loop (read-line)
                      (cons (parse-line line) lines)))))))
    
    (define (2sum target numbers)
      (let ((passed (make-weak-eq-hash-table)))
        (let loop ((numbers numbers))
          (if (null? numbers) #f
              (let* ((x (car numbers))
                     (complement (- target x)))
                (if (and (>= complement 0)
                         (hash-table/get passed complement #f))
                    (cons x complement)
                    (begin
                      (hash-table/put! passed x #t)
                      (loop (cdr numbers)))))))))
    
    (define (with-index iter proc list)
      (iter (lambda (pair) (proc (car pair) (cdr pair)))
            (map cons
                 (iota (length list))
                 list)))
    
    (define (one nums pre-size)
      (cdr (with-index find (lambda (i x)
                              (let ((res (2sum x (sublist nums
                                                          i
                                                          (+ i pre-size)))))
                                (and (not res)
                                     x)))
                       (drop nums pre-size))))
    
    (define (two target nums)
      (let loop ((nums nums)
                 (seq '())
                 (sum 0))
        (cond ((= sum target) (+ (apply min seq)
                                 (apply max seq)))
              ((> sum target) (loop nums
                                    (cdr seq)
                                    (- sum (car seq))))
              (else (loop (cdr nums)
                          (append seq (list (car nums)))
                          (+ sum (car nums)))))))
    
    (define (run lines)
      (let ((one-res (one lines 25)))
        (display "One: ") (display one-res) (newline)
        (display "Two: ")  (display (two one-res lines)) (newline)))
    
    (run (get-lines string->number "input"))
    
    Part 1 alt Re-implemented part one to be 3 times faster and 9 times uglier.
    (define (one-f nums prev-size)
      (let ((prev (make-weak-eq-hash-table)))
        (for-each (lambda (x) (hash-table-set! prev x #t))
                  (take nums prev-size))
        ;; Pairs of the current number to check and the next number to remove from the prev num hash
        ;; We're doing it this way because I couldn't think of how to make an efficient queue
        (let* ((x-remove-pairs (map cons
                                    (drop nums prev-size)
                                    (take nums (- (length nums)
                                                  prev-size)))))
          (car
           (find (lambda (x-rem-p)
                   (let ((x (car x-rem-p))
                         (rem (cdr x-rem-p)))
                     (or (not (find (lambda (y) (hash-table-ref/default prev (- x y) #f))
                                    (hash-table-keys prev)))
                         (begin (hash-table-delete! prev rem)
                                (hash-table-set! prev x #t)
                                #f))))
                 x-remove-pairs)))))
    
    2 votes
  15. wycy
    (edited )
    Link
    Rust I like problems like this where the resulting code can be (relatively) nice and clean. Rust use std::env; use std::io::{self, prelude::*, BufReader}; use std::fs::File; extern crate...

    Rust

    I like problems like this where the resulting code can be (relatively) nice and clean.

    Rust
    use std::env;
    use std::io::{self, prelude::*, BufReader};
    use std::fs::File;
    
    extern crate itertools;
    use itertools::Itertools;
    
    fn validator_part1(input: &Vec<i64>, predicate: usize) -> i64 {
        let mut ans: i64 = 0;
        for window in input.windows(predicate+1) {
            let target = window.iter().last().unwrap();
            if window
                .iter().combinations(2)
                .filter(|pair| pair[0]+pair[1] == *target && pair[0] != pair[1])
                .count() < 1 {
                ans = *target;
                break;
            }
        }
        ans
    }
    
    fn validator_part2(input: &Vec<i64>, predicate: usize, target: i64) -> i64 {
        let mut size: usize = 2;
        loop {
            for window in input.windows(size) {
                if window.iter().sum::<i64>() == target {
                    return window.iter().min().unwrap() + window.iter().max().unwrap();
                }
            }
            size += 1;
        }
    }
    
    fn day09(input: &str) {
        let file = File::open(input).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 stream: Vec<_> = input.iter().map(|x| x.parse::<i64>().unwrap()).collect();
    
        // Part 1
        let part1 = validator_part1(&stream,25);
        println!("Part 1: {}", part1); // 14144619
        
        // Part 2
        let part2 = validator_part2(&stream,25,part1);
        println!("Part 2: {}", part2); // 1766397
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        day09(&filename);
    }
    
    2 votes