this post was submitted on 07 Dec 2024
22 points (89.3% liked)

Advent Of Code

920 readers
79 users here now

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2024

Solution Threads

M T W T F S S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25

Rules/Guidelines

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

founded 1 year ago
MODERATORS
 

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

top 50 comments
sorted by: hot top controversial new old
[–] mykl 6 points 1 week ago* (last edited 1 week ago) (2 children)

Uiua

This turned out to be reasonably easy in Uiua, though this solution relies on macros which maybe slow it down.

(edit: removing one macro sped it up quite a bit)

(edit2: Letting Uiua build up an n-dimensional array turned out to be the solution, though sadly my mind only works in 3 dimensions. Now runs against the live data in around 0.3 seconds.)

Try it here

Data   ← ⊜(β–‘βŠœβ‹•βŠΈ(¬∈": "))βŠΈβ‰ @\n "190: 10 19\n3267: 81 40 27\n83: 17 5\n156: 15 6\n7290: 6 8 6 15\n161011: 16 10 13\n192: 17 8 14\n21037: 9 7 18 13\n292: 11 6 16 20"
Calib! ← β‰‘β—‡βŠ’β–½βŠΈβ‰‘β—‡(βˆˆβ™­/[^0]:Β°βŠ‚) # Calibration targets which can be constructed from their values.
&p/+Calib!βŠƒ(+|Γ—)Data
&p/+Calib!βŠƒ(+|Γ—|+×ⁿ:10+1βŒŠβ‚™β‚β‚€,)Data
[–] iAvicenna 3 points 1 week ago (1 children)

I feel like I need a Rosetta stone to read this code

[–] mykl 1 points 1 week ago (1 children)
[–] iAvicenna 2 points 1 week ago

thanks that indeed is a Rosetta stone

[–] [email protected] 3 points 1 week ago (1 children)

Thanks to your solution I learned more about how to use reduce :D

My solution did work for the example input but not for the actual one. When I went here and saw this tiny code block and you saying

This turned out to be reasonably easy

I was quite taken aback. And it's so much better performance-wise too :D (well, until part 2 comes along in my case. Whatever this black magic is you used there is too high for my fried brain atm)

[–] mykl 2 points 1 week ago (1 children)

Haha, sorry about that, it does seem quite smug :-) I went into it expecting it to be a nightmare of boxes and dimensions, but finding it something I could deal with was a massive relief. Of course once I had a working solution I reversed it back into a multi-dimensional nightmare. That's where the performance gains came from: about 10x speedup from letting Uiua build up as many dimensions as it needed before doing a final deshaping.

I enjoyed reading a different approach to this, and thanks for reminding me that β‹•$"__" exists, that's a great idiom to have up your sleeve.

Let me know if there's any bits of my solution that you'd like me to talk you through.

[–] [email protected] 2 points 1 week ago

No worries, it does seem a lot less difficult in hindsight now, my mind just blanked at what I expected to be a lot more code :))

That performance improvement is amazing, I'll definitely take a look at how that works in detail later. Just gotta recover from the mental stretch gymnastics trying to remember the state of the stack at different code positions

[–] iAvicenna 4 points 1 week ago* (last edited 1 week ago) (3 children)

Python

It is a tree search

def parse_input(path):

  with path.open("r") as fp:
    lines = fp.read().splitlines()

  roots = [int(line.split(':')[0]) for line in lines]
  node_lists = [[int(x)  for x in line.split(':')[1][1:].split(' ')] for line in lines]

  return roots, node_lists

def construct_tree(root, nodes, include_concat):

  levels = [[] for _ in range(len(nodes)+1)]
  levels[0] = [(str(root), "")]
  # level nodes are tuples of the form (val, operation) where both are str
  # val can be numerical or empty string
  # operation can be *, +, || or empty string

  for indl, level in enumerate(levels[1:], start=1):

    node = nodes[indl-1]

    for elem in levels[indl-1]:

      if elem[0]=='':
        continue

      if elem[0][-len(str(node)):] == str(node) and include_concat:
        levels[indl].append((elem[0][:-len(str(node))], "||"))
      if (a:=int(elem[0]))%(b:=int(node))==0:
        levels[indl].append((str(int(a/b)), '*'))
      if (a:=int(elem[0])) - (b:=int(node))>0:
        levels[indl].append((str(a - b), "+"))

  return levels[-1]

