Day 5: Cafeteria

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
    arrow-up
    1
    ·
    edit-2
    22 hours ago

    Kotlin

    I like this one. The first part could be solved trivially with a naive approach. The second part is also not too complicated.
    I started out with a simple merging algorithm that would check all ranges, except the one to merge, for overlaps.
    With clever sorting, I can skip the search for mergable ranges and just iterate in reverse and build larger and larger ranges.
    I haven’t tried to merge adjacent ranges like 1…2 and 3…4 to 1…4. The solution is fast enough already when JIT/C2 compiled (200-250 µs).

    Code on Github

    Code
    class Day05 : AOCSolution {
        override val year = 2025
        override val day = 5
    
        override fun part1(inputFile: String): String {
            val (freshIngredients, availableIngredients) = readResourceTwoParts(inputFile)
    
            val freshRanges = buildRanges(freshIngredients)
    
            val ingredientIds = availableIngredients.lines().filterNot(String::isEmpty).map(String::toLong)
    
            val count = ingredientIds.count { id -> freshRanges.any { range -> id in range } }
    
            return count.toString()
        }
    
        override fun part2(inputFile: String): String {
            val (freshIngredients, _) = readResourceTwoParts(inputFile)
    
            val freshRanges = buildRanges(freshIngredients)
    
            return freshRanges.sumOf { range ->
                range.last - range.first + 1
            }.toString()
        }
    
        private companion object {
            private fun buildRanges(ingredients: String): List<LongRange> {
                val lines = ingredients.lines()
                val ranges = MutableList(lines.size) { i ->
                    val line = lines[i]
                    val hyphen = line.indexOf('-')
                    val lower = line.take(hyphen).toLong()
                    val upper = line.substring(hyphen + 1).toLong()
                    LongRange(lower, upper)
                }
    
                // Sort the ranges
                // The ones with the smallest ID and the least upper end
                // get sorted to the beginning.
                // This allows for easy merging, as overlapping ranges are always adjacent
                ranges.sortWith(Comparator { r1, r2 ->
                    val first = r1.first.compareTo(r2.first)
                    if (first != 0) {
                        first
                    } else {
                        r1.last.compareTo(r2.last)
                    }
                })
    
                // Merge adjacent ranges backwards, modifying the list in-place
                for (i in ranges.lastIndex downTo 1) {
                    val lowerRange = ranges[i - 1]
                    val upperRange = ranges[i]
    
                    // The two ranges do overlap, because the tail of the first range
                    // is in the second range.
                    if (upperRange.first <= lowerRange.last) {
                        ranges[i - 1] = LongRange(
                            lowerRange.first,
                            maxOf(lowerRange.last, upperRange.last)
                        )
                        ranges.removeAt(i)
                    }
                }
    
                return ranges
            }
        }
    }
    
  • Jayjader@jlai.lu
    link
    fedilink
    English
    arrow-up
    2
    ·
    2 days ago

    (Browser-based) Javascript

    The good ol’ range merge is back! I’ve done the past few years in Rust, which has first-class “support” for inclusive ranges, so I was dreading having to re-implement things. Turns out when you can ignore lifetimes things get simpler.

    Code
    function part1(inputText) {
      const [freshRangeDefs, availableIds] = inputText.trim().split('\n\n');
      const parsedIds = new Set(availableIds.trim().split('\n').map(strId => Number.parseInt(strId, 10)));
      const parsedRanges = freshRangeDefs.trim().split('\n').map(rangeDef => rangeDef.split('-').map(strBound => Number.parseInt(strBound, 10)));
    
      for (const id of parsedIds) {
        let fresh = false;
        for (const [lowerBound, upperBound] of parsedRanges) {
          if (lowerBound <= id && id <= upperBound) {
            fresh = true;
            break;
          }
        }
        if (!fresh) {
          parsedIds.delete(id)
        }
      }
      return parsedIds.size;
    }
    
    {
      const start = performance.now();
      const result = part1(document.body.textContent);
      const end = performance.now();
      console.info({
        day: 5,
        part: 1,
        time: end - start,
        result
      });
    }
    
    function part2(inputText) {
      const [freshRangeDefs] = inputText.trim().split('\n\n');
      const parsedRanges = freshRangeDefs.trim().split('\n').map(rangeDef => rangeDef.split('-').map(strBound => Number.parseInt(strBound, 10)));
      parsedRanges.sort(([lowerA, upperA], [lowerB, upperB]) => lowerA - lowerB);
      const merged = [parsedRanges[0]];
      for (const [parsedLower, parsedUpper] of parsedRanges) {
        let inserted = false;
        for (let mIndex = 0; mIndex < merged.length; mIndex++) {
          const [mLower, mUpper] = merged[mIndex];
          if (mUpper < parsedLower) {
            // parsed is completely "above" merged
            continue
          } else if (parsedUpper < mLower) {
            // parsed is completely "below" merged
            merged.splice(mIndex, 0, [parsedLower, parsedUpper]);
            inserted = true;
            break;
          } else {
            // parsed and merged intersect somehow/somewhere)
            merged[mIndex][0] = Math.min(parsedLower, mLower)
            merged[mIndex][1] = Math.max(parsedUpper, mUpper)
            inserted = true;
            break;
          }
        }
        if (!inserted) {
          // parsed is completely "above" ALL already-merged ranges
          merged.push([parsedLower, parsedUpper]);
        }
      }
      return merged.reduce((accu, [lower, upper]) => accu + upper - lower + 1, 0)
    }
    {
      const exampleText = `3-5
    10-14
    16-20
    12-18
    
    1
    5
    8
    11
    17
    32
    `
      const start = performance.now();
      //   const result = part2(exampleText);
      const result = part2(document.body.textContent);
      const end = performance.now();
      console.info({
        day: 5,
        part: 2,
        time: end - start,
        result
      });
    }
    
  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    4
    ·
    10 days ago

    Haskell

    IntSet was the wrong first choice for part 2 :3

    import Control.Arrow  
    import Data.Foldable  
    import Data.Ix  
    
    readInput :: [Char] -> ([(Int, Int)], [Int])  
    readInput =  
      (map readRange *** (map read . tail))  
        . break (== "")  
        . lines  
      where  
        readRange = (read *** (read . tail)) . break (== '-')  
    
    part1 (ranges, ids) = length $ filter (\id -> any (`inRange` id) ranges) ids  
    
    part2 (ranges, _) = sum $ map rangeSize $ foldl' addRange [] ranges  
      where  
        addRange [] x = [x]  
        addRange (r : rs) x  
          | touching r x = addRange rs $ merge r x  
          | otherwise = r : addRange rs x  
        touching (a, b) (c, d) = not (b < c - 1 || a > d + 1)  
        merge (a, b) (c, d) = (min a c, max b d)  
    
    main = do  
      input <- readInput <$> readFile "input05"  
      print $ part1 input  
      print $ part2 input  
    
  • Zikeji@programming.dev
    link
    fedilink
    English
    arrow-up
    3
    ·
    10 days ago

    Javascript

    Short and sweet. Basically just handle all the range overlaps for part 2 and then do basic subtraction to get the answer.

    spoiler
    const input = require('fs').readFileSync('input-day5.txt', 'utf-8');
    
    /** @typedef {[number, number]} Range */
    
    /** @type {[Range[], number[]]} */
    const [freshRanges, availableIngredients] = input.split("\n\n").map(l => l.split("\n")).map((l, i) => (i === 1) ? l.map(v => parseInt(v, 10)) : l.map(v => v.split('-').map(vv => parseInt(vv, 10))));
    
    
    let freshOnHand = 0;
    
    for (const ingredient of availableIngredients) {
        for (const [start, stop] of freshRanges) {
            if (ingredient >= start && ingredient <= stop) {
                freshOnHand += 1;
                break;
            }
        }
    }
    
    console.log(`Part 1 Answer: ${freshOnHand}`);
    
    const sortedRanges = [...freshRanges].sort((a, b) => a[0] - b[0]);
    
    const mergedRanges = [];
    let current = sortedRanges[0];
    
    for (let i = 1; i < sortedRanges.length; i++) {
        const [nextStart, nextEnd] = sortedRanges[i];
        
        if (nextStart <= current[1] + 1) {
            current = [current[0], Math.max(current[1], nextEnd)];
        } else {
            mergedRanges.push(current);
            current = [nextStart, nextEnd];
        }
    }
    
    mergedRanges.push(current);
    
    const freshIngredientCount = mergedRanges.reduce((acc, [start, stop]) => acc + ((stop + 1) - start), 0)
    
    console.log(`Part 2 Answer: ${freshIngredientCount}`);
    
  • Gobbel2000@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    9 days ago

    Rust

    Tried to outsmart part 2 by not merging ranges but just subtracting overlapping ranges from the current range’s size, but then overlapping overlaps are subtracted more than once. So I ended up merging the ranges. They are kept in a list that is sorted by their starting position. Also, transforming the inclusive ranges in the input into exclusive ranges made things quite a bit easier.

    View on github

    use std::ops::Range;
    
    fn parse_input(input: &str) -> (Vec<Range<u64>>, Vec<u64>) {
        let ranges: Vec<_> = input
            .lines()
            .take_while(|l| !l.is_empty())
            .map(|l| {
                let (a, b) = l.split_once('-').unwrap();
                a.parse().unwrap()..b.parse::<u64>().unwrap() + 1
            })
            .collect();
        let nums = input
            .lines()
            .skip(ranges.len() + 1)
            .map(|n| n.parse().unwrap())
            .collect();
        (ranges, nums)
    }
    
    fn part1(input: String) {
        let (ranges, nums) = parse_input(&input);
        let count = nums
            .iter()
            .filter(|n| ranges.iter().any(|r| r.contains(n)))
            .count();
        println!("{count}");
    }
    
    fn part2(input: String) {
        let (ranges, _) = parse_input(&input);
        // Ranges are added to this Vec always sorted by start and non-overlapping
        let mut merged: Vec<Range<u64>> = Vec::with_capacity(ranges.len());
        for r in ranges {
            // Find index of first intersecting range
            let first_int_o = merged.iter().position(|m| {
                // Intersection range (if any)
                let int_start = r.start.max(m.start);
                let int_end = r.end.min(m.end);
                int_start < int_end
            });
            if let Some(first_int) = first_int_o {
                // Exclusive
                let last_int = merged.len()
                    - merged
                        .iter()
                        .rev()
                        .position(|m| {
                            let int_start = r.start.max(m.start);
                            let int_end = r.end.min(m.end);
                            int_start < int_end
                        })
                        .unwrap();
                // New range replacing all intersecting ranges
                let new = r.start.min(merged[first_int].start)..r.end.max(merged[last_int - 1].end);
                merged[first_int] = new;
                for i in (first_int + 1)..last_int {
                    merged.remove(i);
                }
            } else {
                // Does not overlap with anything. Find index that keeps sorting
                let idx = merged
                    .iter()
                    .position(|m| m.start > r.start)
                    .unwrap_or(merged.len());
                merged.insert(idx, r);
            }
        }
        let count = merged.iter().map(|r| r.end - r.start).sum::<u64>();
        println!("{count}");
    }
    
    util::aoc_main!();
    
  • Camille@lemmy.ml
    link
    fedilink
    arrow-up
    2
    ·
    9 days ago

    Go

    I started by not sorting the ranges in the input stream and try to merge everything together. It didn’t work, but the same algorithm with sorted input does. I guess I’m missing something important, but I tested my code and can’t find the corner case that make it fail. Bah…

    day05.go
    package main
    
    import (
    	"aoc/utils"
    	"cmp"
    	"fmt"
    	"slices"
    	"strconv"
    	"strings"
    )
    
    type idrange struct {
    	start, end int
    }
    
    func (rng idrange) contains(entry int) bool {
    	return rng.start <= entry && entry <= rng.end
    }
    
    func (rng idrange) length() int {
    	return (rng.end - rng.start) + 1
    }
    
    type rangeSet []idrange
    
    func (r *rangeSet) addRange(rng idrange) {
    	containsStartIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    		return elem.contains(rng.start)
    	})
    	containsEndIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    		return elem.contains(rng.end)
    	})
    
    	// The range overlaps to other ranges.
    	if containsStartIdx != -1 && containsEndIdx != -1 {
    		// If it is in fact contained inside one range, it is ignored.
    		if containsStartIdx == containsEndIdx {
    			return
    		}
    
    		before := (*r)[containsStartIdx]
    		after := (*r)[containsEndIdx]
    		before.end = after.end
    		(*r)[containsStartIdx] = before
    		*r = slices.Delete(*r, containsStartIdx+1, containsEndIdx+1)
    	} else if containsEndIdx != -1 {
    		// If the range's end overlaps with another range, that range is
    		// extended on the front to start like the range in argument.
    		after := (*r)[containsEndIdx]
    		after.start = rng.start
    		(*r)[containsEndIdx] = after
    
    		smallestAfterIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    			return rng.start < elem.start
    		})
    
    		if smallestAfterIdx != -1 && smallestAfterIdx != containsEndIdx+1 {
    			*r = slices.Delete(*r, smallestAfterIdx, containsEndIdx)
    		}
    	} else if containsStartIdx != -1 {
    		// If the range's start overlaps with another range, that range is
    		// extended on the back to start like the range in argument.
    		before := (*r)[containsStartIdx]
    		before.end = rng.end
    		(*r)[containsStartIdx] = before
    
    		smallestAfterIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    			return rng.end < elem.end
    		})
    
    		if smallestAfterIdx != -1 && smallestAfterIdx != containsStartIdx+1 {
    			*r = slices.Delete(*r, containsStartIdx+1, smallestAfterIdx)
    		}
    	} else {
    		// If the range is standalone, it is added at the right position.
    		afterIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    			return rng.start < elem.start
    		})
    
    		if afterIdx == -1 {
    			*r = append(*r, rng)
    		} else {
    			*r = slices.Insert(*r, afterIdx, rng)
    		}
    	}
    }
    
    func (r rangeSet) contains(entry int) bool {
    	for _, rng := range r {
    		if rng.contains(entry) {
    			return true
    		}
    	}
    
    	return false
    }
    
    func (r rangeSet) length() int {
    	sum := 0
    	for _, rng := range r {
    		sum += rng.length()
    	}
    	return sum
    }
    
    func parseRange(line string) (idrange, error) {
    	bounds := strings.Split(line, "-")
    	start, err1 := strconv.Atoi(bounds[0])
    	end, err2 := strconv.Atoi(bounds[1])
    
    	if err1 != nil {
    		return idrange{}, err1
    	}
    	if err2 != nil {
    		return idrange{}, err2
    	}
    
    	return idrange{
    		start: start,
    		end:   end,
    	}, nil
    }
    
    func parseRangeSet(input chan string) (rangeSet, error) {
    	rangeSet := rangeSet{}
    	ranges := []idrange{}
    
    	for line := range input {
    		if line == "" {
    			break
    		}
    
    		rng, err := parseRange(line)
    		if err != nil {
    			return nil, err
    		}
    
    		ranges = append(ranges, rng)
    	}
    
    	slices.SortFunc(ranges, func(a, b idrange) int {
    		return cmp.Compare(a.start, b.start)
    	})
    
    	for _, rng := range ranges {
    		rangeSet.addRange(rng)
    	}
    
    	return rangeSet, nil
    }
    
    func getNumberChannel(input chan string) chan int {
    	ch := make(chan int, cap(input))
    
    	go func() {
    		for line := range input {
    			num, _ := strconv.Atoi(line)
    			ch <- num
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    func stepOne(input chan string) (int, error) {
    	rngSet, errParse := parseRangeSet(input)
    	if errParse != nil {
    		return 0, errParse
    	}
    
    	numCh := getNumberChannel(input)
    	count := 0
    
    	for entry := range numCh {
    		if rngSet.contains(entry) {
    			count++
    		}
    	}
    
    	return count, nil
    }
    
    func stepTwo(input chan string) (int, error) {
    	rngSet, err := parseRangeSet(input)
    	if err != nil {
    		return 0, err
    	}
    
    	return rngSet.length(), nil
    }
    
    func main() {
    	inputFile := utils.FilePath("day05.txt")
    	utils.RunStep(utils.ONE, inputFile, stepOne)
    	utils.RunStep(utils.TWO, inputFile, stepTwo)
    }
    
  • eco_game@discuss.tchncs.de
    link
    fedilink
    arrow-up
    2
    ·
    9 days ago

    Kotlin

    I first solved the puzzle with my own horrible range-merging algorithm, had quite a few nasty off by one errors. I quickly replaced it with the “normal” merge-algorithm after getting the right solution.

    If you really want to see my abomination, it’s still in the commits on my repo.

    Solution
    class Day05 : Puzzle {
    
        val validIds = mutableSetOf<LongRange>()
        val ids = mutableListOf<Long>()
    
        override fun readFile() {
            val input = readInputFromFile("src/main/resources/a2025/day05.txt")
            val inputList = input.split("\n\n", limit = 2)
            validIds.addAll(
                inputList[0].lines().filter { it.isNotBlank() }
                    .map { it.split("-").map { it.toLong() } }
                    .map { LongRange(it[0], it[1]) }
            )
            ids.addAll(inputList[1].lines().filter { it.isNotBlank() }.map { it.toLong() })
        }
    
        override fun solvePartOne(): String {
            return ids.count { id -> validIds.any { id in it } }.toString()
        }
    
        override fun solvePartTwo(): String {
            val idsSorted = validIds.sortedBy { it.first }
            val merged = mutableSetOf<LongRange>()
    
            var current = idsSorted.first()
            for (i in 1..<idsSorted.size) {
                val next = idsSorted[i]
    
                if (next.first <= current.last) {
                    current = LongRange(current.first, max(current.last, next.last()))
                } else {
                    merged.add(current)
                    current = next
                }
            }
            merged.add(current)
            return merged.sumOf { it.last + 1 - it.first }.toString()
        }
    }
    

    full code on Codeberg

    • chunkystyles@sopuli.xyz
      link
      fedilink
      English
      arrow-up
      1
      ·
      9 days ago

      It’s amusing just how similar our solutions are today. The only real issue I had today was not considering both sides of each range and only taking the last part of the range in the sort order.

      var ingredientRanges: MutableList<LongRange> = mutableListOf()
      var ingredients: MutableList<Long> = mutableListOf()
      
      fun main() {
          val input = getInput(5)
          parseInput1(input)
          var count = 0L
          ingredientRanges.sortBy { it.first }
          var i = 0
          while (i < ingredientRanges.size) {
              var j = i + 1
              while (j < ingredientRanges.size && doRangesOverlap(ingredientRanges[i], ingredientRanges[j])) {
                  ingredientRanges[i] = LongRange(ingredientRanges[i].first, max(ingredientRanges[i].last, ingredientRanges[j].last))
                  j++
              }
              count += ingredientRanges[i].last - ingredientRanges[i].first + 1
              i = j
          }
          println(count)
      }
      
      fun parseInput1(input: String) {
          val split = input.split("\n\n")
          split[0].lines()
              .filter { it.isNotBlank() }
              .forEach {
                  val range = it.split("-")
                  ingredientRanges.add(LongRange(range[0].toLong(), range[1].toLong()))
              }
          split[1].lines()
              .filter { it.isNotBlank() }
              .forEach { ingredients.add(it.toLong()) }
      }
      
      fun doRangesOverlap(left: LongRange, right: LongRange): Boolean = right.first in left || right.first - 1 == left.last
      
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    9 days ago

    Uiua

    It took me a while to work out the merging of ranges, but I’m very pleased with this solution.

    merging ranges

    Merging all the ranges first makes both parts faster. Sorting the ranges first means that you only need to compare adjacent pairs of ranges to decide whether they need to remain separate, or can be merged. If the end of the next range is < end of this range you can drop it; if the start of the next range < end of this range replace this with a new range of (start of this range, max of both ends); otherwise just add next to the merged list and repeat…

    (EDIT: I realised that Part1 was really slow, so rewrote it, and total time dropped from 140ms to about 4ms, which is nice.)

    Run it here

    D  "3-5\n10-14\n16-20\n12-18\n\n1\n5\n8\n11\n17\n32"
    # D       &fras"2025day05.txt" # drop your input file on this pane and uncomment this line to test against your own data.
    Parse   (⊜⋕⊸∊+@010)°$"_\n\n_"          # split at \n\n; find all digits.
    Merge   ((⊟|⊂⊢⟜(↥∩⊣))(≤⊓⊣⊢)|⊙◌)(≤∩⊣) # distinct/overlap/contained.
    Ranges  ⊙◌⍥⍜⊣(Merge⊙°⊂)◡⋅⧻⊃↙↘1⍆↯∞_2      # Merge pairs of ranges.
    
    P₁  /+≠∩⌟1˜⊞≥)⍜⍉°⊟ # membership test
    P₂  /+≡(+1/-)⊙◌       # range counts
    &pnow(P₁ P₂ Ranges Parse D)
    
  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    2
    ·
    9 days ago

    Nothing interesting here. I was tripped up momentarily by the fact that Common Lisp sort, while it’s a destructive operation, does not leave its input argument equal to its result! Typically you see either “nondestructive, returns a value” (Python sorted()) or “destructive, leaves the object in the final state” (Python list.sort()). Old school Lisp sort returns the sorted list and the input structure is not guaranteed to be meaningful afterward. Hope you weren’t using that pair for anything; it now points to hell.

    (ql:quickload :str)
    
    (defun read-inputs (filename)
      (let* ((input-lines (uiop:read-file-lines filename))
             (range-lines (remove-if-not #'(lambda (s) (find #\- s)) input-lines))
             (id-lines (remove-if #'(lambda (s) (or (zerop (length s)) (find #\- s))) input-lines)))
        (flet ((parse-range (line) (mapcar #'parse-integer (str:split "-" line))))
          (cons (mapcar #'parse-range range-lines)
                (mapcar #'parse-integer id-lines)))))
    
    (defun fresh? (fresh-ranges id)
      "Return the first fresh range containing id, or nil if there is no such range.
      Assumes the fresh ranges are sorted to enable early exit."
      (loop for fresh-range in fresh-ranges
            when (<= (car fresh-range) id (cadr fresh-range))
              return fresh-range
            when (> (car fresh-range) id)
              return nil
            finally (return nil)))
    
    (defun range< (range1 range2)
      (destructuring-bind (a1 b1) range1
        (destructuring-bind (a2 b2) range2
          (or (< a1 a2)
              (and (= a1 a2) (< b1 b2))))))
    
    (defun main-1 (filename)
      (destructuring-bind (fresh-ranges . ids) (read-inputs filename)
        (let ((sorted-fresh-ranges (sort fresh-ranges #'range<)))
          (loop for id in ids
                sum (if (fresh? sorted-fresh-ranges id) 1 0)))))
    
    (defun consolidate (fresh-ranges)
      "Remove redundancy in a sorted list of fresh ranges: emit non-overlapping ranges."
      (let ((result nil)
            (current-range (car fresh-ranges)))
        (loop for fresh-range in (cdr fresh-ranges)
              do (if (<= (car fresh-range) (cadr current-range))
                     (setf current-range (list (car current-range)
                                               (max (cadr current-range) (cadr fresh-range))))
                     (progn
                       (setf result (cons current-range result))
                       (setf current-range fresh-range)))
              finally (setf result (cons current-range result)))
        result))
    
    (defun main-2 (filename)
      (destructuring-bind (fresh-ranges . ids) (read-inputs filename)
        (let ((sorted-fresh-ranges (sort fresh-ranges #'range<)))
          (reduce #'+
                  (mapcar #'(lambda (range) (1+ (- (cadr range) (car range))))
                          (consolidate sorted-fresh-ranges))))))
    
  • EnEnCode@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    9 days ago

    Well, I guess I’ve decided to do AoC this year. Trivial problem, though I shamelessly stole some range-merging code from AoC 2022 (it was when I was still well in the learning phase of Rust). If you can’t look at old code and wonder WTF you were thinking, have you really gotten any better?

    Solution (mostly uninteresting)
    pub fn day05(input: &str) -> (u64, u64) {
        let mut part1 = 0;
        let mut part2 = 0;
        let mut ranges = Vec::new();
        let mut lines = input.trim().lines();
        for line in lines.by_ref() {
            if line.is_empty() {
                break;
            }
            let range = line
                .split_once('-')
                .map(|(l, h)| [l.parse::<u64>().unwrap(), h.parse::<u64>().unwrap()])
                .unwrap();
            ranges.push(range);
        }
        let ranges = combine_ranges(ranges);
        for idx in lines {
            for r in ranges.iter() {
                if (r[0]..=r[1]).contains(&idx.parse::<u64>().unwrap()) {
                    part1 += 1;
                    break;
                }
            }
        }
        for r in ranges {
            part2 += r[1] - r[0] + 1;
        }
        (part1, part2)
    }
    

    combine_ranges WARNING : Hideous, triggers Clippy, not blazingly fast
    fn combine_ranges(ranges: Vec<[u64; 2]>) -> Vec<[u64; 2]> {
        let mut temp_ranges = ranges;
        let mut comp_ranges: Vec<[u64; 2]> = Vec::new();
        let mut run_again: bool = true;
        while run_again {
            run_again = false;
            comp_ranges = Vec::new();
            'outer: for i in 0..temp_ranges.len() {
                for j in 0..comp_ranges.len() {
                    if temp_ranges[i][0] <= comp_ranges[j][0] && comp_ranges[j][1] <= temp_ranges[i][1]
                    {
                        //temp covers all of comp
                        comp_ranges[j][0] = temp_ranges[i][0];
                        comp_ranges[j][1] = temp_ranges[i][1];
                        run_again = true;
                        continue 'outer;
                    } else if comp_ranges[j][0] <= temp_ranges[i][0]
                        && temp_ranges[i][1] <= comp_ranges[j][1]
                    {
                        //comp covers all of temp
                        run_again = true;
                        continue 'outer;
                    } else if temp_ranges[i][0] <= comp_ranges[j][0]
                        && comp_ranges[j][0] <= temp_ranges[i][1]
                        && temp_ranges[i][1] <= comp_ranges[j][1]
                    {
                        //grow left
                        comp_ranges[j][0] = temp_ranges[i][0];
                        run_again = true;
                        continue 'outer;
                    } else if comp_ranges[j][0] <= temp_ranges[i][0]
                        && temp_ranges[i][0] <= comp_ranges[j][1]
                        && comp_ranges[j][1] <= temp_ranges[i][1]
                    {
                        //grow right
                        comp_ranges[j][1] = temp_ranges[i][1];
                        run_again = true;
                        continue 'outer;
                    }
                }
                comp_ranges.push(temp_ranges[i]);
            }
            temp_ranges = comp_ranges.clone();
        }
    
        comp_ranges
    }
    
  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    10 days ago

    Nim

    Huh, I didn’t expect two easy days in a row.
    Part 1 is a range check. Part 2 is a range merge.

    Runtime: ~720 µs

    type
      AOCSolution[T,U] = tuple[part1: T, part2: U]
    
    proc merge[T](ranges: var seq[Slice[T]]) =
      ranges.sort(cmp = proc(r1, r2: Slice[T]): int = cmp(r1.a, r2.a))
      var merged = @[ranges[0]]
      for range in ranges.toOpenArray(1, ranges.high):
        if range.a <= merged[^1].b:
          if range.b > merged[^1].b:
            merged[^1].b = range.b
        else:
          merged.add range
      ranges = merged
    
    proc solve(input: string): AOCSolution[int, int] =
      let chunks = input.split("\n\n")
      var freshRanges = chunks[0].splitLines().mapIt:
        let t = it.split('-'); t[0].parseInt .. t[1].parseInt
    
      freshRanges.merge()
    
      block p1:
        let availableFood = chunks[1].splitLines().mapIt(parseInt it)
        for food in availableFood:
          for range in freshRanges:
            if food in range:
              inc result.part1
              break
    
      block p2:
        for range in freshRanges:
          result.part2 += range.b-range.a+1
    

    Full solution at Codeberg: solution.nim

  • tranzystorekk@beehaw.org
    link
    fedilink
    English
    arrow-up
    2
    ·
    10 days ago

    Janet

    > read part2 description
    > immediately think of naive set solution
    > look at input
    > oh...
    

    I honestly didn’t expect to one-shot part2, I’m usually pretty bad with coming up with complex solutions and coding them up in first-try :P

  • Strlcpy@1@lemmy.sdf.org
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    10 days ago

    C

    Repo

    Sweet one. Glad the merge could be done with one n2/2 scan and no sorting or moving, which will make the assembly port a bit easier. Speaking of which, I’m still at day 3 there, fighting not the puzzles but the 64K .COM file limit!

    Code
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <assert.h>
    
    #define MR	256
    
    #define MIN(a,b)	((a)<(b)?(a):(b))
    #define MAX(a,b)	((a)>(b)?(a):(b))
    
    struct range { uint64_t lo, hi; };
    
    static struct range rs[MR];
    static int nrs;
    
    int
    main()
    {
    	uint64_t p1=0,p2=0, val;
    	int i,j;
    	char buf[64];
    
            for (; fgets(buf, sizeof(buf), stdin); nrs++) {
                    assert(nrs < MR);
                    if (sscanf(buf, "%lu-%lu", &rs[nrs].lo, &rs[nrs].hi) != 2)
                            break;
            }
    
    	for (i=0; i<nrs-1; i++)
    	for (j=i+1; j<nrs; j++)
    		if (rs[i].lo <= rs[j].hi && rs[i].hi >= rs[j].lo) {
    			rs[j].lo = MIN(rs[i].lo, rs[j].lo);
    			rs[j].hi = MAX(rs[i].hi, rs[j].hi);
    			rs[i].lo = rs[i].hi = 0;
    		}
    
    	while (scanf("%lu", &val) == 1)
    		for (i=0; i<nrs; i++)
    			if (val >= rs[i].lo && val <= rs[i].hi)
    				{ p1++; break; }
    	
    	for (i=0; i<nrs; i++)
    		if (rs[i].lo)
    			p2 += rs[i].hi - rs[i].lo + 1;
    	
    	printf("05: %lu %lu\n", p1, p2);
    }
    
  • Deebster@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    9 days ago

    Rust

    I didn’t look at the input at first so tried a naive version with sets, but it was too slow even for part one. I got smarter and wrote a version with less code that runs in under half a millisecond.

    type Id = usize;
    
    struct Ingredients {
        fresh: Vec<RangeInclusive<Id>>,
        available: HashSet<Id>,
    }
    
    impl Ingredients {
        fn is_fresh(&self, id: Id) -> bool {
            self.fresh.iter().any(|range| range.contains(&id))
        }
    
        fn num_fresh_available(&self) -> usize {
            self.available
                .iter()
                .filter(|&&id| self.is_fresh(id))
                .count()
        }
    
        fn num_fresh_all(&self) -> usize {
            self.fresh
                .iter()
                .map(|range| 1 + range.end() - range.start())
                .sum()
        }
    }
    
    fn merge_ranges(mut ranges: Vec<RangeInclusive<Id>>) -> Vec<RangeInclusive<Id>> {
        if ranges.is_empty() {
            return ranges;
        }
        ranges.sort_by_key(|range| *range.start());
    
        let mut merged = Vec::new();
        let mut start = ranges[0].start();
        let mut end = ranges[0].end();
        for range in ranges.iter().skip(1) {
            if range.start() > end {
                merged.push(RangeInclusive::new(*start, *end));
                start = range.start();
                end = range.end();
            }
            if range.end() > end {
                end = range.end();
            }
        }
        merged.push(RangeInclusive::new(*start, *end));
    
        merged
    }
    
    impl FromStr for Ingredients {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let Some((fresh_str, avail_str)) = s.split_once("\n\n") else {
                bail!("missing divider");
            };
    
            let fresh = fresh_str
                .lines()
                .map(|l| -> Result<RangeInclusive<_>, Self::Err> {
                    let (start, end) = l.split_once('-').ok_or_eyre("bad range")?;
                    let start: Id = start.parse()?;
                    let end: Id = end.parse()?;
                    Ok(start..=end)
                })
                .collect::<Result<_, _>>()?;
    
            let available = avail_str
                .lines()
                .map(|l| l.parse())
                .collect::<Result<_, _>>()?;
    
            Ok(Ingredients {
                fresh: merge_ranges(fresh),
                available,
            })
        }
    }
    
    Full code
    use std::{collections::HashSet, fs, ops::RangeInclusive, str::FromStr};
    
    use color_eyre::eyre::{OptionExt, Report, Result, bail};
    
    #[derive(Debug, PartialEq, Eq)]
    struct Ingredients {
        fresh: Vec<RangeInclusive<Id>>,
        available: HashSet<Id>,
    }
    
    type Id = usize;
    
    impl Ingredients {
        fn is_fresh(&self, id: Id) -> bool {
            self.fresh.iter().any(|range| range.contains(&id))
        }
    
        fn num_fresh_available(&self) -> usize {
            self.available
                .iter()
                .filter(|&&id| self.is_fresh(id))
                .count()
        }
    
        fn num_fresh_all(&self) -> usize {
            self.fresh
                .iter()
                .map(|range| 1 + range.end() - range.start())
                .sum()
        }
    }
    
    fn merge_ranges(mut ranges: Vec<RangeInclusive<Id>>) -> Vec<RangeInclusive<Id>> {
        if ranges.is_empty() {
            return ranges;
        }
        ranges.sort_by_key(|range| *range.start());
    
        let mut merged = Vec::new();
        let mut start = ranges[0].start();
        let mut end = ranges[0].end();
        for range in ranges.iter().skip(1) {
            if range.start() > end {
                merged.push(RangeInclusive::new(*start, *end));
                start = range.start();
                end = range.end();
            }
            if range.end() > end {
                end = range.end();
            }
        }
        merged.push(RangeInclusive::new(*start, *end));
    
        merged
    }
    
    impl FromStr for Ingredients {
        type Err = Report;
    
        fn from_str(s: &str) -> Result<Self, Self::Err> {
            let Some((fresh_str, avail_str)) = s.split_once("\n\n") else {
                bail!("missing divider");
            };
    
            let fresh = fresh_str
                .lines()
                .map(|l| -> Result<RangeInclusive<_>, Self::Err> {
                    let (start, end) = l.split_once('-').ok_or_eyre("bad range")?;
                    let start: Id = start.parse()?;
                    let end: Id = end.parse()?;
                    Ok(start..=end)
                })
                .collect::<Result<_, _>>()?;
    
            let available = avail_str
                .lines()
                .map(|l| l.parse())
                .collect::<Result<_, _>>()?;
    
            Ok(Ingredients {
                fresh: merge_ranges(fresh),
                available,
            })
        }
    }
    
    fn part1(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let ingrd = Ingredients::from_str(&input).unwrap();
        Ok(ingrd.num_fresh_available())
    }
    
    fn part2(filepath: &str) -> Result<usize> {
        let input = fs::read_to_string(filepath)?;
        let ingrd = Ingredients::from_str(&input).unwrap();
        Ok(ingrd.num_fresh_all())
    }
    
    fn main() -> Result<()> {
        color_eyre::install()?;
    
        println!("Part 1: {}", part1("d05/input.txt")?);
        println!("Part 2: {}", part2("d05/input.txt")?);
        Ok(())
    }
    
  • gedhrel@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    7 days ago

    There’s an alternative approach to merging ranges. Put starting and ending points into a heap then scan it, keeping track of the number of open ranges present. Something like this:

    mergeRanges rs =
        let
            -- We put in this order so we count openings before closings
            starts = map (\(a, b) -> (a, -1)) rs
            ends = map (\(a, b) -> (b, 1)) rs
            heap = H.fromList (starts ++ ends) :: H.MinHeap (Int, Int)
         in
            mergeNo heap
      where
        -- not in a range currently
        mergeNo :: H.MinHeap (Int, Int) -> [R]
        mergeNo h =
            case H.view h of
                Nothing -> []
                Just ((a, -1), h') -> mergeYes a 1 h'
        -- in a range
        mergeYes :: Int -> Int -> H.MinHeap (Int, Int) -> [R]
        mergeYes start height h =
            let Just ((v, delta), h') = H.view h
                newHeight = height - delta
             in if newHeight == 0
                    then (start, v) : mergeNo h'
                    else mergeYes start newHeight h'