Day 7: Bridge Repair
Megathread guidelines
- Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
- You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL
FAQ
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
- the_beber ( @the_beber@lemm.ee ) 1•5 days ago
Kotlin
I finally got around to doing day 7. I try the brute force method (takes several seconds), but I’m particularly proud of my sequence generator for operation permutations.
The
Collection#rotate
method is in the fileUtils.kt
, which can be found in my repo.Solution
import kotlin.collections.any import kotlin.math.pow fun main() { fun part1(input: List): Long { val operations = setOf(CalibrationOperation.Plus, CalibrationOperation.Multiply) return generalizedSolution(input, operations) } fun part2(input: List): Long { val operations = setOf(CalibrationOperation.Plus, CalibrationOperation.Multiply, CalibrationOperation.Concat) return generalizedSolution(input, operations) } val testInput = readInput("Day07_test") check(part1(testInput) == 3749L) check(part2(testInput) == 11387L) val input = readInput("Day07") part1(input).println() part2(input).println() } fun parseInputDay7(input: List) = input.map { val calibrationResultAndInput = it.split(':') calibrationResultAndInput[0].toLong() to calibrationResultAndInput[1].split(' ').filter { it != "" }.map { it.toLong() } } fun generalizedSolution(input: List, operations: Set): Long { val parsedInput = parseInputDay7(input) val operationsPermutations = CalibrationOperation.operationPermutationSequence(*operations.toTypedArray()).take(calculatePermutationsNeeded(parsedInput, operations)).toList() return sumOfPossibleCalibrationEquations(parsedInput, operationsPermutations) } fun calculatePermutationsNeeded(parsedInput: List>>, operations: Set): Int { val highestNumberOfOperations = parsedInput.maxOf { it.second.size - 1 } return (1..highestNumberOfOperations).sumOf { operations.size.toDouble().pow(it).toInt() } } fun sumOfPossibleCalibrationEquations(parsedInput: List>>, operationPermutationCollection: Collection): Long { val permutationsGrouped = operationPermutationCollection.groupBy { it.size } return parsedInput.sumOf { (equationResult, equationInput) -> if (permutationsGrouped[equationInput.size - 1]!!.any { operations -> equationResult == equationInput.drop(1) .foldIndexed(equationInput[0]) { index, acc, lng -> operations[index](acc, lng) } }) equationResult else 0 } } typealias OperationPermutation = List sealed class CalibrationOperation(val operation: (Long, Long) -> Long) { operator fun invoke(a: Long, b: Long) = operation(a, b) object Plus : CalibrationOperation({ a: Long, b: Long -> a + b }) object Multiply : CalibrationOperation({ a: Long, b: Long -> a * b }) object Concat : CalibrationOperation({ a: Long, b: Long -> "$a$b".toLong() }) companion object { fun operationPermutationSequence(vararg operations: CalibrationOperation) = sequence { val cache = mutableListOf() val calculateCacheRange = { currentLength: Int -> val sectionSize = operations.size.toDouble().pow(currentLength - 1).toInt() val sectionStart = (1 until currentLength - 1).sumOf { operations.size.toDouble().pow(it).toInt() } sectionStart..(sectionStart + sectionSize - 1) } // Populate the cache with initial values for permutation length 1. operations.forEach { operation -> yield(listOf(operation).also { cache.add(it) }) } var currentLength = 2 var offset = 0 var cacheRange = calculateCacheRange(currentLength) var rotatingOperations = operations.toList() yieldAll( generateSequence { if (cacheRange.count() == offset) { rotatingOperations = rotatingOperations.rotated(1) if (rotatingOperations.first() == operations.first()) { currentLength++ } offset = 0 cacheRange = calculateCacheRange(currentLength) } val cacheSlice = cache.slice(cacheRange) return@generateSequence (cacheSlice[offset] + rotatingOperations.first()).also { cache += it offset++ } } ) } } }
- lwhjp ( @lwhjp@lemmy.sdf.org ) 3•8 days ago
Haskell
A surprisingly gentle one for the weekend! Avoiding string operations for
concatenate
got the runtime down below one second on my machine.import Control.Arrow import Control.Monad import Data.List import Data.Maybe readInput :: String -> [(Int, [Int])] readInput = lines >>> map (break (== ':') >>> (read *** map read . words . tail)) equatable :: [Int -> Int -> Int] -> (Int, [Int]) -> Bool equatable ops (x, y : ys) = elem x $ foldM apply y ys where apply a y = (\op -> a `op` y) <$> ops concatenate :: Int -> Int -> Int concatenate x y = x * mag y + y where mag z = fromJust $ find (> z) $ iterate (* 10) 10 main = do input <- readInput <$> readFile "input07" mapM_ (print . sum . map fst . (`filter` input) . equatable) [ [(+), (*)], [(+), (*), concatenate] ]
- lwhjp ( @lwhjp@lemmy.sdf.org ) 1•8 days ago
Since all operations increase the accumulator, I tried putting a
guard (a <= x)
inapply
, but it doesn’t actually help all that much (0.65s -> 0.43s).
- sjmulder ( @sjmulder@lemmy.sdf.org ) 3•8 days ago
C
Big integers and overflow checking, what else is there to say 😅
Code
#include "common.h" /* returns 1 on sucess, 0 on overflow */ static int concat(uint64_t a, uint64_t b, uint64_t *out) { uint64_t mul; for (mul=1; mul<=b; mul*=10) ; return !__builtin_mul_overflow( mul, a, out) && !__builtin_add_overflow(*out, b, out); } static int recur(uint64_t expect, uint64_t acc, uint64_t arr[], int n, int p2) { uint64_t imm; return n < 1 ? acc == expect : acc >= expect ? 0 : recur(expect, acc + arr[0], arr+1, n-1, p2) || recur(expect, acc * arr[0], arr+1, n-1, p2) || (p2 && concat(acc, arr[0], &imm) && recur(expect, imm, arr+1, n-1, p2)); } int main(int argc, char **argv) { char buf[128], *tok, *rest; uint64_t p1=0, p2=0, arr[32], expect; int n; if (argc > 1) DISCARD(freopen(argv[1], "r", stdin)); while ((rest = fgets(buf, sizeof(buf), stdin))) { assert(strchr(buf, '\n')); expect = atoll(strsep(&rest, ":")); for (n=0; (tok = strsep(&rest, " ")); n++) { assert(n < (int)LEN(arr)); arr[n] = atoll(tok); } p1 += recur(expect, 0, arr, n, 0) * expect; p2 += recur(expect, 0, arr, n, 1) * expect; } printf("07: %"PRIu64" %"PRIu64"\n", p1, p2); return 0; }
- Andy ( @Andy@programming.dev ) 3•8 days ago
Factor
spoiler
TUPLE: equation value numbers ; C: equation : get-input ( -- equations ) "vocab:aoc-2024/07/input.txt" utf8 file-lines [ split-words unclip but-last string>number swap [ string>number ] map ] map ; : possible-quotations ( funcs numbers -- quots ) dup length 1 - swapd all-selections [ unclip swap ] dip [ zip concat ] with map swap '[ _ prefix >quotation ] map ; : possibly-true? ( funcs equation -- ? ) [ numbers>> possible-quotations ] [ value>> ] bi '[ call( -- n ) _ = ] any? ; : solve ( funcs -- n ) get-input [ possibly-true? ] with filter [ value>> ] map-sum ; : part1 ( -- n ) { + * } solve ; : _|| ( m n -- mn ) [ number>string ] bi@ append string>number ; : part2 ( -- n ) { + * _|| } solve ;
- Andy ( @Andy@programming.dev ) 1•8 days ago
Slow and dumb gets it done! I may revisit this when I give up on future days.
- Ananace ( @ace@lemmy.ananace.dev ) 2•8 days ago
Made a couple of attempts to munge the input data into some kind of binary search tree, lost some time to that, then threw my hands into the air and did a more naïve sort-of breadth-first search instead. Which turned out to be better for part 2 anyway.
Also, maths. Runs in just over a hundred milliseconds when usingAsParallel
, around half a second without.C#
List<(long, int[])> data = new List<(long, int[])>(); public void Input(IEnumerable lines) { foreach (var line in lines) { var parts = line.Split(':', StringSplitOptions.TrimEntries); data.Add((long.Parse(parts.First()), parts.Last().Split(' ').Select(int.Parse).ToArray())); } } public void Part1() { var correct = data.Where(kv => CalcPart(kv.Item1, kv.Item2)).Select(kv => kv.Item1).Sum(); Console.WriteLine($"Correct: {correct}"); } public void Part2() { var correct = data.AsParallel().Where(kv => CalcPart2(kv.Item1, kv.Item2)).Select(kv => kv.Item1).Sum(); Console.WriteLine($"Correct: {correct}"); } public bool CalcPart(long res, Span num, long carried = 0) { var next = num[0]; if (num.Length == 1) return res == carried + next || res == carried * next; return CalcPart(res, num.Slice(1), carried + next) || CalcPart(res, num.Slice(1), carried * next); } public bool CalcPart2(long res, Span num, long carried = 0) { var next = num[0]; // Get the 10 logarithm for the next number, expand the carried value by 10^, add the two together // For 123 || 45 // 45 ⇒ 10log(45) + 1 == 2 // 123 * 10^2 + 45 == 12345 long combined = carried * (long)Math.Pow(10, Math.Floor(Math.Log10(next) + 1)) + next; if (num.Length == 1) return res == carried + next || res == carried * next || res == combined; return CalcPart2(res, num.Slice(1), carried + next) || CalcPart2(res, num.Slice(1), carried * next) || CalcPart2(res, num.Slice(1), combined); }
- hades ( @hades@lemm.ee ) 1•8 days ago
you meant depth first, right? since you’re using recursion
- Ananace ( @ace@lemmy.ananace.dev ) 1•8 days ago
That is true, I’ve evidently not had enough coffee yet this morning.
- janAkali ( @janAkali@lemmy.one ) English2•8 days ago
Nim
Bruteforce, my beloved.
Wasted too much time on part 2 trying to make combinations iterator (it was very slow). In the end solved both parts with
3^n
andtoTernary
.Runtime: 1.5s
func digits(n: int): int = result = 1; var n = n while (n = n div 10; n) > 0: inc result func concat(a: var int, b: int) = a = a * (10 ^ b.digits) + b func toTernary(n: int, len: int): seq[int] = result = newSeq[int](len) if n == 0: return var n = n for i in 0..
- Rin ( @Rin@lemm.ee ) 2•7 days ago
TypeScript
I wrote my own iterator because I’m a big dummy. Also brute forced (~8s). Might be worth adding a cache to skip all the questions that have been computed / done.
Solution
import { AdventOfCodeSolutionFunction } from "./solutions"; function MakeCombination(choices: Array, state: Array): Array { return state.map((v) => choices[v]); } function MakeStateArray(length: number) { const newArray = []; while (length-- > 0) newArray.push(0); return newArray; } function IncrementState(state: Array, max: number): [state: Array, overflow: boolean] { state[0]++; for (let index = 0; index < state.length; index++) { if (state[index] == max) { state[index] = 0; if (index + 1 == state.length) return [state, true]; state[index + 1]++; } } return [state, false]; } function GenerateCombinations(choices: Array, length: number): Array> { const states = MakeStateArray(length); const combinations: Array> = []; let done = false while (!done) { combinations.push(MakeCombination(choices, states)); done = IncrementState(states, choices.length)[1]; } return combinations; } enum Op { MUL = "*", ADD = "+", CON = "|", } function ApplyOp(a: number, b: number, op: Op): number { switch (op) { case Op.MUL: return a * b; case Op.ADD: return a + b; case Op.CON: return Number(`${a}${b}`); } } function ApplyOperatorsToNumbers(numbers: Array, ops: Array): number { let prev = ApplyOp(numbers[0], numbers[1], ops[0]); for (let index = 2; index < numbers.length; index++) { prev = ApplyOp(prev, numbers[index], ops[index - 1]) } return prev; } export const solution_7: AdventOfCodeSolutionFunction = (input) => { const numbers = // [{target: 123, numbers: [1, 2, 3, ...]}, ...] input.split("\n") .map( (v) => v.trim() .split(":") .map(v => v.trim().split(" ").map(v => Number(v))) ).map( (v) => { return { target: v[0][0], numbers: v[1] } } ); let part_1 = 0; let part_2 = 0; for (let index = 0; index < numbers.length; index++) { const target = numbers[index].target; const numbs = numbers[index].numbers; // GenerateCombinations(["+", "*"], 2) => [["+", "+"], ["+", "*"], ["*", "+"], ["*", "*"]] const combinations = GenerateCombinations([Op.ADD, Op.MUL], numbs.length - 1); // part 1 calculations for (let combinationIndex = 0; combinationIndex < combinations.length; combinationIndex++) { const combination = combinations[combinationIndex]; const result = ApplyOperatorsToNumbers(numbs, combination); if (result == target) { part_1 += result; break; } } const combinations2 = GenerateCombinations([Op.ADD, Op.MUL, Op.CON], numbs.length - 1); // part 2 calculations for (let combinationIndex = 0; combinationIndex < combinations2.length; combinationIndex++) { const combination = combinations2[combinationIndex]; const result = ApplyOperatorsToNumbers(numbs, combination); if (result == target) { part_2 += result; break; } } } return { part_1, part_2, } }
- stevenviola ( @stevenviola@programming.dev ) English2•7 days ago
Python
Takes ~5.3s on my machine to get both outputs. Not sure how to optimize it any further other than running the math in threads? Took me longer than it should have to realize a lot of unnecessary math could be cut if the running total becomes greater than the target while doing the math. Also very happy to see that none of the inputs caused the recursive function to hit Python’s max stack depth.
Code
import argparse import os class Calibrations: def __init__(self, target, operators) -> None: self.operators = operators self.target = target self.target_found = False def do_math(self, numbers, operation) -> int: if operation == "+": return numbers[0] + numbers[1] elif operation == "*": return numbers[0] * numbers[1] elif operation == "||": return int(str(numbers[0]) + str(numbers[1])) def all_options(self, numbers, last) -> int: if len(numbers) < 1: return last for j in self.operators: # If we found our target already, abort # If the last value is greater than the target, abort if self.target_found or last > self.target: return total = self.all_options( numbers[1:], self.do_math((last, numbers[0]), j) ) if total == self.target: self.target_found = True def process_line(self, line) -> int: numbers = [int(x) for x in line.split(":")[1].strip().split()] self.all_options(numbers[1:], numbers[0]) if self.target_found: return self.target return 0 def main() -> None: path = os.path.dirname(os.path.abspath(__file__)) parser = argparse.ArgumentParser(description="Bridge Repair") parser.add_argument("filename", help="Path to the input file") args = parser.parse_args() sum_of_valid = [0, 0] with open(f"{path}/{args.filename}", "r") as f: for line in f: line = line.strip() target = int(line.split(":")[0]) for idx, ops in enumerate([["+", "*"], ["+", "*", "||"]]): c = Calibrations(target, ops) found = c.process_line(line) sum_of_valid[idx] += found if found: break for i in range(0, 2): part = i + 1 print( "The sum of valid calibrations for Part " + f"{part} is {sum(sum_of_valid[:part])}" ) if __name__ == "__main__": main()
- ystael ( @ystael@beehaw.org ) 2•7 days ago
J
Didn’t try to make it clever at all, so it’s fairly slow (minutes, not seconds). Maybe rewriting
foldl_ops
in terms of destructive array update would improve matters, but the biggest problem is that I don’t skip unnecessary calculations (because we’ve already found a match or already reached too big a number). This is concise and follows clearly from the definitions, however.data_file_name =: '7.data lines =: cutopen fread data_file_name NB. parse_line yields a boxed vector of length 2, target ; operands NB. &. is "under": u &. v is v^:_1 @: u @: v with right rank of v parse_line =: monad : '(". &. >) (>y) ({.~ ; (}.~ >:)) '':'' i.~ >y' NB. m foldl_ops n left folds n by the string of binary operators named by m, NB. as indices into the global operators, the leftmost element of m naming NB. an operator between the leftmost two elements of n. #m must be #n - 1. foldl_ops =: dyad define if. 1 >: # y do. {. y else. (}. x) foldl_ops (((operators @. ({. x))/ 2 {. y) , 2 }. y) end. ) NB. b digit_strings n enumerates i.b^n as right justified digit strings digit_strings =: dyad : '(y # x) #:"1 0 i. x ^ y' feasible =: dyad define operators =: x NB. global 'target operands' =. y +./ target = ((# operators) digit_strings (<: # operands)) foldl_ops"1 operands ) compute =: monad : '+/ ((> @: {.) * (y & feasible))"1 parse_line"0 lines' result1 =: compute +`* concat =: , &.: (10 & #.^:_1) result2 =: compute +`*`concat
- wer2 ( @wer2@lemm.ee ) 2•7 days ago
Lisp
Could probably go much faster if I kept track of calculations to not repeat, but 4 seconds for part 2 on my old laptop is good enough for me. Also, not really a big change from part 1 to part 2.
Part 1 and 2
(defstruct calibration result inputs) (defun p1-process-line (line) (let ((parts (str:words line))) (make-calibration :result (parse-integer (car parts) :junk-allowed t) :inputs (mapcar #'parse-integer (cdr parts))))) (defun apply-opperators (c opps) (let ((accum (car (calibration-inputs c)))) (loop for o in opps for v in (cdr (calibration-inputs c)) until (> accum (calibration-result c)) if (eql o 'ADD) do (setf accum (+ accum v)) else if (eql o 'MUL) do (setf accum (* accum v)) else do (setf accum (+ v (* accum (expt 10 (1+ (floor (log v 10))))))) finally (return accum) ))) (defun generate-operators (item-count) (labels ((g-rec (c results) (if (< c 1) results (g-rec (1- c) (loop for r in results collect (cons 'ADD r) collect (cons 'MUL r)))))) (g-rec (1- item-count) '((ADD) (MUL))))) (defun generate-ops-hash (c gen-ops) (let ((h (make-hash-table))) (dotimes (x c) (setf (gethash (+ 2 x) h) (funcall gen-ops (+ 1 x)))) h)) (defun validate-calibration (c ops-h) (let ((r (calibration-result c)) (ops (gethash (length (calibration-inputs c)) ops-h))) (loop for o in ops for v = (apply-opperators c o) when (= v r) return t))) (defun run-p1 (file) (let ((calibrations (read-file file #'p1-process-line)) (ops (generate-ops-hash 13 #'generate-operators))) (loop for c in calibrations when (validate-calibration c ops) sum (calibration-result c)))) (defun generate-operators-p2 (item-count) (labels ((g-rec (c results) (if (< c 1) results (g-rec (1- c) (loop for r in results collect (cons 'ADD r) collect (cons 'MUL r) collect (cons 'CAT r)))))) (g-rec (1- item-count) '((ADD) (MUL) (CAT))))) (defun run-p2 (file) (let ((calibrations (read-file file #'p1-process-line)) (ops (generate-ops-hash 13 #'generate-operators-p2))) (loop for c in calibrations when (validate-calibration c ops) sum (calibration-result c))))
- hades ( @hades@lemm.ee ) 2•8 days ago
C#
public class Day07 : Solver { private ImmutableList<(long, ImmutableList)> equations; public void Presolve(string input) { equations = input.Trim().Split("\n") .Select(line => line.Split(": ")) .Select(split => (long.Parse(split[0]), split[1].Split(" ").Select(long.Parse).ToImmutableList())) .ToImmutableList(); } private bool TrySolveWithConcat(long lhs, long head, ImmutableList tail) { var lhs_string = lhs.ToString(); var head_string = head.ToString(); return lhs_string.Length > head_string.Length && lhs_string.EndsWith(head_string) && SolveEquation(long.Parse(lhs_string.Substring(0, lhs_string.Length - head_string.Length)), tail, true); } private bool SolveEquation(long lhs, ImmutableList rhs, bool with_concat = false) { if (rhs.Count == 1) return lhs == rhs[0]; long head = rhs[rhs.Count - 1]; var tail = rhs.GetRange(0, rhs.Count - 1); return (SolveEquation(lhs - head, tail, with_concat)) || (lhs % head == 0) && SolveEquation(lhs / head, tail, with_concat) || with_concat && TrySolveWithConcat(lhs, head, tail); } public string SolveFirst() => equations .Where(eq => SolveEquation(eq.Item1, eq.Item2)) .Select(eq => eq.Item1) .Sum().ToString(); public string SolveSecond() => equations .Where(eq => SolveEquation(eq.Item1, eq.Item2, true)) .Select(eq => eq.Item1) .Sum().ToString(); }
- JRaccoon ( @JRaccoon@discuss.tchncs.de ) English2•8 days ago
Java
Today was pretty easy one but for some reason I spent like 20 minutes overthinking part 2 when all it needed was one new
else if
. I initially through the concatenation operator would take precedence even tho it clearly says “All operators are still evaluated left-to-right” in the instructions…I’m sure there are optimizations to do but using parallelStreams it only takes around 300ms total on my machine so there’s no point really
The code
import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; public class Day7 { public static void main(final String[] _args) throws IOException { final List equations = Files.readAllLines(Path.of("2024\\07\\input.txt"), StandardCharsets.UTF_8).stream() .map(line -> line.split(":\\s")) .map(line -> new Equation( Long.parseLong(line[0]), Arrays.stream(line[1].split("\\s")) .map(Integer::parseInt) .toArray(Integer[]::new) ) ).toList(); final char[] part1Operators = {'+', '*'}; System.out.println("Part 1: " + equations.parallelStream() .mapToLong(equation -> getResultIfPossible(equation, part1Operators)) .sum() ); final char[] part2Operators = {'+', '*', '|'}; System.out.println("Part 2: " + equations.parallelStream() .mapToLong(equation -> getResultIfPossible(equation, part2Operators)) .sum() ); } private static Long getResultIfPossible(final Equation equation, final char[] operators) { final var permutations = Math.pow(operators.length, equation.values.length - 1); for (int i = 0; i < permutations; i++) { long result = equation.values[0]; int count = i; for (int j = 0; j < equation.values.length - 1; j++) { // If the result is already larger than the expected one, we can short circuit here to save some time if (result > equation.result) { break; } final char operator = operators[count % operators.length]; count /= operators.length; if (operator == '+') { result += equation.values[j + 1]; } else if (operator == '*') { result *= equation.values[j + 1]; } else if (operator == '|') { result = Long.parseLong(String.valueOf(result) + String.valueOf(equation.values[j + 1])); } else { throw new RuntimeException("Unsupported operator " + operator); } } if (result == equation.result) { return result; } } return 0L; } private static record Equation(long result, Integer[] values) {} }