def solve_problem(file_name, include_concat):

  roots, node_lists = parse_input(Path(cwd, file_name))
  valid_roots = []

  for root, nodes in zip(roots, node_lists):

    top = construct_tree(root, nodes[::-1], include_concat)

    if any((x[0]=='1' and x[1]=='*') or (x[0]=='0' and x[1]=='+') or
           (x[0]=='' and x[1]=='||') for x in top):

      valid_roots.append(root)

  return sum(valid_roots)
load more comments (3 replies)
[–] LeixB 4 points 1 week ago (1 children)

Haskell

import Control.Arrow
import Data.Char
import Text.ParserCombinators.ReadP

numP = read <$> munch1 isDigit
parse = endBy ((,) <$> (numP <* string ": ") <*> sepBy numP (char ' ')) (char '\n')

valid n [m] = m == n
valid n (x : xs) = n > 0 && valid (n - x) xs || (n `mod` x) == 0 && valid (n `div` x) xs

part1 = sum . fmap fst . filter (uncurry valid . second reverse)

concatNum r = (+r) . (* 10 ^ digits r)
    where
        digits = succ . floor . logBase 10 . fromIntegral

allPossible [n] = [n]
allPossible (x:xs) = ((x+) <$> rest) ++ ((x*) <$> rest) ++ (concatNum x <$> rest)
    where
        rest = allPossible xs

part2 = sum . fmap fst . filter (uncurry elem . second (allPossible . reverse))

main = getContents >>= print . (part1 &&& part2) . fst . last . readP_to_S parse
[–] [email protected] 2 points 1 week ago

Oooh, some nice number theory going on there!

[–] [email protected] 3 points 1 week 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;
}

https://github.com/sjmulder/aoc/blob/master/2024/c/day07.c

[–] [email protected] 3 points 1 week ago (1 children)

Uiua

Credits to @[email protected] for the approach of using reduce and also how to split the input by multiple characters.
I can happily say that I learned quite a bit today, even though the first part made me frustrated enough that I went searching for other approaches ^^

Part two just needed a simple modification. Changing how the input is parsed and passed to the adapted function took longer than changing the function itself actually.

Run with example input here

PartOne ← (
  &rs ∞ &fo "input-7.txt"
  βŠœβ–‘β‰ @\n.
  ≑◇(βŠœβ–‘β‰ @:.)
  β‰‘βœβŠ‘β‹•0
  β‰‘βœ(Β°β–‘βŠ‘1)(βŠœβ‹•β‰ @ .)
  ⟜(⊑0⍉)

  # own attempt, produces a too low number
  # ≑(:βˆ©Β°β–‘Β°βŠŸ
  #   ⍣(⍀.◑⍣(1⍀.(≀/Γ—)⍀.(β‰₯/+),,)0
  #     βŠ™Β€β‹―β‡‘βΏ:2-1⊸⧻
  #     ⊞(β₯(⟜⍜(βŠ™(↙2))(⨬+Γ—βŠ™Β°βŠŸβŠ‘0)
  #         β†˜1
  #       )⧻.
  #       ⍀.=0⧻.
  #     )
  #     βˆˆβ™­β—Œ
  #   )0)

  # reduce approach found on the programming.dev AoC community by [email protected]
  ≑(β—‡(∈/(β—΄β™­[βŠƒ(+|Γ—)]))⊑0:Β°βŠ‚)
  Β°β–‘/+β–½
)

PartTwo ← (
  &rs ∞ &fo "input-7.txt"
  ⊜(β–‘βŠœβ‹•Β¬βˆˆ": ".)β‰ @\n.
  βŸœβ‰‘β—‡βŠ’
  ≑◇(∈/(β—΄β™­[β‰‘βŠƒβŠƒ(+|Γ—|β‹•$"__")]):Β°βŠ‚)
  Β°β–‘/+β–½
)

&p "Day 7:"
&pf "Part 1: "
&p PartOne
&pf "Part 2: "
&p PartTwo
[–] mykl 2 points 1 week ago (1 children)

Team Uiua in the house!

(β•―Β°β–‘Β°)β•―οΈ΅ ┻━┻

[–] [email protected] 2 points 1 week ago

Hell yeah!

┻━┻︡ (Β°β–‘Β°)/ οΈ΅ ┻━┻

[–] [email protected] 3 points 1 week ago* (last edited 1 week ago) (3 children)

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]
    ]
[–] LeixB 3 points 1 week ago

Love the fold on the list monad to apply the operations.

