Day 3: Lobby

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

  • GiantTree@feddit.org
    link
    fedilink
    English
    arrow-up
    2
    ·
    3 days ago

    Kotlin

    I’m late to the party but I hope some of you will still be inspired by my submisison. This is an iterative solution. I began with a recursive solution that worked but I noticed that it should really be rewritten in an iterative way. The solution is also pointlessly optimized, to some degree, but that’s just what I like to do. 🙂

    The logic follows a simple pattern of knowing which window of the battery bank to search in. Given the amount of batteries that remain to be turned on, if you were to turn on the last battery in the window, you’d need to turn on all the remaining batteries. So the window begins at one position past the prior battery and ends at the last battery you actually can choose to turn on. Once that has been turned on, all remaining ones need to be turned on. The window can only actually shrink to at least one position.

    Code inside
    class Day03 : AOCSolution {
        override val year = 2025
        override val day = 3
    
        override fun part1(inputFile: String): String {
            return readResourceBinary(inputFile).lineSequence().sumOf { batteryBank ->
                findHighestJoltage(batteryBank, 2)
            }.toString()
        }
    
        override fun part2(inputFile: String): String {
            return readResourceBinary(inputFile).lineSequence().sumOf { batteryBank ->
                findHighestJoltage(batteryBank, 12)
            }.toString()
        }
    
        private fun findHighestJoltage(
            bank: EightBitString,
            batteries: Int,
        ): Long {
            val digitsArray = ByteArray(batteries) { -1 }
    
            var lastDigitIndex = 0
            repeat(batteries) { currentDigit ->
                val remainingDigits = batteries - currentDigit
                val lastIndex = bank.length - remainingDigits + 1
    
                val maxIndex = bank.indexOfMax(lastDigitIndex, lastIndex)
                lastDigitIndex = maxIndex + 1
                digitsArray[batteries - remainingDigits] = bank[maxIndex].toDigit()
            }
    
            return digitsArray.fold(0L) { acc, i -> acc * 10L + i }
        }
    
    
        private companion object {
            private fun ByteArray.lineSequence(): Sequence<EightBitString> {
                val buffer = EightBitString(this)
                var currentOffset = 0
                return generateSequence {
                    for (characterIndex in currentOffset until buffer.limit()) {
                        if (buffer[characterIndex] == '\n') {
                            val slice = buffer.subSequence(currentOffset, characterIndex)
    
                            // Despite believing that `currentIndex` is not read,
                            // it is indeed read the next time this generator is called.
                            @Suppress("AssignedValueIsNeverRead")
                            currentOffset = characterIndex + 1
                            return@generateSequence slice
                        }
                    }
                    // A '\n' is always found, because the files end with a new line.
                    return@generateSequence null
                }
            }
    
            private fun EightBitString.indexOfMax(
                startIndex: Int,
                endIndex: Int,
            ): Int {
                if (startIndex >= endIndex) {
                    return -1
                }
                var maxIndex = startIndex
                var max = 0.toByte()
                for (i in startIndex until endIndex) {
                    val c = getByte(i)
                    if (c > max) {
                        maxIndex = i
                        max = c
                    }
                }
                return maxIndex
            }
    
            private fun Char.toDigit(): Byte = (this - '0').toByte()
        }
    }
    
    
  • Jayjader@jlai.lu
    link
    fedilink
    arrow-up
    2
    ·
    3 days ago

    (Browser-based) Javascript

    For part 2, I eagerly wrote a nice, clean, generic, functional depth-first search, only to get an out of memory error 😭. Note the top-level code blocks: they scope the variables declared inside them, allowing me to run the whole script repeatedly in the console without getting “redeclared variable name” errors.

    function part1(inputText) {
      let totalOutputJoltage = 0;
      for (const batteryBankDef of inputText.split('\n')) {
        let bestBankJoltage = 0;
        const previousDigits = [];
        for (const character of batteryBankDef) {
          const currentDigit = Number.parseInt(character, 10);
          for (const previousDigit of previousDigits) {
            const possibleVoltage = 10 * previousDigit + currentDigit;
            if (possibleVoltage > bestBankJoltage) {
              bestBankJoltage = possibleVoltage;
            }
          }
          previousDigits.push(currentDigit);
        }
            totalOutputJoltage += bestBankJoltage;
      }
      return totalOutputJoltage;
    }
    {
        const start = performance.now();
        const result = part1(document.body.textContent)
        const end = performance.now();
        console.info({day: 3, part: 1, result, time: end - start})
    }
    
    function findNthDigitForSequence(bankDef, n, startIndex) {
      let digit = 9;
      while (digit > 0) {
        for (let i = startIndex; i < bankDef.length - 11 + n; i++) {
          if (bankDef[i] === digit.toString()) {
            return [digit, i]
          }
        }
        digit--;
      }
      return undefined;
    }
    function findBestJoltageForBank(bankDef) {
      const digits = [];
      let previousFoundDigitIndex = -1;
      for (let i = 0; i < 12; i++) {
        const digitFound = findNthDigitForSequence(bankDef, i, previousFoundDigitIndex + 1);
        if (digitFound === undefined) {
          debugger;
          return undefined;
        }
        const [digit, index] = digitFound;
        digits.push(digit);
        previousFoundDigitIndex = index;
      }
      return Number.parseInt(digits.join(''), 10);
    }
    function part2(inputText) {
      let totalOutputJoltage = 0;
      for (const batteryBankDef of inputText.trim().split('\n')) {
        totalOutputJoltage += findBestJoltageForBank(batteryBankDef) ?? 0;
      }
      return totalOutputJoltage;
    }
    
    {
      const start = performance.now();
      const result = part2(document.body.textContent);
      const end = performance.now();
      console.info({ day: 3, part: 2, time: end - start, result });
    }
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    5
    ·
    edit-2
    13 days ago

    Uiua

    You can run this in Uiua Pad

    "987654321111111\n811111111111119\n234234234234278\n818181911112111"
    ⊜∘⊸≠@\n
    Jolt  (⊙⊃⋅∘∘+1(⊡|˜↘⊙+₁)⍜↘⟜⨂⊸/↥)))(+1¯)
    ∩/+⊃≡(Jolt2)(Jolt12)
    
    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.

      • mykl@lemmy.world
        link
        fedilink
        arrow-up
        2
        ·
        12 days ago

        Thanks, although it only really became clear after I’d written it. I am thinking about doing a more detailed explainer post based on one day’s answer to help people get a feel for it.

  • Deebster@programming.dev
    link
    fedilink
    English
    arrow-up
    5
    ·
    edit-2
    13 days 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()
        }
    }
    
  • Strlcpy@1@lemmy.sdf.org
    link
    fedilink
    English
    arrow-up
    4
    ·
    edit-2
    12 days 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!

  • Pyro@programming.dev
    link
    fedilink
    arrow-up
    4
    ·
    edit-2
    13 days 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
    
    
  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    4
    ·
    13 days ago

    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]  
    
    • Amy@piefed.blahaj.zone
      link
      fedilink
      English
      arrow-up
      1
      ·
      12 days ago

      Version 2. I realized last night that my initial approach was way more complicated than it needed to be…

      import Data.List
      import Data.Semigroup
      
      maxJolt :: Int -> [Char] -> Int
      maxJolt r xs = read $ go r (length xs) xs
        where
          go r n xs =
            (\(Arg x xs) -> x : xs) . maximum $
              do
                (n', x : xs') <- zip (reverse [r .. n]) (tails xs)
                return . Arg x $ if r == 1 then [] else go (r - 1) (n' - 1) xs'
      
      main = do
        input <- lines <$> readFile "input03"
        mapM_ (print . sum . (`map` input) . maxJolt) [2, 12]
      
  • eco_game@discuss.tchncs.de
    link
    fedilink
    arrow-up
    4
    ·
    12 days ago

    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

    • chunkystyles@sopuli.xyz
      link
      fedilink
      English
      arrow-up
      2
      ·
      12 days ago

      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
      }
      
        • chunkystyles@sopuli.xyz
          link
          fedilink
          English
          arrow-up
          2
          arrow-down
          1
          ·
          12 days ago

          I was curious, so I ran yours and it is only like 3-4ms slower. I was honestly surprised it was that close.

          Just goes to show that we’re often wrong when estimating and the only real way to know is to benchmark.

          • Deebster@programming.dev
            link
            fedilink
            arrow-up
            2
            ·
            12 days ago

            My version used strings as well, and I thought that as I was comparing small integers either way, it made sense to stay in ASCII as the strings were already easy to index, and it meant I could skip parsing input numbers, only needing to parse output numbers so they could be summed.

            I did start with numbers so I could convert it back to compare, but it’s so fast (the whole thing takes 1ms - and that’s reading/parsing the input twice) that it’s almost a micro benchmark.

          • eco_game@discuss.tchncs.de
            link
            fedilink
            arrow-up
            2
            ·
            12 days ago

            Yeah, I vaguely remember reading something about how close string splitting is to all the logarithm math in splitting numbers, and since then I’ve just always used strings because that’s way more intuitive to me lol

  • h4x0r@lemmy.dbzer0.com
    link
    fedilink
    English
    arrow-up
    3
    ·
    edit-2
    12 days ago

    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);
    }
    
    • hades@programming.devM
      link
      fedilink
      arrow-up
      4
      ·
      13 days ago
      constexpr u32 TEN = 10;
      constexpr u64 TWELVE = 12;
      

      I love the easter egg jokes you add to your code :)

  • Camille@lemmy.ml
    link
    fedilink
    arrow-up
    3
    ·
    13 days 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

  • Avicenna@programming.dev
    link
    fedilink
    arrow-up
    3
    ·
    12 days 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
    
  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    3
    ·
    13 days 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

  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    3
    ·
    12 days 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 Program

    It 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
    
  • Chais@sh.itjust.works
    link
    fedilink
    arrow-up
    2
    ·
    8 days ago

    Python

    I had a hunch that part 2 would just be part one with more places and immediately factored that code into a separate method. Overall quite happy with the compact code.

    from functools import partial
    from pathlib import Path
    from typing import List
    
    
    def parse_input(input: str) -> List[List[int]]:
        return [list(map(int, l)) for l in input.splitlines()]
    
    
    def find_highest(bank: List[int], n: int) -> int:
        if n == 1:
            return max(bank)
        rest = bank[bank[:-n+1].index(place := max(bank[:-n+1]))+1:]
        return int(f"{place}{find_highest(rest, n-1)}")
    
    
    def part_one(input: str) -> int:
        bat_banks = parse_input(input)
        return sum(map(partial(find_highest, n = 2), bat_banks))
    
    
    def part_two(input: str) -> int:
        bat_banks = parse_input(input)
        return sum(map(partial(find_highest, n = 12), bat_banks))
    
    
    if __name__ == "__main__":
        input = Path("_2025/_3/input").read_text("utf-8")
        print(part_one(input))
        print(part_two(input))