[–] 2 points 11 months ago* (last edited 11 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.

  • source
  • [–] 1 point 11 months ago

    Haskell

    I think I could have avoided the minimumBy hack by doing another reverse and changing the indices.

    import Data.List
    import Data.Function
    import Control.Arrow
    
    parse = fmap (fmap (read . pure)) . lines
    
    solve n = sum . fmap (sum . zipWith (*) (iterate (*10) 1) . reverse . go n)
      where
        go :: Int -> [Int] -> [Int]
        go 0 l = pure $ maximum l
        go n l = mx : go (n-1) (drop idx l)
          where
            -- use minimumBy since if there are multiple least elements, we want the leftmost one.
            (idx, mx) = minimumBy (compare `on` (negate . snd)) . zip [1..] . take (length l - n) $ l
    
    main = getContents >>= print . (solve 1 &&& solve 11) . parse
    
  • source
  • [–] 2 points 11 months ago (4 children)

    Today was interesting. My first thought was that part 2 would be a lot more complex at first glance. But I realized that my solution for part 1 worked almost out of the box for part 2.

    I was also pleased to see that the algorithm ran in 1ms, which was a good deal faster than just parsing the input.

    fun main() {
        val input = getInput(3)
        val banks = parseInput(input)
        var total = 0L
        banks.forEach { bank ->
            var location = 0
            var joltage = 0L
            for (power in 11 downTo 0) {
                val multiplier = 10.toDouble().pow(power).toLong()
                val batteryLocation = findBattery(bank, location, bank.size - power - 1)
                val battery = bank[batteryLocation]
                location = batteryLocation + 1
                joltage += battery.toLong() * multiplier
            }
            total += joltage
        }
        println(total)
    }
    
    fun parseInput(input: String): List<List<Int>> = input
        .split("\n")
        .filter { it.isNotBlank() }
        .map { it.toCharArray() }
        .map { it.map { digit -> digit.digitToInt() } }
    
    fun findBattery(bank: List<Int>, start: Int, end: Int): Int {
        var max = 0
        var location = 0
        for (i in start..end) {
            val battery = bank[i]
            if (battery > max) {
                max = battery
                location = i
                if (battery == 9) {
                    break
                }
            }
        }
        return location
    }
    
  • source
  • parent
  • context
  • [–] [S] 1 point 11 months ago* (last edited 11 months ago)
       fn calc_joltage(
            values: &[u32],
            count: usize,
            cache: &mut HashMap<(usize, usize), usize>,
        ) -> usize {
            if let Some(result) = cache.get(&(values.len(), count)) {
                return *result;
            }
            if count == 0 {
                return 0;
            }
            let mut highest = 0;
            let mut highest_base = 0;
            for (i, value) in values[0..values.len() - count + 1].iter().enumerate() {
                if *value < highest_base {
                    continue;
                }
                let base_joltage = (*value as usize) * 10_usize.pow(count as u32 - 1);
                let joltage = base_joltage + calc_joltage(&values[i + 1..], count - 1, cache);
                if joltage > highest {
                    highest = joltage;
                    highest_base = *value;
                }
            }
            cache.insert((values.len(), count), highest);
            highest
        }
    
        #[test]
        fn test_y2025_day3_part2() {
            let input = std::fs::read_to_string("input/2025/day_3.txt").unwrap();
            let mut total = 0;
            input.lines().for_each(|line| {
                let banks = line
                    .chars()
                    .map(|c| c.to_digit(10).unwrap())
                    .collect::<Vec<u32>>();
                let joltage = calc_joltage(&banks, 12, &mut HashMap::new());
                total += joltage;
            });
            println!("Total: {}", total);
        }
    

    Seems i missed the faster solutions, but i did get this down to a respectable 400ms. edit: 400ms was not respectable, mykl's method took 1ms. Mine was close though, with a bit more brain and optimisation I got there.

    And the bot worked all by itself!

  • source
  • [–] 1 point 11 months ago

    Rust

    Seeing some of the other solutions in this thread, there are definitely simpler (and probably still faster) solutions possible, but I first sorted the bank by the highest batteries (keeping the index information) and then used a recursive greedy algorithm to find the largest battery that still follows the index order.

    View on github

    fn part1(input: String) {
        let mut sum = 0;
        'banks: for l in input.lines() {
            let mut sorted: Vec<(usize, u32)> = l
                .chars()
                .map(|c| c.to_digit(10).unwrap())
                .enumerate()
                .collect();
            sorted.sort_by(|(_, a), (_, b)| a.cmp(b).reverse());
            for (idx, first) in &sorted {
                for (id2, second) in &sorted {
                    if id2 > idx {
                        sum += first * 10 + second;
                        continue 'banks;
                    }
                }
            }
        }
        println!("{sum}");
    }
    
    // Recursive implementation of greedy algorithm.
    // Returns Vec of length 12 if a result was found, guaranteed to be optimal.
    // If there is no solution with the input, a shorter Vec is returned.
    fn recursive(bank: &[(usize, u32)], mut cur: Vec<(usize, u32)>) -> Vec<(usize, u32)> {
        let pos = cur.last().unwrap().0;
        for &(idx, e) in bank.iter().filter(|(idx, _)| *idx > pos) {
            cur.push((idx, e));
            if cur.len() == 12 {
                // Recursion anchor: We have filled all 12 spots and therefore found
                // the best solution
                return cur;
            }
            // Recurse
            cur = recursive(bank, cur);
            if cur.len() == 12 {
                // Result found
                return cur;
            }
            // Nothing found, try next in this position
            cur.pop();
        }
        // Unsuccessful search with given inputs
        cur
    }
    
    fn part2(input: String) {
        let mut sum = 0;
        'banks: for l in input.lines() {
            let mut sorted: Vec<(usize, u32)> = l
                .chars()
                .map(|c| c.to_digit(10).unwrap())
                .enumerate()
                .collect();
            sorted.sort_by(|(_, a), (_, b)| a.cmp(b).reverse());
            let mut cur: Vec<(usize, u32)> = Vec::with_capacity(12);
            for &(idx, first) in &sorted {
                cur.push((idx, first));
                cur = recursive(&sorted, cur);
                if cur.len() == 12 {
                    let num = cur.iter().fold(0u64, |acc, e| acc * 10 + e.1 as u64);
                    sum += num;
                    continue 'banks;
                }
                cur.pop();
            }
        }
        println!("{sum}");
    }
    
    util::aoc_main!();
    
  • source
  • [–] 3 points 11 months ago

    Haskell

    Usually, I get up for AoC, way earlier than I normally would. But today I had to get up at exactly AoC time. I ended up postponing the puzzles until now:

    Complete, Dependency-Free and Runnable ProgramIt reads from stdin and writes the both solutions on a separate line to stdout.

    {-# OPTIONS_GHC -Wall #-}
    import qualified Data.Text.IO as TextIO
    import Control.Monad ((<$!>))
    import qualified Data.Array.Unboxed as Array
    import qualified Data.Text as Text
    import qualified Data.Char as Char
    import Data.Array.Unboxed (UArray)
    import qualified Data.Foldable as Foldable
    import Control.Arrow ((&&&))
    import qualified Data.List as List
    
    parse :: Text.Text -> UArray (Int, Int) Int
    parse t = let
        banks = init $ Text.lines t
        bankSize = maybe 0 pred $ Text.findIndex (== '\n') t
        bankCount = Text.count "\n" t - 2
      in Array.listArray ((0, 0), (bankCount, bankSize)) $ List.concatMap (fmap Char.digitToInt . Text.unpack) banks
    
    rowsOf :: UArray (Int, Int) Int -> Int
    rowsOf = fst . snd . Array.bounds
    
    colsOf :: UArray (Int, Int) Int -> Int
    colsOf = snd . snd . Array.bounds
    
    main :: IO ()
    main = do
      batteryBanks <- parse <$!> TextIO.getContents
      print $ part1 batteryBanks
      print $ part2 batteryBanks
    
    part1 :: UArray (Int, Int) Int -> Int
    part1 batteryBanks = Foldable.sum $ pickBatteries 2 batteryBanks <$> [0.. rowsOf batteryBanks]
    
    part2 :: UArray (Int, Int) Int -> Int
    part2 banks = Foldable.sum $ pickBatteries 12 banks <$> [0.. rowsOf banks]
    
    pickBatteries :: Int -> UArray (Int, Int) Int -> Int -> Int
    pickBatteries batteryCount banks row = let
        width = colsOf banks
        getBattery col = banks Array.! (row, col)
    
        go acc 0 _      = acc
        go acc n offset = let
            effectiveEnd = width - pred n
            availableIndices = [offset .. effectiveEnd]
            batteryWithIndices = (id &&& getBattery) <$> availableIndices
            (offset', selectedBattery) = maximumOn snd batteryWithIndices
          in go (acc * 10 + selectedBattery) (pred n) (succ offset')
      in go 0 batteryCount 0
    
    maximumOn :: (Foldable t, Ord b) => (a -> b) -> t a -> a
    maximumOn f collection = case Foldable.toList collection of
      [] -> error "maximumOn: empty foldable"
      (x:xs) -> List.foldl selectMax x xs
      where
        selectMax a b = if f a < f b then b else a
    

  • source
  • [–] 4 points 11 months ago*

    C

    Surprise, O(n^12) solutions don't scale! But then it was delightful when the realization hit that the solution is actually very simple to implement - just keep removing the first digit that is followed by a higher one.

    static uint64_t joltage(char *s, int len, int target) {
    	int i;
    
    	for (; len > target; len--) {
    		for (i=0; i<len-1 && s[i] >= s[i+1]; i++) ;
    		memmove(s+i, s+i+1, len-i);
    	}
    
    	return strtoul(s, NULL, 10);
    }
    
    int main() {
    	char buf[1024];
    	uint64_t p1=0,p2=0;
    	int len;
    
    	while (fgets(buf, sizeof(buf), stdin)) {
    		for (len=0; isdigit(buf[len]); len++) ;
    		buf[len] = '\0';
    		p2 += joltage(buf, len, 12);
    		p1 += joltage(buf, 12, 2);
    	}
    
    	printf("03: %lu %lu\n", p1, p2);
    }
    

    Repo link

    I'm still having to finish yesterday's x86-16 assembly implementation, for which I had to write some support code to deal with large numbers as strings. That will come in useful today, too!

  • source
  • [–] 4 points 11 months ago (5 children)

    Kotlin

    First day this year where I am very happy with my solution. Just super simple, recursive string building.

    Solution

    class Day03 : Puzzle {
    
        val banks = mutableListOf<String>()
    
        override fun readFile() {
            val input = readInputFromFile("src/main/resources/a2025/day03.txt")
            banks.addAll(input.lines().filter(String::isNotBlank))
        }
    
        override fun solvePartOne(): String {
            val sum = banks.map { buildNumber(it, 2) }.sumOf { it.toLong() }
            return sum.toString()
        }
    
        override fun solvePartTwo(): String {
            val sum = banks.map { buildNumber(it, 12) }.sumOf { it.toLong() }
            return sum.toString()
        }
    
        private fun buildNumber(bank: String, remainingDigits: Int): String {
            if (remainingDigits <= 0) return ""
            val current = bank.dropLast(remainingDigits - 1)
            val max = current.max()
            return max + buildNumber(bank.split(max, limit = 2)[1], remainingDigits - 1)
        }
    }
    

    full code on Codeberg

  • source
  • [–] 3 points 11 months ago
    import numpy as np
    
    def parse_input(file_path):
    
      with file_path.open("r") as fp:
        banks = map(str.strip, fp.readlines())
    
      return map(lambda x: list(map(int, list(x))), banks)
    
    def max_jolt(bank, length):
    
      if length==1:
        return max(bank)
    
      amax = np.argmax(bank[:-(length-1)])
    
      return 10**(length-1)*bank[amax] + max_jolt(bank[amax+1:], length-1)
    
    def solve_problem(file_name, length):
    
      banks = parse_input(Path(cwd, file_name))
      sumj = 0
    
      for bank in banks:
        sumj += max_jolt(bank, length)
    
      return sumj
    
  • source
  • [–] 5 points 11 months ago*

    Rust

    My first version worked with numbers, but since I was still sick of yesterday's pow(10) calls, I changed it to use ascii for the second half - the nice thing is that means it can work with hex input with no modification!

    Clippy was complaining about "needless_range_loops", but it's way more readable this way.

    struct PowerSource(Vec<Bank>);
    
    impl FromStr for PowerSource {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self> {
            let banks = s.lines().map(|l| Bank(l.to_owned())).collect();
            Ok(Self(banks))
        }
    }
    
    impl PowerSource {
        fn max_joltage(&self, num_digits: usize) -> usize {
            self.0.iter().map(|b| b.max_joltage(num_digits)).sum()
        }
    }
    
    struct Bank(String);
    
    impl Bank {
        fn max_joltage(&self, num_digits: usize) -> usize {
            let batts = self.0.as_bytes();
    
            let mut digits = vec![b'0'; num_digits];
            let mut start = 0;
            for d in 0..num_digits {
                for i in start..=batts.len() - num_digits + d {
                    if batts[i] > digits[d] {
                        digits[d] = batts[i];
                        start = i + 1;
                    }
                }
            }
    
            usize::from_str(str::from_utf8(&digits).unwrap()).unwrap()
        }
    }
    
  • source
  • [–] 3 points 11 months ago

    Go

    I usually write a little helper library to bootstrap the input reading process and sometimes even the downloading of the input file. So here it is:

    utils.go

    package utils
    
    import (
    	"bufio"
    	"os"
    	"strings"
    )
    
    type Input interface {
    	GetLineChannel() (chan string, error)
    }
    
    type FilePath string
    type InputText string
    
    func (path FilePath) GetLineChannel() (chan string, error) {
    	file, err := os.Open(string(path))
    	if err != nil {
    		return nil, err
    	}
    
    	scanner := bufio.NewScanner(file)
    
    	ch := make(chan string, 1024)
    	go (func() {
    		defer file.Close()
    
    		for scanner.Scan() {
    			ch <- scanner.Text()
    		}
    
    		close(ch)
    	})()
    
    	return ch, nil
    }
    
    func (inputText InputText) GetLineChannel() (chan string, error) {
    	lines := strings.Split(string(inputText), "\n")
    	ch := make(chan string, len(lines))
    
    	go (func() {
    		for _, line := range lines {
    			ch <- line
    		}
    
    		close(ch)
    	})()
    
    	return ch, nil
    }
    

    And here comes the solution to day 3:

    package main
    
    import (
    	"aoc/utils"
    	"errors"
    	"fmt"
    	"math"
    )
    
    const inputText = `987654321111111
    811111111111119
    234234234234278
    818181911112111`
    
    type bank []int
    
    func (bk bank) largestNDigitJoltage(n int) int {
    	digits := make([]int, n)
    	count := 0
    	idx := 0
    
    	lenbk := len(bk)
    
    	for range n {
    		for i := idx; i < lenbk-(n-count-1); i++ {
    			val := bk[i]
    			if val > digits[count] {
    				idx = i + 1
    				digits[count] = val
    			}
    		}
    		count++
    	}
    
    	sum := 0
    	for index, val := range digits {
    		sum += val * int(math.Pow10(n-index-1))
    	}
    
    	return sum
    }
    
    func readBank(line string) (bank, error) {
    	runes := []rune(line)
    	bk := make(bank, len(runes))
    	for idx, c := range runes {
    		switch c {
    		case '0':
    			bk[idx] = 0
    		case '1':
    			bk[idx] = 1
    		case '2':
    			bk[idx] = 2
    		case '3':
    			bk[idx] = 3
    		case '4':
    			bk[idx] = 4
    		case '5':
    			bk[idx] = 5
    		case '6':
    			bk[idx] = 6
    		case '7':
    			bk[idx] = 7
    		case '8':
    			bk[idx] = 8
    		case '9':
    			bk[idx] = 9
    		default:
    			msg := fmt.Sprintf("not a number: %c", c)
    			return bank{}, errors.New(msg)
    		}
    	}
    	return bk, nil
    }
    
    func getBankChannel(input chan string) chan bank {
    	ch := make(chan bank, cap(input))
    
    	go func() {
    		for line := range input {
    			bank, err := readBank(line)
    			if err != nil {
    				fmt.Errorf("error reading line %v: %v\n", line, err)
    				close(ch)
    				return
    			}
    			ch <- bank
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    func stepOne(input chan string) (int, error) {
    	ch := getBankChannel(input)
    	sum := 0
    	for bank := range ch {
    		sum += bank.largestNDigitJoltage(2)
    	}
    
    	return sum, nil
    }
    
    func stepTwo(input chan string) (int, error) {
    	ch := getBankChannel(input)
    	sum := 0
    	for bank := range ch {
    		sum += bank.largestNDigitJoltage(12)
    	}
    
    	return sum, nil
    }
    
    func main() {
    	// input2 := utils.InputText(inputText)
    	input := utils.FilePath("day03.txt")
    
    	ch, err := input.GetLineChannel()
    	if err != nil {
    		fmt.Errorf("step one error: %v\n", err)
    		return
    	}
    
    	var one int
    	one, err = stepOne(ch)
    	if err != nil {
    		fmt.Errorf("step one error: %v\n", err)
    		return
    	}
    	fmt.Printf("Step one result: %v\n", one)
    
    	// input2 := utils.InputText(inputText)
    	input2 := utils.FilePath("day03.txt")
    
    	ch, err = input2.GetLineChannel()
    	if err != nil {
    		fmt.Errorf("step two error: %v\n", err)
    		return
    	}
    
    	var two int
    	two, err = stepTwo(ch)
    	if err != nil {
    		fmt.Errorf("step two error: %v\n", err)
    		return
    	}
    	fmt.Printf("Step two result: %v\n", two)
    }
    

    While I am quite an adaptable person and I learn to program quickly in about all the languages I've tried, I'm still at the beginning of my journey with Go. It does feel like the language is trying to resist me being clever at every corner. I understand the reasons, why not, but damn it does make the development a bit frustrating at times

  • source
  • [–] 6 points 11 months ago* (last edited 11 months ago) (2 children)

    Uiua

    You can run this in Uiua Pad

    "987654321111111\n811111111111119\n234234234234278\n818181911112111"
    βŠœβˆ˜βŠΈβ‰ @\n
    Jolt ← β₯(βŠ™βŠƒβ‹…βˆ˜βˆ˜+1⟜(βŠƒ(˜⊑|Λœβ†˜βŠ™+₁)βœβ†˜βŸœ(Λœβ¨‚βŠΈ/β†₯)))⟜(+1Β―)
    ∩/+βŠƒβ‰‘(β‹•Jolt2)≑(β‹•Jolt12)
    

    ::: spoiler Explanation You're always looking for the highest digit far enough from the end of the string that there's space to pick the remaining number of digits. So the algorithm is very straightforward: just temporarily exclude the last n-1 characters off the end of the string and look for the first instance of the largest digit in that substring, store that char and behead the string at that point. Repeat n times.

  • source
  • [–] 3 points 11 months ago* (last edited 11 months ago) (1 child)

    c

    #include "aoc.h"
    #include <stdio.h>
    #include <string.h>
    
    constexpr usize LINE_BUFSZ = (1 << 7);
    constexpr u64 TEN = 10;
    constexpr u64 TWELVE = 12;
    
    static void
    joltage(strc line, u64* total, usize on) {
      usize len = strlen(line);
      usize off = len - on;
      usize slen = 0;
      c8 stack[LINE_BUFSZ] = {};
      for (usize i = 0; i < len; i++) {
        while (slen > 0 && off > 0 && stack[slen - 1] < line[i]) {
          slen--;
          off--;
        }
        stack[slen++] = line[i];
      }
      u64 jltg = 0;
      for (usize i = 0; i < on; i++) {
        jltg = (jltg * TEN) + (u64)(stack[i] - '0');
      }
      *total += jltg;
    }
    
    static void
    solve(u64 on) {
      FILE* input = fopen("input", "r");
      c8 line[LINE_BUFSZ] = {};
      u64 total = 0;
      while (fgets(line, sizeof(line), input)) {
        line[strcspn(line, "\n")] = 0;
        joltage(line, &total, on);
      }
      fclose(input);
      printf("%lu\n", total);
    }
    
    i32
    main(void) {
      solve(2);
      solve(TWELVE);
    }
    
  • source
  • [–] 3 points 11 months ago

    Nim

    type
      AOCSolution[T,U] = tuple[part1: T, part2: U]
    
    proc maxJoltage(bank: string, n: int): int =
      var index = 0
      for leftover in countDown(n-1, 0):
        var best = bank[index]
        for batteryInd in index+1 .. bank.high-leftover:
          let batt = bank[batteryInd]
          if batt > best: (best = batt; index = batteryInd)
          if best == '9': break # max for single battery
        result += (best.ord - '0'.ord) * 10^leftover
        inc index
    
    proc solve(input: string): AOCSolution[int, int] =
      for line in input.splitLines:
        result.part1 += line.maxJoltage 2
        result.part2 += line.maxJoltage 12
    

    Runtime: ~240 ΞΌs

    Day 3 was very straightforward, although I did wrestle a bit with the indexing.
    Honestly, I expected part 2 to require dynamic programming, but it turned out I only needed to tweak a few numbers in my part 1 code.

    Full solution at Codeberg: solution.nim

  • source
  • [–] 4 points 11 months ago (1 child)

    Haskell

    Yay, dynamic programming!

    import Data.Map qualified as Map  
    
    maxJolt :: Int -> [Char] -> Int  
    maxJolt r xs = read $ maximize r 0  
      where  
        n = length xs  
        maximize =  
          (curry . (Map.!) . Map.fromList . (zip <*> map (uncurry go)))  
            [(k, o) | k <- [1 .. r], o <- [r - k .. n - k]]  
        go k o =  
          maximum $ do  
            (x, o') <- drop o $ zip xs [1 .. n - (k - 1)]  
            return . (x :) $ if k == 1 then [] else maximize (k - 1) o'  
    
    main = do  
      input <- lines <$> readFile "input03"  
      mapM_ (print . sum . (`map` input) . maxJolt) [2, 12]  
    
  • source
  • [–] 4 points 11 months ago* (last edited 11 months ago)

    Python

    This was the easier one for me out of the first 3 days. Cleaned up my solution before posting for better readability:

    # get joltage of picked batteries
    def get_joltage(batteries_picked: list[int]):
        bank_joltage = 0
        for batt in batteries_picked:
            bank_joltage = bank_joltage * 10 + batt
        return bank_joltage
    
    # get maximum joltage of a bank
    def get_bank_joltage(bank: str, pick_limit = 2) -> int:
        # pick first <pick_limit> batteries
        batteries_picked = [int(bank[i]) for i in range(pick_limit)]
        max_joltage = get_joltage(batteries_picked)
    
        # iterate over remaining batteries
        for i in range(pick_limit, len(bank)):
            batt = int(bank[i])        
            # we add batt for selection consideration
            batteries_picked.append(batt)
            # If all batteries are in descending order and batt is the lowest, 
            #   we will eventually discard batt
            to_discard = pick_limit
    
            # However if not, we discard the leftmost MSB battery which has lower joltage than its successor
            #   and shift all batteries left with batt added at the end.
            # This guarantees that we keep the maximum lexicographical order of picked batteries
            #   regardless of batt's value.
            for i in range(pick_limit):
                if batteries_picked[i] < batteries_picked[i+1]:
                    to_discard = i
                    break
            batteries_picked.pop(to_discard)
    
            # update max_joltage, it may have increased
            max_joltage = max(max_joltage, get_joltage(batteries_picked))
    
        return max_joltage
    
    # part 1 asserts
    assert get_bank_joltage("987654321111111", pick_limit=2) == 98
    assert get_bank_joltage("811111111111119", pick_limit=2) == 89
    assert get_bank_joltage("234234234234278", pick_limit=2) == 78
    assert get_bank_joltage("818181911112111", pick_limit=2) == 92
    
    # part 2 asserts
    assert get_bank_joltage("987654321111111", pick_limit=12) == 987654321111
    assert get_bank_joltage("811111111111119", pick_limit=12) == 811111111119
    assert get_bank_joltage("234234234234278", pick_limit=12) == 434234234278
    assert get_bank_joltage("818181911112111", pick_limit=12) == 888911112111
    
    # get total joltage of a set of banks
    def solve(data: str, pick_limit = 2):
        total_joltage = 0
        for bank in data.splitlines():
            total_joltage += get_bank_joltage(bank, pick_limit)
        return total_joltage
    
    # asserts for sample data
    sample = """987654321111111
    811111111111119
    234234234234278
    818181911112111"""
    assert solve(sample, pick_limit=2) == 357               # part 1
    assert solve(sample, pick_limit=12) == 3121910778619    # part 2
    
    
  • source
  • [–] 2 points 11 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  
    
  • source
  • [–] 2 points 11 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!();
    
  • source
  • [–] 5 points 11 months ago* (last edited 11 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

  • source
  • [–] 2 points 11 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.

  • source
  • parent
  • context
  • [–] 2 points 11 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)
    }
    
  • source
  • view more: β€Ή prev next β€Ί