[–] [email protected] 2 points 1 week ago* (last edited 1 week ago) (1 children)

Since all operations increase the accumulator, I tried putting a guard (a <= x) in apply, but it doesn't actually help all that much (0.65s -> 0.43s).

[–] VegOwOtenks 2 points 1 week ago* (last edited 1 week ago) (1 children)

0.65 -> 0.43 sounds pretty strong, isn't that a one-fourth speedup?

Edit: I was able to achieve a 30% speed improvement using this on my solution

[–] [email protected] 2 points 1 week ago

It's not insignificant, sure, but I'd prefer 10x faster :D

Plus I'm not sure it's worth the loss of generality and readability. It is tempting to spend hours chasing this kind of optimization though!

[–] VegOwOtenks 2 points 1 week ago (2 children)

I wanted to this the way yo did, by repeatedly applying functions, but I didn't dare to because I like to mess up and spend some minutes debugging signatures, may I ask what your IDE setup is for the LSP-Hints with Haskell?
Setting up on my PC was a little bit of a pain because it needed matching ghc and ghcide versions, so I hadn't bothered doing it on my Laptop yet.

[–] LeixB 3 points 1 week ago (1 children)

I use neovim with haskell-tools.nvim plugin. For ghc, haskell-language-server and others I use nix which, among other benefits makes my development environment reproducible and all haskellPackages are built on the same version so there are no missmatches.

But, as much as I love nix, there are probably easier ways to setup your environment.

[–] VegOwOtenks 2 points 1 week ago

I just checked and I have haskell-tools.nvim on my PC but it somehow crashes the default config of the autocompletion for me, which I am too inexperienced to debug. I'll try it nonetheless, since I don't have autocompletion on the laptop anyways, thank you for the suggestion!

[–] [email protected] 2 points 1 week ago (1 children)

Ah, well, I have a bit of a weird setup. GHC is 9.8.4, built from git. I'm using HLS version 2.9.0.1 (again built from git) under Emacs with the LSP and flycheck packages. There are probably much easier ways of getting it to work :)

[–] VegOwOtenks 2 points 1 week ago

I envy emacs for all of its modes, but I don't think I'm relearning the little I know about vi. Thank you for the answer on the versions and building!

[–] VegOwOtenks 3 points 1 week ago (1 children)

Haskell

I'm not very proud, I copied my code for part two.

import Control.Arrow hiding (first, second)

import qualified Data.List as List
import qualified Data.Char as Char

parseLine l = (n, os)
        where
                n = read . takeWhile (Char.isDigit) $ l
                os = map read . drop 1 . words $ l

parse :: String -> [(Int, [Int])]
parse s = map parseLine . takeWhile (/= "") . lines $ s

