26

Day 2: Gift Shop

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 33 comments
sorted by: hot top new old
[-] ystael@beehaw.org 8 points 2 months ago

An ID is invalid if and only if it is divisible by a number of the form 1001001001, where the 1's are separated by the same number of 0's and the block length times the number of blocks equals the digit length of the ID. Given that, the problem reduces to summing the members of some arithmetic progressions; we never have to iterate over the members of a range at all.

(ql:quickload :str)

(defun parse-range (range)
  (mapcar #'parse-integer (str:split "-" range)))

(defun parse-line (line)
  (mapcar #'parse-range (str:split "," line)))

(defun read-inputs (filename)
  (let ((input-lines (uiop:read-file-lines filename)))
    (parse-line (car input-lines))))

(defun split-range (start end)
  "Split the range (start end) into a list of ranges whose bounds have same number of digits."
  (let ((start-digits (1+ (floor (log start 10))))
        (end-digits (1+ (floor (log end 10)))))
    (if (< start-digits end-digits)
        (cons (list start (1- (expt 10 start-digits)))
              (split-range (expt 10 start-digits) end))
        (list (list start end)))))

(defun sum-multiples-in-range (d start end)
  "Add up the sum of all multiples n of d satisfying start <= n <= end."
  (multiple-value-bind (q0 r0) (floor start d)
    ;; q1, q2 are coefficients of the least and greatest multiple of d potentially in range
    (let ((q1 (if (zerop r0) q0 (1+ q0)))
          (q2 (floor end d)))
      (if (> q1 q2)
          0
          (flet ((arith-up-to (n) (floor (* n (1+ n)) 2)))
            (* d (- (arith-up-to q2) (arith-up-to (1- q1)))))))))

(defun sum-invalid-in-range (range repeat-count)
  "Add up the sum of all IDs in range start <= n <= end which are invalid due to having
  exactly repeat-count repeats."
  (loop for homogeneous-range in (apply #'split-range range)
        sum (destructuring-bind (hstart hend) homogeneous-range
              (let ((digits (1+ (floor (log hstart 10)))))
                (if (not (zerop (mod digits repeat-count)))
                    0
                    (let ((divisor
                            (loop for k from 0 to (1- digits) by (floor digits repeat-count)
                                  sum (expt 10 k))))
                      (sum-multiples-in-range divisor hstart hend)))))))

(defun main-1 (filename)
  (reduce #'+ (mapcar #'(lambda (range) (sum-invalid-in-range range 2))
                      (read-inputs filename))))

(defun sum-all-invalids-in-range (range)
  "Add up the sum of _all_ invalid IDs (with any available repeat count) in range."
  ;; Composite repeat counts will be overcounted. Because the maximum digit length of
  ;; inputs is limited, we can cheat and just use an explicit constant for weights.
  (let ((repeat-weights '((2 1) (3 1) (5 1) (6 -1) (7 1) (10 -1))))
    (loop for repeat-weight in repeat-weights
          sum (destructuring-bind (repeat-count weight) repeat-weight
                (* weight (sum-invalid-in-range range repeat-count))))))

(defun main-2 (filename)
  (reduce #'+ (mapcar #'sum-all-invalids-in-range (read-inputs filename))))
[-] strlcpy@lemmy.sdf.org 4 points 2 months ago

Awesome! I was looking for this type of solution and made some progress but couldn't get to it

[-] strlcpy@lemmy.sdf.org 5 points 2 months ago* (last edited 2 months ago)

C

There are interesting analytical observations to be made about the problem to sidestep most of the actual iteration, but I wasn't up to working it all out and the runtime was pretty much instant anyway.

Here's my original solution with ints, using divions with powers of 10 to do the splitting: day02-u64.c

But I'm only doing the C implementations to prototype my assembly solutions, and dealing with 64-bit numbers, especially divisions, on x86-16 is painful, so I rewrote the solution using a fixed-length string "numbers" instead: day02.c

Still working on the assembly version

Assembly update: have part 1 working now! It's dog slow on DOSBox, but on QEMU it's good: day02.asm

[-] h4x0r@lemmy.dbzer0.com 4 points 2 months ago* (last edited 2 months ago)

c

#include "aoc.h"
#include <stdio.h>
#include <string.h>

constexpr usize LINE_BUFSZ = (1 << 9);
constexpr usize PID_BUFSZ = (1 << 4);

static void
solve(strl re) {
  FILE* input = fopen("input", "r");
  c8 line[LINE_BUFSZ] = {};
  fgets(line, sizeof(line), input);
  line[strcspn(line, "\n")] = 0;
  usize total = 0;
  strc tok = strtok(line, ",");
  while (tok) {
    Point rng = {};
    sscanf(tok, "%zu-%zu", &rng.x, &rng.y);
    for (usize i = rng.x; i <= rng.y; i++) {
      c8 pid[PID_BUFSZ] = {};
      snprintf(pid, sizeof(pid), "%zu", i);
      is_regex_match(pid, re) ? total += i : 0;
    }
    tok = strtok(nullptr, ",");
  }
  fclose(input);
  printf("%zu\n", total);
}

i32
main(void) {
  solve("^(.+)\\1$");
  solve("^(.+)\\1+$");
}
[-] h4x0r@lemmy.dbzer0.com 3 points 2 months ago

One last time with threads.

p1 isolated: 12ms

p2 isolated: 13ms

combined run: 25ms

typedef struct job_s {
  Mode mode;
  Point rng;
  usize total;
} Job;

static void*
worker(void* a) {
  Job* job = a;
  (job->mode == MODE_ONE) ? one(job->rng, &job->total)
                          : two(job->rng, &job->total);
  return nullptr;
}

static void
solve(Mode mode) {
  FILE* input = fopen("input", "r");
  c8 line[LINE_BUFSZ] = {};
  fgets(line, sizeof(line), input);
  line[strcspn(line, "\n")] = 0;
  fclose(input);
  usize nrng = 1;
  for (strc s = line; *s; s++) {
    if (*s == ',') {
      nrng++;
    }
  }
  Job* jobs = calloc(nrng, sizeof(*jobs));
  pthread_t* threads = calloc(nrng, sizeof(*threads));
  usize idx = 0;
  strc tok = strtok(line, ",");
  while (tok) {
    sscanf(tok, "%zu-%zu", &jobs[idx].rng.x, &jobs[idx].rng.y);
    jobs[idx].mode = mode;
    tok = strtok(nullptr, ",");
    idx++;
  }
  for (usize i = 0; i < nrng; i++) {
    pthread_create(&threads[i], nullptr, worker, &jobs[i]);
  }
  usize total = 0;
  for (usize i = 0; i < nrng; i++) {
    pthread_join(threads[i], nullptr);
    total += jobs[i].total;
  }
  free(threads);
  free(jobs);
  printf("%zu\n", total);
}

i32
main(void) {
  solve(MODE_ONE);
  solve(MODE_TWO);
}
[-] h4x0r@lemmy.dbzer0.com 3 points 2 months ago

Circling back around to go faster.

p1 isolated: 76ms

p2 isolated: 82ms

combined run: 156ms

static void
one(Point rng, usize* total) {
  for (usize i = rng.x; i <= rng.y; i++) {
    c8 pid[PID_BUFSZ] = {};
    snprintf(pid, sizeof(pid), "%zu", i);
    usize len = strlen(pid);
    if (len % 2 != 0) {
      continue;
    }
    usize hlen = len / 2;
    c8 a[PID_BUFSZ] = {};
    memcpy(a, pid, hlen);
    c8 b[PID_BUFSZ] = {};
    memcpy(b, pid + hlen, hlen);
    if (strcmp(a, b) == 0) {
      *total += i;
    }
  }
}

static void
two(Point rng, usize* total) {
  for (usize i = rng.x; i <= rng.y; i++) {
    c8 pid[PID_BUFSZ] = {};
    snprintf(pid, sizeof(pid), "%zu", i);
    usize len = strlen(pid);
    for (usize j = 1; j <= len / 2; j++) {
      if (len % j != 0) {
        continue;
      }
      bool valid = true;
      for (usize k = j; k < len; k++) {
        if (pid[k] != pid[k - j]) {
          valid = false;
          break;
        }
      }
      if (valid) {
        *total += i;
        break;
      }
    }
  }
}

static void
solve(Mode mode) {
  FILE* input = fopen("input", "r");
  c8 line[LINE_BUFSZ] = {};
  fgets(line, sizeof(line), input);
  line[strcspn(line, "\n")] = 0;
  usize total = 0;
  strc tok = strtok(line, ",");
  while (tok) {
    Point rng = {};
    sscanf(tok, "%zu-%zu", &rng.x, &rng.y);
    (mode == MODE_ONE ? one : two)(rng, &total);
    tok = strtok(nullptr, ",");
  }
  fclose(input);
  printf("%zu\n", total);
}

i32
main(void) {
  solve(MODE_ONE);
  solve(MODE_TWO);
}
[-] CameronDev@programming.dev 2 points 2 months ago

I like your regexes a lot better than mine. I wonder if there is a perf difference between them. I'll have to try.

[-] tranzystorek_io@beehaw.org 4 points 2 months ago
[-] Deebster@programming.dev 4 points 2 months ago

Another day where the dumb way would have so much quicker and easier, but I'm not competing for time.

I decided to solve it numerically without regex or using to_string(), which was more taxing for the ol' grey matter but is perhaps fairly optimal (if I bothered to pre-compute all those pow() calls, anyway).

Part 2 runs in 35ms (on my AMD Ryzen 7 9800X3D), whereas the to_string() version runs in 40ms. So... not really worth it, and it's less readable.

Rust

use std::fs;

use color_eyre::eyre::{Result, bail};

type InvalidChecker = fn(usize) -> bool;

fn sum_invalids(input: &str, checkfn: InvalidChecker) -> Result<usize> {
    let total = input
        .trim()
        .split(',')
        .map(|idrange| {
            if let Some((start, end)) = idrange.split_once('-') {
                let mut sum = 0;
                for n in start.parse::<usize>()?..=end.parse::<usize>()? {
                    if checkfn(n) {
                        sum += n;
                    }
                }
                Ok(sum)
            } else {
                bail!("Couldn't parse {idrange}")
            }
        })
        .sum::<Result<usize, _>>()?;
    Ok(total)
}

fn is_invalid_p1(n: usize) -> bool {
    let len = n.ilog10() + 1;
    // odd-length numbers can't repeat
    if len % 2 == 1 {
        return false;
    }

    let lhs = n / 10_usize.pow(len / 2);
    let rhs = n - (lhs * 10_usize.pow(len / 2));
    lhs == rhs
}

const SPANS: &[&[u32]] = &[
    &[],              // i = 0
    &[],              // i = 1
    &[1],             // i = 2
    &[1],             // i = 3
    &[1, 2],          // i = 4
    &[1],             // i = 5
    &[1, 2, 3],       // i = 6
    &[1],             // i = 7
    &[1, 2, 4],       // i = 8
    &[1, 3],          // i = 9
    &[1, 2, 5],       // i = 10
    &[1],             // i = 11
    &[1, 2, 3, 4, 6], // i = 12
];

fn is_invalid_p2(n: usize) -> bool {
    let len = n.ilog10() + 1;
    // 1-length numbers can't repeat
    if len == 1 {
        return false;
    }

    SPANS[len as usize].iter().any(|&span| {
        let lhs = n / 10_usize.pow(len - span);
        let mut remainder = n;
        let mut rhs = lhs;
        (2..=(len / span)).all(|i| {
            remainder -= rhs * 10_usize.pow(len - (i - 1) * span);
            rhs = remainder / 10_usize.pow(len - i * span);
            lhs == rhs
        })
    })
}

fn part1(filepath: &str) -> Result<usize> {
    let input = fs::read_to_string(filepath)?;
    let res = sum_invalids(&input, is_invalid_p1)?;
    Ok(res)
}

fn part2(filepath: &str) -> Result<usize> {
    let input = fs::read_to_string(filepath)?;
    let res = sum_invalids(&input, is_invalid_p2)?;
    Ok(res)
}

to_string version:

fn is_invalid_p2(n: usize) -> bool {
    let s = n.to_string();
    let len = s.len();
    // 1-length numbers can't repeat
    if len == 1 {
        return false;
    }

    SPANS[len].iter().any(|&span| {
        let span = span as usize;
        let lhs = &s[0..span].as_bytes();
        s.as_bytes().chunks(span).all(|rhs| *lhs == rhs)
    })
}
[-] CameronDev@programming.dev 4 points 2 months ago* (last edited 2 months ago)

When you have regex, everything looks like a haystack.

Nearly solved pt2 by accident in pt1. Just showing the invalid checks, rest of the code is uninteresting.

fn check_invalid(p0: usize) -> bool {
        let str = format!("{}", p0);
        let len = str.len();
        if len % 2 == 1 {
            return false;
        }
        let len = len / 2;
        str[0..len] == str[len..]
    }

    fn check_invalid2(p0: usize) -> bool {
        let str = format!("{}", p0);
        let re = Regex::new(r"^([0-9]{1,})\1{1,}$").unwrap();
        if re.is_match(str.as_bytes()).unwrap() {
            return true;
        }
        false
    }

edit: The bot worked as well! With some slight human intervention. Tomorrow might be automatic if we are lucky.

[-] CameronDev@programming.dev 3 points 2 months ago* (last edited 2 months ago)

Regex free solution. Much faster (233ms vs ~4s)

  fn check_invalid3(p0: usize) -> u32 {
        let mut i = 0;
        let mut found_count = 0;
        loop {
            let mut v = p0;
            i += 1;
            let mask = 10_usize.pow(i);
            if mask >= p0 {
                // Mask is larger than input, we have exhausted available matchers.
                return found_count;
            }
            let remainer = v % mask;
            if remainer < 10_usize.pow(i - 1) {
                // Zero prefix, won't be a pattern. (01, 002, etc)
                continue;
            }
            let mut count = 1;
            loop {
                let new_v = v / mask;
                if new_v % mask != remainer {
                    // doesnt repeat.
                    break;
                }
                if new_v / mask == 0 {
                    // has repeated, so we have found at least one pattern. Lets keep going to see if there is a simpler pattern.
                    found_count = count;
                    break;
                }
                count += 1;
                v = new_v;
            }
        }
    }
[-] CameronDev@programming.dev 2 points 2 months ago

heh, recompiling the regex was wasting 80% of my time :D

[-] Deebster@programming.dev 2 points 2 months ago

Every so often I look for a library that will compile the regex at compile time - iirc, there's some stuff needing to made const fn before that can happen.

Last time I used regex, lazy_static was the way to go; I assume that regex can go in OnceCell nowadays.

[-] CameronDev@programming.dev 2 points 2 months ago

I just passed it around, felt easier and rust-like. Compile time would be nice, but I have a vague feeling this would be too restrictive for some regexes/engines?

[-] Deebster@programming.dev 3 points 2 months ago

Maybe? There's some deep wizardry shown in some people's macros so a regex feels fairly basic in comparison.

[-] VegOwOtenks@lemmy.world 4 points 2 months ago* (last edited 2 months ago)

Easy one to get through, no edge-cases biting me this time.

I learned this year again: running in interpreted mode can cause significant slowdowns. Later, I'll hopefully find the time clean it up, this solution feels ugly. Reading everyone else did it also like this or with regex makes me feel better about it though.

Haskell

Code from this morning (AoC is at 06:00 at my place)

module Main (main) where
import qualified Text.ParserCombinators.ReadP as ReadP
import Numeric.Natural (Natural)
import Control.Monad ((<$!>), guard)
import qualified Data.List as List
import Control.Arrow ((>>>))
import qualified Data.Text as Text
import qualified Data.Foldable as Foldable

newtype Range = Range { getRange :: (Natural, Natural) }
  deriving Show

parseRange :: ReadP.ReadP Range
parseRange = do
  n1 <- ReadP.readS_to_P reads
  _ <- ReadP.char '-'
  n2 <- ReadP.readS_to_P reads
  pure . Range $ (n1, n2)

parseLine :: ReadP.ReadP [Range]
parseLine = parseRange `ReadP.sepBy` ReadP.char ','

main :: IO ()
main = do
  ranges <- fst . last . ReadP.readP_to_S parseLine <$!> getContents
  print $ part1 ranges
  print $ part2 ranges

part1 :: [Range] -> Natural
part1 = List.concatMap (uncurry enumFromTo . getRange)
  >>> List.filter isDoublePattern
  >>> Foldable.sum

part2 :: [Range] -> Natural
part2 = List.concatMap (uncurry enumFromTo . getRange)
  >>> List.filter isMultiplePattern
  >>> Foldable.sum

isMultiplePattern :: Natural -> Bool
isMultiplePattern n = let
    textN = Text.show n
    textLength = Text.length textN
  in flip any (divisorsOf textLength) $ \ divisor -> let
      patternLength = textLength `div` divisor
      patternPart = Text.take (fromIntegral patternLength) textN
    in Text.replicate (fromIntegral divisor) patternPart == textN

isDoublePattern :: Natural -> Bool
isDoublePattern n = let
  textN = Text.show n
  evenLength = even (Text.length textN)
  (first, second) = Text.splitAt (Text.length textN `div` 2) textN
  in evenLength && first == second

divisorsOf :: Integral b => b -> [b]
divisorsOf n = do
  x <- [2..n]
  guard ((n `mod` x) == 0)
  pure x

Using the interpreter, this solution made me wait for two minutes until I could submit. x.x After testing it again in compiled mode, it only takes four seconds.

[-] CameronDev@programming.dev 3 points 2 months ago

120s -> 4s is a solid optimisation :)

[-] Zikeji@programming.dev 3 points 2 months ago

Javascript

More bruteforcing! There are probably better ways to do this but I'm happy enough with this lol.

SolutionYou can replace the require('fs') on the first line with the input and run it in your browser console as well.

const input = require('fs').readFileSync('input-day2.txt', 'utf-8');

let idsPart1 = [];
let idsPart2 = [];

input.split(',').forEach(range => {
    const [start, end] = range.split('-').map(v => parseInt(v, 10));
    let cursor = start;

    while (cursor <= end) {
        const cursorString = cursor.toString(10);

        // part 1 check
        let halfLength = Math.floor(cursorString.length / 2);
        let left = cursorString.slice(0, halfLength);
        let right = cursorString.slice(halfLength, cursorString.length);
        if (left === right) {
            idsPart1.push(cursor);
        }

        // part 2 check
        let sequenceLength = 1;
        while (sequenceLength <= halfLength) {
            const sequence = cursorString.slice(0, sequenceLength);
            let builtString = sequence;
            while (builtString.length < cursorString.length) {
                builtString = `${builtString}${sequence}`;
            }
            if (builtString === cursorString) {
                idsPart2.push(cursor);
                break;
            }

            sequenceLength += 1;
        }

        cursor += 1;
    }
})

const answer1 = idsPart1.flat().reduce((acc, cur) => acc += cur, 0);
const answer2 = idsPart2.flat().reduce((acc, cur) => acc += cur, 0);

console.log(`Part 1 Answer: ${answer1}`);
console.log(`Part 2 Answer: ${answer2}`);

[-] Nighed@feddit.uk 3 points 2 months ago* (last edited 2 months ago)

C# - no regex, 235ms for part 2. down to 51ms when I put each range on its own thread

spoiler

public class Day2
{
    public void Go()
    {
        //var input = File.ReadAllText("Day2/ExampleData.txt");
        var input = File.ReadAllText("Day2/RealData.txt");
        var inputs = input.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        var pairs = inputs.Select(s =>
            s.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
            .Select(s => new Tuple<Int64,Int64>(Int64.Parse(s[0]), Int64.Parse(s[1])));

        Int64 sum = 0;
        foreach (var pair in pairs)
        {
            sum += CheckPair2(pair);
        }

        Console.WriteLine($"Total invalid sum: {sum}");
    }

    public Int64 CheckPair(Tuple<Int64, Int64> pair)
    {
        Int64 sum = 0;
        
        for (Int64 i = pair.Item1; i <= pair.Item2; i++)
        {
            var s =  i.ToString();
            if (s.Length%2 == 1)
            {
                continue;
            }
            
            var p1 = s.Substring(0, s.Length/2);
            var p2 = s.Substring(s.Length/2);

            if (p1 == p2)
            {
                Console.WriteLine($"INVALID PAIR: {s} is made up of {p1} and {p2}");
                sum += i;
            }
        }

        return sum;
    }
    
    public Int64 CheckPair2(Tuple<Int64, Int64> pair)
    {
        Int64 sum = 0;
        
        for (Int64 id = pair.Item1; id <= pair.Item2; id++)
        {
            var s =  id.ToString();
            
            for (int searchLength = 1; searchLength <= s.Length/2; searchLength++)
            {
                if (s.Length%searchLength != 0)
                {
                    continue;
                }

                var valid = true;
                var firstSet = s.Substring(0, searchLength);
                
                for (int repeatPosition = 1; repeatPosition < s.Length/searchLength; repeatPosition++)
                {
                    var checkSet = s.Substring(searchLength*repeatPosition, searchLength);

                    if (firstSet != checkSet)
                    {
                        valid = false;
                        break;
                    }
                }
                
                if (valid)
                {
                    Console.WriteLine($"INVALID ID: {s} is made up of {firstSet} {s.Length/searchLength} times");
                    sum += id;
                    break;
                }
                
            }
        }

        return sum;
    }
}

[-] janAkali@lemmy.sdf.org 3 points 2 months ago* (last edited 2 months ago)

Nim

Easy one today. Part 2 is pretty forgiving on performance, so regex bruteforce was only a couple seconds . But eventually I've cleaned it up and did a solution that runs in ~340 ms.

type
  AOCSolution[T,U] = tuple[part1: T, part2: U]

proc isRepeating(str:string, sectorLength=1): bool =
  if str.len mod sectorLength != 0: return false
  for i in countUp(0, str.len - sectorLength, sectorLength):
    if str.toOpenArray(i, i+sectorLength-1) != str.toOpenArray(0, sectorLength-1):
      return false
  true

proc solve(input: string): AOCSolution[int, int] =
  let ranges = input.split(',').mapIt:
    let parts = it.split('-')
    (parseInt parts[0], parseInt parts[1])

  for (a, b) in ranges:
    for num in a .. b:
      if num < 10: continue
      let strnum = $num
      let half = strnum.len div 2

      for i in countDown(half, 1):
        if strnum.isRepeating(i):
          if i == half and strnum.len mod 2 == 0:
            result.part1 += num
          result.part2 += num
          break

Full solution at Codeberg: solution.nim

[-] CameronDev@programming.dev 2 points 2 months ago

At least for rust, regex perf was basically the same for pt1 and pt2. So very forgiving.

[-] mykl@lemmy.world 3 points 2 months ago

Uiua

Considerably easier than Day 1 for me. Uiua does have regex support, but no back references, so this is my poor man's version.

"11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124"
⊜(⊜⋕⊸≠@-)⊸≠@,
R    ← +⊙⇡⟜-⊙+₁°⊟
Dup  ← ¬˜∊0⦷⊸↙⌈÷2⊸⧻°⋕
Dup₂ ← ⨬(/↥≡⌟(¬˜∊0⦷⊸↙)↘1⇡+1⌈÷2⊸⧻|0)<2÷∩⧻⊸⊸◴°⋕
⊃≡(/+▽⊸≡Dup R)≡(/+▽⊸≡Dup₂ R)
∩/+

You can run the code on Uiua Pad, btw.

[-] mykl@lemmy.world 2 points 2 months ago

And here's a much improved version based on my understanding of another solution on the Discord.

[-] eco_game@discuss.tchncs.de 2 points 2 months ago

Kotlin

got up early for this one, sadly it took me over 30 minutes to realize, that my code at the time was also considering a single digit valid .-.

also still pretty scuffed, but hey it works:

Solution

class Day02 : Puzzle {

    val ids = mutableSetOf<String>()

    override fun readFile() {
        val input = readInputFromFile("src/main/resources/a2025/day02.txt")
        ids.addAll(
            input.replace("\n", "")
                .split(",")
                .map { it.split("-") }
                .map(this::buildList)
                .flatten()
        )
    }

    private fun buildList(rangeList: List<String>): List<String> {
        val start = rangeList[0].toLong()
        val end = rangeList[1].toLong()
        val ids = mutableListOf<String>()
        for (i in start..end) {
            ids.add(i.toString())
        }
        return ids
    }

    override fun solvePartOne(): String {
        return ids.filter(this::idNotValid)
            .sumOf(String::toLong).toString()
    }

    override fun solvePartTwo(): String {
        return ids.filter { idNotValid(it, true) }
            .sumOf(String::toLong).toString()
    }

    private fun idNotValid(id: String, multipleSplits: Boolean = false): Boolean {
        val length = id.length

        // try all splits
        var split = 2
        while (split <= length) {
            if (length % split == 0) {
                val splits = mutableListOf<String>()
                var beg = 0
                var end = length / split
                val step = end
                for (i in 0..<split) {
                    splits.add(id.substring(beg, end))
                    beg += step
                    end += step
                }
                if (splits.all { it == splits[0] }) return true
            }
            if (multipleSplits) {
                split++
            } else {
                break
            }
        }
        return false
    }
}

full code on Codeberg

[-] chunkystyles@sopuli.xyz 2 points 2 months ago* (last edited 2 months ago)

My part 2 Kotlin solution:

val factors = intArrayOf(2, 3, 5, 7)

fun main() {
    var total = 0L
    val input = getInput(2)
    val ranges = parseInput1(input)
    ranges.forEach {
        val start = it.first.toLong()
        val end = it.second.toLong()
        for (id in start..end) {
            val idString = id.toString()
            if (isIdInvalid2(idString)) {
                total += id
            }
        }
    }
    println(total)
}

fun parseInput1(input: String): List<Pair<String, String>> {
    return input.split(",")
        .filter { it.isNotBlank() }
        .map {
            val secondSplit = it.split("-")
            secondSplit.first().trim() to secondSplit.last().trim()
        }
}

fun isIdInvalid2(id: String): Boolean {
    for (factor in factors) {
        if (id.length % factor == 0) {
            val gap = id.length / factor
            var areAllMatching = true
            for (i in 0..<gap) {
                val end = (factor - 1) * gap + i
                if (!areCharactersTheSame(id, gap, i, end)) {
                    areAllMatching = false
                    break
                }
            }
            if (areAllMatching) {
                return true
            }
        }
    }
    return false
}

fun areCharactersTheSame(string: String, gap: Int, start: Int, end: Int): Boolean {
    val character = string[start]
    for (i in start + gap..end step gap) {
        if (character != string[i]) {
            return false
        }
    }
    return true
}

I didn't look closely enough at the input to know how big an entire list of IDs would be huge or not. But I assumed it was. So instead I just did ranges as Pairs.

I also only considered the prime factors up to 7, because there weren't any IDs that needed any higher.

Edit: I also worried that a brute force solution might be slow, but being day 2, I might have been a little too worried about that. The main algorithm ran in 68ms.

[-] reboot6675@sopuli.xyz 2 points 2 months ago

First I tried to to part 2 with a very poor regex strategy and the performance was abysmal. Switched to plain substrings and boom, instant result.

Golang

func part1() {
	ranges := readInput()
	invalidSum := 0

	for _, r := range ranges {
		parts := strings.Split(r, "-")
		start, _ := strconv.Atoi(parts[0])
		end, _ := strconv.Atoi(parts[1])

		for num := start; num <= end; num++ {
			current := strconv.Itoa(num)
			n := len(current)
			if n%2 != 0 {
				continue
			}
			left := current[:n/2]
			right := current[n/2:]
			if left == right {
				invalidSum += num
			}
		}
	}

	fmt.Println(invalidSum)
}

func part2() {
	ranges := readInput()
	invalidSum := 0

	for _, r := range ranges {
		parts := strings.Split(r, "-")
		start, _ := strconv.Atoi(parts[0])
		end, _ := strconv.Atoi(parts[1])

		for num := start; num <= end; num++ {
			current := strconv.Itoa(num)
			n := len(current)

			for index := 1; index <= n/2; index++ {
				if n%index != 0 {
					continue
				}

				left := 0
				right := index
				prefix := current[left:right]
				isRepeated := true
				for left < n && right < n {
					left = right
					right = right + index
					next := current[left:right]
					if next != prefix {
						isRepeated = false
						break
					}
				}

				if isRepeated {
					invalidSum += num
					break
				}
			}
		}
	}

	fmt.Println(invalidSum)
}
[-] eta@feddit.org 2 points 2 months ago

Elixir

This one took way to long to do because I got hung up on what turned out to just be forgetting to rename functions when copying code from part 1 to part 2.

I did part 1 in language but turned to regex for part 2.

#!/usr/bin/elixir

defmodule GiftShopDatabase do

def getRanges(filename) do
	{:ok, content} = File.read(filename)
	input = String.trim(content)
	ranges = []
	currLowerLim = ""
	currNum = ""
	parseRanges(input, ranges, currLowerLim, currNum)
end

defguardp isDigit(c) when c in ?0..?9

defp parseRanges(input, ranges, currLowerLim, currNum) do
	case input do
		<<>> -> Enum.reverse([{currLowerLim,IO.iodata_to_binary(currNum)} | ranges])
		<<",", rest::binary>> ->
			#newUpperLim = IO.iodata_to_binary(currNum) |> String.to_integer
			newUpperLim = IO.iodata_to_binary(currNum)
			parseRanges(rest, [{currLowerLim,newUpperLim} | ranges], currLowerLim, "")
		<<"-", rest::binary>> ->
			#newLowerLim = IO.iodata_to_binary(currNum) |> String.to_integer
			newLowerLim = IO.iodata_to_binary(currNum)
			parseRanges(rest, ranges, newLowerLim, "")
		<<char::utf8, rest::binary>> when isDigit(char) ->
			parseRanges(rest, ranges, currLowerLim, [currNum | <<char>>])
		other -> raise "[ ERROR ] unkown input:#{inspect(other)}"
	end
end

def getInvIDSum(ranges, invIDSum) do
	case ranges do
		[] -> invIDSumString.to_integer(elem(range,0)), invIDSum))
		[range | rest] -> getInvIDSum(rest, invIDSum+checkRange(range, String.to_integer(elem(range,0)), 0))
		other -> raise "[ ERROR ] invalid ranges given:#{inspect(other)}"
	end
end

defp checkRange(range, currID, invIDSum) do
	strUpperLim = String.to_integer(elem(range,1))
	unevenDigitCount = rem(String.length(Integer.to_string(currID)),2)
	case currID do
		_ when currID > strUpperLim -> invIDSum
		_ when unevenDigitCount == 1 -> checkRange(range, currID+1, invIDSum)
		_ -> checkRange(range, currID+1, invIDSum + checkID(currID))
	end
end

defp checkID(currID) do
	pow = :math.pow(10,String.length(Integer.to_string(currID))/2)
	front = floor(currID/pow)
	back = currID - floor(front*pow)
	if front == back, do: currID, else: 0
end

def getInvIDSumPart2(ranges, invIDSum) do
	case ranges do
		[] -> invIDSum
		#[range | rest] -> getInvIDSumPart2(rest, invIDSum+checkRangePart2(range, elem(range,0), invIDSum))
		[range | rest] -> getInvIDSumPart2(rest, invIDSum+checkRangePart2(range, elem(range,0), 0))
		other -> raise "[ ERROR ] invalid ranges given:#{inspect(other)}"
	end
end

def checkRangePart2(range, currID, invIDSum) do
	numID = String.to_integer(currID)
	numUpperLim = String.to_integer(elem(range,1))
	case currID do
		_ when numID > numUpperLim -> invIDSum
		_ when numID < 10 -> checkRangePart2(range, Integer.to_string(String.to_integer(currID)+1), invIDSum)
		_ -> checkRangePart2(range, Integer.to_string(String.to_integer(currID)+1), invIDSum + checkIDPart2(currID,1,floor(String.length(currID)/2)))
	end
end

def checkIDPart2(currID, currLen, maxLen) do
	regex = "(#{String.slice(currID,0,currLen)})+"
	regexComp = Regex.compile(regex)
	res = Regex.run(elem(regexComp,1), currID)
	case res do
		[_, _] when currLen > maxLen or maxLen == 0 -> 0
		[longestMatch, _] when longestMatch == currID -> String.to_integer(currID)
		[_, _] -> checkIDPart2(currID, currLen+1, maxLen)
		_ -> 0
	end
end

end #module


IO.puts "### Part 1 ###"
ranges = GiftShopDatabase.getRanges("input/day02Input.txt")
IO.puts "[ INFO ] ranges:#{inspect(ranges)}"
invIDSum = GiftShopDatabase.getInvIDSum(ranges, 0)
IO.puts "[ INFO ] invalid ID sum:#{invIDSum}"

IO.puts "### Part 2 ###"
ranges = GiftShopDatabase.getRanges("input/day02Input.txt")
IO.puts "[ INFO ] ranges:#{inspect(ranges)}"
invIDSum = GiftShopDatabase.getInvIDSumPart2(ranges, 0)
IO.puts "[ INFO ] invalid ID sum:#{invIDSum}"
[-] Jayjader@jlai.lu 2 points 1 month ago

(Browser-based) Javascript

This year I'm tired of dealing with reading from files and setting up IDEs, so I'm attempting to solve each day directly in my web browser's console: after opening my problem input in a new tab I can do mySolutionFunction(document.body.textContent) in that tab's console. Thankfully the browser I use (ff) has a mode that lets me write several lines and then run them, otherwise this would not be simpler. Unfortunately, this means I lost my code for day1 when I closed the tab a bit too quickly.

I didn't want to use regex for today; you need backreferences and those are impossible to optimize if they blow up (computationally speaking). I'm not so sure my solution for part 2 actually does run in more linear time than a regex with a single backreference does...

function part1(input) {
  let sumOfValidIds = 0;
  for (const rangeDef of input.split(',')) {
    const [start, stop] = rangeDef.split('-').map(s => Number.parseInt(s, 10));
    const rangeLength = stop - start + 1;
    for (let id = start; id <= stop; id++) {
      const idLength = id.toString().length;
      if (idLength % 2 === 1) {
        continue
      }
      const halfLength = idLength / 2;
      const topHalf = Math.floor(id / Math.pow(10, halfLength));
      const bottomHalf = id - topHalf * Math.pow(10, halfLength);
      if (topHalf === bottomHalf) {
        sumOfValidIds += id;
      }
    }
  }
  return sumOfValidIds;
}
part1(document.body.textContent);

function extendsPattern(containerString, pattern) {
  let container = containerString;
  while (container.length > pattern.length) {
    if (!container.startsWith(pattern)) {
      return false;
    }
    container = container.slice(pattern.length)
  }
  return pattern.startsWith(container);
}
function findContainedPatterns(containerString) {
  const patterns = [];
  for (let i = 0; i < containerString.length; i++) {
    const upTillNow = containerString.substring(0, i+1);
    for (let j = 0; j < patterns.length; j++) {
      if (!extendsPattern(upTillNow, patterns[j])) {
        patterns.splice(j, 1);
        j--;
      }
    }
    patterns.push(upTillNow);
  }
  return patterns;
}
function part2(input) {
  let sumOfValidIds = 0;
  for (const rangeDef of input.split(',')) {
    const [start, stop] = rangeDef.split('-').map(s => Number.parseInt(s, 10));
    const rangeLength = stop - start + 1;
    for (let id = start; id <
      const idString = id.toString();
      const patterns = findContainedPatterns(idString);
      if (patterns.length > 1) {
        const shortestPatternCandidate = patterns[0];
        if (idString.length % shortestPatternCandidate.length === 0) {
          sumOfValidIds += id;
        }
      }
    }
  }
  return sumOfValidIds;
}
//part2(`11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124`)
part2(document.body.textContent);
[-] lwhjp@piefed.blahaj.zone 2 points 2 months ago

Haskell

Not much time for challenges right now sadly :/

import Data.Bifunctor  
import Data.IntSet qualified as IntSet  
import Data.List.Split  

repeats bound (from, to) = IntSet.elems $ IntSet.unions $ map go [2 .. bound l2]  
  where  
    l1 = length (show from)  
    l2 = length (show to)  
    go n =  
      let l = max 1 $ l1 `quot` n  
          start = if n > l1 then 10 ^ (l - 1) else read . take l $ show from  
       in IntSet.fromList  
            . takeWhile (<= to)  
            . dropWhile (< from)  
            . map (read . concat . replicate n . show)  
            $ enumFrom start  

main = do  
  input <-  
    map (bimap read (read . tail) . break (== '-')) . splitOn ","  
      <$> readFile "input02"  
  let go bound = sum $ concatMap (repeats bound) input  
  print $ go (const 2)  
  print $ go id  
[-] strlcpy@lemmy.sdf.org 2 points 2 months ago* (last edited 2 months ago)

DOS + BIOS boot (hybrid binary)

QEMU screenshot

Repo | day02.asm | .COM download

Got the x86-16 assembly implementation working at last! Here, 64-bit integer math, especially lots of divisions by power of 10, wasn't going to do so the code instead operates on fixed-width, zero-padded numeric strings. Lots of time lost to debugging control flow/logic mistakes this time. I need to make printf() a priority!

Short input so no space issues, sitting comfortably at 45K, well below the 64K COM limit! Sadly no time yet to add animations or anything cool.

[-] VegOwOtenks@lemmy.world 2 points 2 months ago

Futhark

I translated my Haskell solution to Futhark, basically. It runs abysmally faster.

The syntax highlighting is likely very off, because the closest language highlighter I could find was ocaml.

def fst 'a 'b ((a, _b): (a, b)): a = a
def snd 'a 'b ((_a, b): (a, b)): b = b
def (>>>) 'a 'b 'c (f: a -> b) (g: b -> c) (x: a): c = g (f x)
def (|) '^a 'b (f: a -> b) (x: a): b = f x -- $ is not allowed
def even (x: i64): bool = x % 2 == 0

def digitCount (x: i64): i64
  = snd | 
      loop (i, len) = (x, 0)
      while i != 0
      do (i / 10, len + 1)

def digitAt (n: i64) (i: i64): i64 = (n / 10 ** i) % 10

def keepTrue (p: i64 -> bool) (x: i64): i64
  = if p x
      then x
      else 0

def tup2RangeArray ((start, end): (i64, i64)): []i64
  = (start ... end)

def sumInvalidIds (p: i64 -> bool) (rangeTup: (i64, i64)): i64 
  = let range = tup2RangeArray rangeTup in
  reduce (+) 0 (map (keepTrue p) range)

def tup2FromArray 'a (as: [2]a): (a, a) = (as[0], as[1])

def impl (p: i64 -> bool) (ranges: [](i64, i64)): i64 
  = reduce (+) 0 (map (sumInvalidIds p) ranges)

def withValidRepeatOffsets (nDigits: i64) (f: i64 -> bool): bool
  = match nDigits
    case 2  -> map f >>> or | [1]
    case 3  -> map f >>> or | [1]
    case 4  -> map f >>> or | [1, 2]
    case 5  -> map f >>> or | [1]
    case 6  -> map f >>> or | [1, 2, 3]
    case 7  -> map f >>> or | [1]
    case 8  -> map f >>> or | [1, 2, 4]
    case 9  -> map f >>> or | [1, 3]
    case 10 -> map f >>> or | [1, 2, 5]
    case 11 -> map f >>> or | [1]
    case 12 -> map f >>> or | [1, 2, 3, 4, 6]
    case _ -> false

def isValid2 (x: i64): bool = 
  let len = digitCount x in
  let lookupDigit = digitAt x in
  withValidRepeatOffsets len | \ repeatOffset ->
    let repeatCount = len / repeatOffset in
    let digitIndices = (0..< repeatOffset) in
    let repeatIndices = (0..<repeatCount) in
    and | 
      map (\ digitIndex -> 
        and |
          map (\ repeatIndex -> 
            let expectedDigit = lookupDigit digitIndex in
            let actualDigit   = lookupDigit | repeatIndex * repeatOffset + digitIndex in
            expectedDigit == actualDigit
          ) 
          repeatIndices
      ) digitIndices

def part2 : [](i64, i64) -> i64 = impl isValid2 

def isValid1 (x: i64): bool = 
  let len = digitCount x in
  let halfLength = len / 2 in
  let first = x / 10 ** halfLength in
  let second = x % 10 ** halfLength in
  even len && first == second

def part1 : [](i64, i64) -> i64 = impl isValid1 

def main (rangeArrays: [][2]i64) 
  = let rangeTuples = map tup2FromArray rangeArrays in
    (part1 rangeTuples, part2 rangeTuples)

Sed-Script to Transform input for Futhark

i [
s/\([0-9]\+\)-\([0-9]\+\)/\[\1, \2]/g
a ]

[-] Gobbel2000@programming.dev 1 points 2 months ago

Rust

View on github

I feared that this required some complicated maths to quickly figure out the next "invalid" number, but the total number of IDs to check was only about 2 million, so brute force it is.

use std::ops::RangeInclusive;

fn parse_input(input: &str) -> Vec<RangeInclusive<u64>> {
    input
        .trim()
        .split(',')
        .map(|r| {
            let (a, b) = r.split_once('-').unwrap();
            RangeInclusive::new(a.parse().unwrap(), b.parse().unwrap())
        })
        .collect()
}

fn part1(input: String) {
    let ranges = parse_input(&input);
    let mut sum = 0;
    for e in ranges.into_iter().flatten() {
        let width = e.ilog10() + 1;
        if width % 2 == 0 {
            let top = 10u64.pow(width / 2);
            if e / top == e % top {
                sum += e;
            }
        }
    }
    println!("{sum}");
}

fn part2(input: String) {
    let ranges = parse_input(&input);
    let mut sum = 0;
    'nums: for e in ranges.into_iter().flatten() {
        let width = e.ilog10() + 1;
        for rep in 2..=width {
            if width % rep == 0 {
                let top = 10u64.pow(width / rep);
                let mut a = e;
                let lowest = a % top;
                let mut invalid = true;
                while a > top {
                    a /= top;
                    if a % top != lowest {
                        invalid = false;
                        break;
                    }
                }
                if invalid {
                    sum += e;
                    // Don't check other numbers of repetitions
                    continue 'nums;
                }
            }
        }
    }
    println!("{sum}");
}

util::aoc_main!();
[-] Chais@sh.itjust.works 1 points 2 months ago

Python

I love it when I can just slap a problem with a regex and be done with it.

import re

from pathlib import Path
from typing import Generator

def parse_input(input: str) -> Generator[range, None, None]:
    for r in input.split(","):
        v = r.split("-")
        yield range(int(v[0]), int(v[1]) + 1)


def solve(input: str, pattern: str) -> int:
    exp = re.compile(pattern)
    return sum(
        map(
            lambda m: int(m.group()),
            filter(
                None,
                map(lambda s: exp.match(s), [n for r in parse_input(input) for n in map(str, r)])
            )
        )
    )


if __name__ == "__main__":
    input = Path("_2025/_2/input").read_text("utf-8")
    print(solve(input, r"^(\d+)\1$"))
    print(solve(input, r"^(\d+)\1+$"))
this post was submitted on 02 Dec 2025
26 points (100.0% liked)

Advent Of Code

1217 readers
1 users here now

An unofficial home for the advent of code community on programming.dev! Other challenges are also welcome!

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.

Everybody Codes is another collection of programming puzzles with seasonal events.

EC 2025

AoC 2025

Solution Threads

M T W T F S S
1 2 3 4 5 6 7
8 9 10 11 12

Visualisations Megathread

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 2 years ago
MODERATORS