from lib import get_data def part_1_with_numpy(data): import numpy as np def make_base_matrix(pattern, n): xss = [] for round in range(n): xs = [pattern[((i + 1) // (round + 1)) % len(pattern)] for i in range(n)] xss.append(xs) return np.array(xss) pattern = [0, 1, 0, -1] input = int(data.strip()) v = np.array(list(map(int, str(input)))) m = make_base_matrix(pattern, len(v)) func = np.vectorize(lambda x: abs(x) % 10) for _ in range(100): v = func(np.dot(m, v)) print("".join(map(str, v[:8].tolist()))) def phase(digits_in): pattern = [0, 1, 0, -1] digits_out = [] for round in range(len(digits_in)): i, out = 0, 0 while i < len(digits_in): pattern_i = ((i + 1) // (round + 1)) % len(pattern) out += pattern[pattern_i] * digits_in[i] i += 1 out = abs(out) % 10 digits_out.append(out) return digits_out def phase_with_offset(digits_in, pattern, offset): digits_out = digits_in.copy() for round in range(offset, len(digits_in)): i, out = 0, 0 print(round) pattern_value = pattern[((i + 1) // (round + 1)) % len(pattern)] if pattern_value == 0: i += round while i < len(digits_in): pattern_i = ((i + 1) // (round + 1)) % len(pattern) pattern_value = pattern[pattern_i] if pattern_value != 0: out += pattern_value * sum( digits_in[i : min(i + 1 + round, len(digits_in))] ) # for j in range(i, min(i + 1 + round, len(digits_in))): # out += (pattern_value * digits_in[j]) i += round + 1 out = abs(out) % 10 digits_out[round] = out return digits_out def part_1(data): pattern = [0, 1, 0, -1] input = list(map(int, (data.strip()))) for _ in range(100): input = phase_with_offset(input, pattern, 0) print("".join(map(str, input[:8]))) input = list(map(int, (data.strip()))) * 10_000 offset = int("".join(map(str, input[:7]))) - 200 for i in range(100): print(i) input = phase_with_offset(input, pattern, offset) print(input) return # What do I know? # # 1. There is a solution. Other people have solved it. # 2. The solution is not crazy. It will be rather obvious. # 3. 6_500_000 * 6_500_000 is definitely too much to brute force. # 4. Can we go from O(N^2) to O(N) somehow? Yes, that's what we have to do. # The whole point of FFT is to get from O(N^2) to O(N*log(N)). Now, # how exactly do we do that? # # Ways to improve performance: # # 1. Speed up `phase` significantly. Yes, but how? # 2. Only compute a subset of the lists? - No! # 3. Discover some kind of pattern? - No! # # Assumptions: # # 1. I need every digit of the previous round. - False! # 2. I cannot just operate on a subset. - False! # # Non-approaches: # # 1. Fancy recursive algorithm that selectively picks fields. # 2. Pattern detection or subset consideration. def main(): data = get_data(__file__) # part_1_with_numpy(data) part_1(data) if __name__ == "__main__": main()