insertOperators target (r:rs) = any (target ==) (insertOperators' r rs)
insertOperators' :: Int -> [Int] -> [Int]
insertOperators' acc []     = [acc]
insertOperators' acc (r:rs) = insertOperators' (acc+r) rs ++ insertOperators' (acc*r) rs

insertOperators2 target (r:rs) = any (target ==) (insertOperators2' r rs)
insertOperators2' :: Int -> [Int] -> [Int]
insertOperators2' acc []     = [acc]
insertOperators2' acc (r:rs) = insertOperators2' (acc+r) rs ++ insertOperators2' (acc*r) rs ++ insertOperators2' concatN rs
        where
                concatN = read (show acc ++ show r)

part1 ls = filter (uncurry insertOperators)
        >>> map fst
        >>> sum
        $ ls
part2 ls = filter (uncurry insertOperators2)
        >>> map fst
        >>> sum
        $ ls

main = getContents >>= print . (part1 &&& part2) . parse
[–] [email protected] 2 points 1 week ago

If you get the right answer, it's ok πŸ‘

[–] [email protected] 2 points 1 week ago* (last edited 1 week 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 and toTernary.

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..<len:
    result[i] = n mod 3
    n = n div 3

proc solve(input: string): AOCSolution[int, int] =
  for line in input.splitLines():
    let parts = line.split({':',' '})
    let res = parts[0].parseInt
    var values: seq[int]
    for i in 2..parts.high:
      values.add parts[i].parseInt

    let opsCount = values.len - 1
    var solvable = (p1: false, p2: false)
    for s in 0 ..< 3^opsCount:
      var sum = values[0]
      let ternary = s.toTernary(opsCount)
      for i, c in ternary:
        case c
        of 0: sum *= values[i+1]
        of 1: sum += values[i+1]
        of 2: sum.concat values[i+1]
        else: raiseAssert"!!"
      if sum == res:
        if ternary.count(2) == 0:
          solvable.p1 = true
        solvable.p2 = true
        if solvable == (true, true): break
    if solvable.p1: result.part1 += res
    if solvable.p2: result.part2 += res

Codeberg repo

[–] [email protected] 2 points 1 week ago (1 children)

Factor

spoiler

TUPLE: equation value numbers ;
C: <equation> equation

: get-input ( -- equations )
  "vocab:aoc-2024/07/input.txt" utf8 file-lines [
    split-words unclip but-last string>number
    swap [ string>number ] map <equation>
  ] 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 ;

[–] [email protected] 1 points 1 week ago* (last edited 1 week ago)

Slow and dumb gets it done! I may revisit this when I give up on future days.

[–] [email protected] 2 points 1 week ago

C#

public class Day07 : Solver
{
  private ImmutableList<(long, ImmutableList<long>)> 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<long> 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<long> 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();
}
[–] TunaCowboy 2 points 1 week ago* (last edited 1 week ago) (1 children)

python

45s on my machine for first shot, trying to break my will to brute force πŸ˜…. I'll try improving on it in a bit after I smoke another bowl and grab another drink.

solution

import itertools
import re
import aoc

def ltr(e):
    r = int(e[0])
    for i in range(1, len(e), 2):
        o = e[i]
        n = int(e[i + 1])
        if o == '+':
            r += n
        elif o == '*':
            r *= n
        elif o == '||':
            r = int(f"{r}{n}")
    return r

def pl(l, os):
    d = [int(x) for x in re.findall(r'\d+', l)]
    t, ns = d[0], d[1:]
    ops = list(itertools.product(os, repeat=len(ns) - 1))
    for o in ops:
        e = str(ns[0])
        for i, op in enumerate(o):
            e += f" {op} {ns[i + 1]}"
        r = ltr(e.split())
        if r == t:
            return r
    return 0

def one():
    lines = aoc.get_lines(7)
    acc = 0
    for l in lines:
        acc += pl(l, ['+', '*'])
    print(acc)

def two():
    lines = aoc.get_lines(7)
    acc = 0
    for l in lines:
        acc += pl(l, ['+', '*', '||'])
    print(acc)

one()
two()

[–] [email protected] 1 points 1 week ago (2 children)

What a horrible way to name variables

[–] TunaCowboy 1 points 1 week ago* (last edited 1 week ago) (1 children)

It's not a long lived project, it's a puzzle, and once solved never needs to run again. My objective here is to get the correct answer, not win a style contest.

Can you provide a link to your solution? I'd like to check it out.

[–] [email protected] 1 points 1 week ago* (last edited 1 week ago)

My initial comment was a bit harsh, I'm sorry for that. It was meant to be a bit of a joke. Anyway here's my code. Do note that I don't do the challenges timed so I have a bit more time to name my variables accordingly. Takes 35 seconds to run on a pc with a AMD Ryzen 5 5600

import sys
from tqdm import tqdm


input = sys.stdin.read()

def all_operator_permutations(operator_count):
    if operator_count == 0:
        return [[]]

    smaller_permutations = all_operator_permutations(operator_count-1)
    return [
            *[['+', *ops] for ops in smaller_permutations],
            *[['*', *ops] for ops in smaller_permutations],
            *[['||', *ops] for ops in smaller_permutations],
            ]

def test_operators(ops, values):
    res = values.pop(0)
    for op in ops:
        match op:
            case '*':
                res *= values.pop(0)
            case '+':
                res += values.pop(0)
            case '||':
                res = int(f"{res}{values.pop(0)}")
    return res


total_calibration_result = 0

for line in tqdm(input.splitlines()[:]):
    target, *tail = line.split(':')
    target = int(target)
    values = [int(val) for val in tail[0].split()]

    all_perms = all_operator_permutations(len(values) - 1)
    ops = all_perms.pop()
    while True:
        res = test_operators(ops, values.copy())
        if res == target:
            total_calibration_result += target
            break
        if not all_perms:
            break
        ops = all_perms.pop()

print(total_calibration_result)
[–] [email protected] 1 points 1 week ago

a e o, Killer Tofu. That's all I can think of reading this code.

[–] [email protected] 2 points 1 week ago* (last edited 1 week ago) (1 children)

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 using AsParallel, around half a second without.

::: spoiler C#

List<(long, int[])> data = new List<(long, int[])>();

public void Input(IEnumerable<string> 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<int> 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<int> num, long carried = 0)
{
  var next = num[0];
  // Get the 10 logarithm for the next number, expand the carried value by 10^<next 10log + 1>, 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);
}
[–] [email protected] 1 points 1 week ago (1 children)

you meant depth first, right? since you're using recursion

[–] [email protected] 1 points 1 week ago

That is true, I've evidently not had enough coffee yet this morning.

[–] [email protected] 2 points 1 week 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<Equation> 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) {}
}

[–] mykl 2 points 1 week ago* (last edited 1 week ago)

Dart

Suspiciously easy, so let's see how tomorrow goes.... (edit: forgot to put the language! Dart for now, thinking about Uiua later)

import 'package:more/more.dart';

var ops = [(a, b) => a + b, (a, b) => a * b, (a, b) => int.parse('$a$b')];

bool canMake(int target, List<int> ns, int sofar, dynamic ops) {
  if (ns.isEmpty) return target == sofar;
  for (var op in ops) {
    if (canMake(target, ns.sublist(1), op(sofar, ns.first), ops)) return true;
  }
  return false;
}

solve(List<String> lines, dynamic ops) {
  var sum = 0;
  for (var line in lines.map((e) => e.split(' '))) {
    var target = int.parse(line.first.skipLast(1));
    var ns = line.skip(1).map(int.parse).toList();
    sum += (canMake(target, ns.sublist(1), ns.first, ops)) ? target : 0;
  }
  return sum;
}

part1(List<String> lines) => solve(lines, ops.sublist(0, 2));
part2(List<String> lines) => solve(lines, ops);
[–] [email protected] 2 points 1 week ago (1 children)

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()

https://github.com/stevenviola/advent-of-code-2024

[–] iAvicenna 2 points 1 week ago* (last edited 1 week ago) (1 children)

If you havent already done so, doing it in the form of "tree search", the code completes in the blink of an eye (though on a high end cpu 11th Gen Intel(R) Core(TM) i7-11800H @ 2.30GHz). posted the code below

[–] [email protected] 1 points 1 week ago (1 children)

Thanks! yup, I figured there would be a way. You're right, much faster, on my machine with your code, this is the speed:

$ time python3 day7.py 
4555081946288
227921760109726

real    0m0.171s

I'll have to take a look to understand how that works to be better.

[–] Acters 1 points 1 week ago* (last edited 1 week ago)

I posted my solution here and found my way to finish 30 milliseconds faster.(~100ms for his, and ~66 ms for mine) However, as I noted I stop prematurely sometimes. Which seems to work with my given input. but here is the one that makes sure it gets to the end of the list of integers:

code

def main(input_data):
    input_data = input_data.replace('\r', '')
    parsed_data = {int(line[0]): [int(i) for i in line[1].split()[::-1]] for line in [l.split(': ') for l in input_data.splitlines()]}
    part1 = 0
    part2 = 0
    for item in parsed_data.items():
        root, num_array = item
        part_1_branches = [set() for _ in range(len(num_array)+1)]
        part_2_branches = [set() for _ in range(len(num_array)+1)]
        part_1_branches[0].add(root)
        part_2_branches[0].add(root)
        for level,i in enumerate(num_array):
            if len(part_1_branches[level]) == 0 and len(part_2_branches[level]) == 0:
                break

            for branch in part_1_branches[level]:
                if level==len(num_array)-1:
                    if branch == i:
                        part1 += root
                        break
                if branch % i == 0:
                    part_1_branches[level+1].add(branch//i)
                if branch - i > 0:
                    part_1_branches[level+1].add(branch-i)

            for branch in part_2_branches[level]:
                if level==len(num_array)-1:
                    if (branch == i or str(branch) == str(i)):
                        part2 += root
                        break
                if branch % i == 0:
                    part_2_branches[level+1].add(branch//i)
                if branch - i > 0:
                    part_2_branches[level+1].add(branch-i)
                if str(i) == str(branch)[-len(str(i)):]:
                    part_2_branches[level+1].add(int(str(branch)[:-len(str(i))].rjust(1,'0')))
    
    print("Part 1:", part1, "\nPart 2:", part2)
    return [part1, part2]

if __name__ == "__main__":
    with open('input', 'r') as f:
        main(f.read())

[–] [email protected] 2 points 1 week 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<T>(choices: Array<T>, state: Array<number>): Array<T> {
    return state.map((v) => choices[v]);
}

function MakeStateArray(length: number) {
    const newArray = [];
    while (length-- > 0)
        newArray.push(0);

    return newArray;
}

function IncrementState(state: Array<number>, max: number): [state: Array<number>, 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<T>(choices: Array<T>, length: number): Array<Array<T>> {
    const states = MakeStateArray(length);
    const combinations: Array<Array<T>> = [];

    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<number>, ops: Array<Op>): 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,
    }
}

[–] [email protected] 2 points 1 week 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))))

