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

  • Avicenna@programming.dev
    link
    fedilink
    arrow-up
    3
    ·
    3 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
    
  • Gobbel2000@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    3 days 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!();
    
  • Camille@lemmy.ml
    link
    fedilink
    arrow-up
    3
    ·
    3 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

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

    I’m really proud of my solution to part 2. When I first read it, I was freaking stumped, I had no idea how to approach it. I was wondering if I was going to wind up resorting to brute forcing it in factorial time. But then I had an idea on the way home, and I one shotted it! (Got a few tests on the example but first try on the actual input)

    from day3_p1 import get_input, get_banks, sample
    
    def turn_on_batteries(bank: string, num_on: int):
        bank_size = len(bank)
        l = 0
        r = bank_size - num_on + 1
        on_bats = []
        while r <= bank_size:
            index_batt_list = list(enumerate(bank))
            index, batt = max(index_batt_list[l:r], key=lambda x: x[1])
            on_bats.append(batt)
            old_l = l
            l = index + 1
            r += 1
        return int("".join(on_bats))
    
    
    actual = get_input("input")
    
    if __name__ == "__main__":
        all_banks = get_banks(actual, "\n")
        res = 0
        for bank in all_banks:
            res += turn_on_batteries(bank, 12)
        print(res)
    

    Part 1 for completeness:

    def get_input(path: str) -> str:
        with open("input") as f:
            data = f.read()
        return data.strip()
    
    def get_banks(data: str, sep=" ") -> list[str]:
        return data.split(sep)
    
    def find_max_battery(bank: str, heap_size=2) -> int:
        batteries = list(enumerate([int(c) for c in bank]))
        first_digit = max(batteries, key=lambda x: x[1])
        if first_digit[0] == len(bank) - 1:
            first_digit = max(batteries[:-1], key=lambda x: x[1])
        second_digit = max(batteries[first_digit[0]+1:], key=lambda x: x[1])
        return first_digit[1] * 10 + second_digit[1]
    
    
    
    sample = "987654321111111 811111111111119 234234234234278 818181911112111" 
    actual = get_input("input")
    
    DATA = actual
    if __name__ == "__main__":
        all_banks = get_banks(DATA, "\n")
        res = 0
        for bank in all_banks:
            res += find_max_battery(bank)
        print(res)
    
  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    1
    ·
    2 days ago

    This problem was quite straightforward; I think it probably could have been day 1. The only interesting thing here is the use of values and multiple-value-bind, which are the standard way in Common Lisp to give a function a primary return value but also one or more advisory return values. This lets you return a rich data structure for consumers who want it, but consumers who don’t want it don’t need to parse that entire data structure to get the primary return value.

    (defun parse-line (line)
      (let ((digits (loop for i from 0 to (1- (length line))
                          collect (parse-integer line :start i :end (1+ i)))))
        (make-array (list (length digits)) :initial-contents digits)))
    
    (defun read-inputs (filename)
      (let* ((input-lines (uiop:read-file-lines filename)))
        (mapcar #'parse-line input-lines)))
    
    (defun arg-max (v &key start end)
      "Yield the index and the greatest element of vector v, between indices start (inclusive) and
      end (exclusive) if they are supplied. Returns the earliest instance of the maximum."
      (let* ((start (max 0 (or start 0)))
             (end (min (length v) (or end (length v))))
             (arg-max start)
             (val-max (aref v start)))
        (loop for i from start to (1- end)
              if (> (aref v i) val-max)
                do (progn (setf arg-max i)
                          (setf val-max (aref v i)))
              finally (return (values arg-max val-max)))))
    
    (defun bank-max-joltage (digits bank)
      (let ((search-start 0)
            (result 0))
        (loop for d from 0 to (1- digits)
              do (multiple-value-bind (digit-pos digit)
                     (arg-max bank :start search-start :end (+ (length bank) (- digits) (1+ d)))
                   (setf search-start (1+ digit-pos))
                   (setf result (+ (* 10 result) digit))))
        result))
    
    (defun main-1 (filename)
      (reduce #'+ (mapcar #'(lambda (bank) (bank-max-joltage 2 bank))
                          (read-inputs filename))))
    
    (defun main-2 (filename)
      (reduce #'+ (mapcar #'(lambda (bank) (bank-max-joltage 12 bank))
                          (read-inputs filename))))
    
  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    3
    ·
    3 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

  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    4
    ·
    3 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
      ·
      2 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]
      
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    3
    ·
    3 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
    
  • eco_game@discuss.tchncs.de
    link
    fedilink
    arrow-up
    4
    ·
    3 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
      ·
      3 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
          ·
          2 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
            ·
            2 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
            ·
            2 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

  • LeixB@lemmy.world
    link
    fedilink
    arrow-up
    1
    ·
    3 days 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
    
  • urandom@lemmy.world
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    2 days ago

    I’m still struggling to learn Rust, and also to figure out a good solution to these problems, but here’s mine anyway:

    #[allow(dead_code)]
    pub fn part1(input: Lines<BufReader<File>>) {
        let mut sum = 0;
        for line in input.map_while(Result::ok) {
            let total = find_joltage(line.as_bytes(), 2, false);
            let res = str::from_utf8(total.as_slice()).unwrap();
    
            sum += res.parse::<u32>().unwrap();
        }
    
        println!("{}", sum);
    }
    
    #[allow(dead_code)]
    pub fn part2(input: Lines<BufReader<File>>) {
        let mut sum = 0;
        for (_, line) in input.map_while(Result::ok).enumerate() {
            let total = find_joltage(line.as_bytes(), 12, false);
            let res = str::from_utf8(total.as_slice()).unwrap();
    
            sum += res.parse::<u64>().unwrap();
        }
    
        println!("{}", sum);
    }
    
    fn find_joltage(b: &[u8], size: usize, debug: bool) -> Vec<u8> {
        if size == 0 {
            return vec![];
        }
    
        let mut max: u8 = 0;
        let mut max_i = 0;
        for i in (0..=b.len() - size).rev() {
            if b[i] >= max {
                max = b[i];
                max_i = i;
            }
        }
    
        if debug {
            println!("max = {}, i = {}", max as char, max_i);
        }
        let mut rest = find_joltage(&b[max_i + 1..], size - 1, debug);
    
        rest.insert(0, max);
        rest
    }
    
  • VegOwOtenks@lemmy.world
    link
    fedilink
    English
    arrow-up
    2
    ·
    1 day ago

    Futhark

    I am on my way to re-do all previous days in Futhark and complete the Rest of AoC, hopefully.

    def hole: u8 = 0
    def zipIndices 'a (xs: []a): [](i64, a) = zip (indices xs) xs
    def foldMin (xs: []u8): (i64, u8) = 
      let indexedXs = tail (zipIndices xs) in
      let start = (0, head xs) in
      foldl (\ (ci, cv) (ni, nv) -> if nv > cv then (ni, nv) else (ci, cv)) start indexedXs
    
    def slice 'a (xs: []a) (start: i64) (end: i64) = drop start (take end xs)
    
    def pickBattery (bank: []u8) (reserved: i64): (i64, u8) = 
      let batteries = slice bank 0 (length bank - reserved) in
      foldMin batteries
    
    def pickNBatteries (n: i8) (banks: []u8): u64 =
      let (_, result) =
        loop (batteries, sum) = (banks, 0)
        for i in reverse (0...n-1)
        do
          let (offset, battery) = pickBattery batteries (i64.i8 i) in
          (drop (offset + 1) batteries, sum * 10 + u64.u8 battery)
      in result
    
    def part1 (banks: [][]u8): u64 = reduce (+) 0 (map (pickNBatteries 2) banks)
    
    def part2 (banks: [][]u8): u64 = reduce (+) 0 (map (pickNBatteries 12) banks)
    
    def main (banks: [][]u8) = (part1 banks, part2 banks)
    
    Script to Generate input for Futhark
    {-# OPTIONS_GHC -Wall #-}
    {-# LANGUAGE OverloadedStrings #-}
    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.List as List
    import qualified Data.ByteString as ByteString
    import Data.Word (byteSwap64, Word64)
    import GHC.ByteOrder (ByteOrder(..), targetByteOrder)
    import qualified Data.Bits as Bits
    
    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
    
    byteStringLeWord64 :: Word64 -> ByteString.ByteString
    byteStringLeWord64 word = let
        leWord = case targetByteOrder of
          BigEndian -> byteSwap64 word
          LittleEndian -> word
      in ByteString.pack . map (fromIntegral . (leWord `Bits.shiftR`)) $ [0,8..56]
    
    main :: IO ()
    main = do
      batteryBanks <- parse <$!> TextIO.getContents
      putChar 'b'
      ByteString.putStr (ByteString.singleton 2) -- version
      ByteString.putStr (ByteString.singleton 2) -- dimensions
      TextIO.putStr "  u8" -- type
      ByteString.putStr (byteStringLeWord64 . fromIntegral . succ . rowsOf $ batteryBanks) -- outer dim
      ByteString.putStr (byteStringLeWord64 . fromIntegral . succ . colsOf $ batteryBanks) -- inner dim
      ByteString.putStr . ByteString.pack . fmap fromIntegral . Array.elems $ batteryBanks -- elements
    
  • Deebster@programming.dev
    link
    fedilink
    English
    arrow-up
    5
    ·
    edit-2
    3 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()
        }
    }