[–] [email protected] 1 points 1 week ago* (last edited 1 week 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 file Utils.kt, which can be found in my repo.

Solution

import kotlin.collections.any
import kotlin.math.pow

fun main() {
    fun part1(input: List<String>): Long {
        val operations = setOf(CalibrationOperation.Plus, CalibrationOperation.Multiply)
        return generalizedSolution(input, operations)
    }

    fun part2(input: List<String>): 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<String>) = input.map {
    val calibrationResultAndInput = it.split(':')
    calibrationResultAndInput[0].toLong() to calibrationResultAndInput[1].split(' ').filter { it != "" }.map { it.toLong() }
}

fun generalizedSolution(input: List<String>, operations: Set<CalibrationOperation>): Long {
    val parsedInput = parseInputDay7(input)
    val operationsPermutations = CalibrationOperation.operationPermutationSequence(*operations.toTypedArray()).take(calculatePermutationsNeeded(parsedInput, operations)).toList()
    return sumOfPossibleCalibrationEquations(parsedInput, operationsPermutations)
}

fun calculatePermutationsNeeded(parsedInput: List<Pair<Long, List<Long>>>, operations: Set<CalibrationOperation>): Int {
    val highestNumberOfOperations = parsedInput.maxOf { it.second.size - 1 }
    return (1..highestNumberOfOperations).sumOf { operations.size.toDouble().pow(it).toInt() }
}

fun sumOfPossibleCalibrationEquations(parsedInput: List<Pair<Long, List<Long>>>, operationPermutationCollection: Collection<OperationPermutation>): 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<CalibrationOperation>

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<OperationPermutation> {
            val cache = mutableListOf<OperationPermutation>()
            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++
                    } 
                }
            )
        }
    }
}


[–] Karmmah 1 points 1 week ago

Julia

Took quite some time to debug but in the end I think it's a nice solution using base 2 and 3 numbers counting up to check all operator combinations.

Code

function readInput(inputFile::String)::Vector{Vector{Int}}
	f = open(inputFile,"r")
	lines::Vector{String} = readlines(f)
	close(f)
	equations::Vector{Vector{Int}} = []
	function getValues(line::String)
		return map(sp->parse(Int,sp),(sp=split(line," ");sp[1]=sp[1][1:end-1];sp))
	end
	map(l->push!(equations,getValues(l)),lines)
	return equations
end

function checkEq(eq::Vector{Int},withConCat::Bool)::Bool
	function calcEq(eq::Vector{Int},operators::Vector{Int},withConCat::Bool)::Int
		res::Int = eq[2]
		for (i,op) in enumerate(operators)
			if op == 0 #+
				res += eq[i+2]
			elseif op ==1 #*
				res *= eq[i+2]
			else #op==2 ||
				res = parse(Int,string(res)*string(eq[i+2]))
			end
		end
		return res
	end
	opInt::Int = 0
	operators = Vector{Int}(undef,length(eq)-2)
	while opInt < (withConCat ? 3^(length(eq)-2) : 2^(length(eq)-2))
		withConCat==true ? operators=digits(opInt,base=3,pad=length(eq)-2) : operators=digits(opInt,base=2,pad=length(eq)-2)
		#calcEq(eq,operators,withConCat)==eq[1] ? (return true) : opInt -= 1
		calcEq(eq,operators,withConCat)==eq[1] ? (return true) : opInt += 1
	end
	return false
end

function calcTotCalRes(equations::Vector{Vector{Int}},withConCat::Bool)::Int
	totCalRes::Int = 0
	for e in equations
		checkEq(e,withConCat) ? totCalRes+=e[1] : nothing
	end
	return totCalRes
end

@info "Part 1"
println("result: $(calcTotCalRes(readInput("day07Input"),false))")
@info "Part 2"
println("result: $(calcTotCalRes(readInput("day07Input"),true))")

load more comments
view more: next β€Ί