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

  • gedhrel@lemmy.world
    link
    fedilink
    arrow-up
    2
    ·
    3 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'
    
  • mykl@lemmy.world
    link
    fedilink
    arrow-up
    4
    ·
    5 days ago

    Uiua

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

    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"
    
    Merge  (
      (        # -> distinct, keep both.
      | ⊂⊢⟜(↥∩⊣) # -> overlap, merge them.
      )(≤⊓⊣⊢)
    | ⊙◌ # -> inside, ignore it.
    )(≤∩⊣)
    
    Ranges  ⊙◌⍥⍜⊣(Merge⊙°⊂)◡⋅⧻⊃↙↘1⍆↯∞_2 # Merge pairs of ranges.
    
    P₁  /+≡⌞(/↥≡⌟(↧⊓⌟≥≤°⊟))
    P₂  /+≡(+1/-)⊙◌
    P₁ P₂ Ranges Parse D
    
  • EnEnCode@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    5 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
    }
    
  • ystael@beehaw.org
    link
    fedilink
    arrow-up
    2
    ·
    5 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))))))
    
  • eco_game@discuss.tchncs.de
    link
    fedilink
    arrow-up
    2
    ·
    5 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
      ·
      4 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
      
  • Camille@lemmy.ml
    link
    fedilink
    arrow-up
    2
    ·
    5 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)
    }
    
  • Gobbel2000@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    5 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!();
    
  • Deebster@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    5 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(())
    }
    
  • Strlcpy@1@lemmy.sdf.org
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    5 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);
    }
    
  • Amy@piefed.blahaj.zone
    link
    fedilink
    English
    arrow-up
    2
    ·
    5 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  
    
  • CameronDev@programming.devOPM
    link
    fedilink
    arrow-up
    1
    ·
    5 days ago

    Rust

       #[test]
        fn test_y2025_day5_part2() {
            let input = std::fs::read_to_string("input/2025/day_5.txt").unwrap();
            let (fresh, _) = input.split_once("\n\n").unwrap();
            let mut fresh = fresh
                .lines()
                .map(|l| {
                    let (p1, p2) = l.split_once("-").unwrap();
                    (p1.parse::<usize>().unwrap(), p2.parse::<usize>().unwrap())
                })
                .collect::<Vec<(usize, usize)>>();
    
            fresh.sort_by_key(|a| a.0);
    
            let mut non_overlapping = vec![*fresh.first().unwrap()];
    
            for range in fresh[1..].iter() {
                let last = *non_overlapping.last().unwrap();
                if range.0 > last.1 {
                    // println!("Non overlapping: {range:?} -> {last:?}");
                    non_overlapping.push((range.0, range.1));
                    continue;
                }
                if range.0 <= last.1 && range.1 > last.1 {
                    // println!("Overlapping: {range:?} -> {last:?}");
                    let new_last = (last.0, range.1);
                    non_overlapping.pop();
                    non_overlapping.push(new_last);
                    continue;
                }
                // println!("{range:?} is entirely within {last:?}");
            }
    
            let mut count = 0;
            for r in &non_overlapping {
                count += r.1 - r.0 + 1;
            }
            assert_eq!(count, 352556672963116);
            println!("{}", count);
        }
    

    Took me way longer than it should have to work out pt2, got tripped up by a < instead of <=.

  • tranzystorekk@beehaw.org
    link
    fedilink
    English
    arrow-up
    1
    ·
    5 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

  • Pyro@programming.dev
    link
    fedilink
    arrow-up
    1
    ·
    5 days ago

    Python

    Not too bad though part2 could become difficult if you don’t realize the simple method to merge overlapping intervals.

    see code
    # takes a list of (start, end) ranges and merges overlapping ones
    def merge_overlapping_ranges(ranges: list[tuple[int, int]]) -> list[tuple[int, int]]:
        # sort ranges by start so that overlaps are adjacent
        ranges.sort(key=lambda r: r[0])
        merged_ranges = []
    
        # keep track of a current range that could be merged into
        curr_start, curr_end = ranges[0]
        for start, end in ranges:
            if curr_start <= start <= curr_end:
                # overlap, extend current range
                curr_end = max(curr_end, end)
            else:
                # no overlap, save current range and record the new one
                merged_ranges.append((curr_start, curr_end))
                curr_start, curr_end = start, end
        # finally, don't forget to add the last range that's being tracked
        merged_ranges.append((curr_start, curr_end))
    
        return merged_ranges
    
    def part1(data: str):
        # split input
        freshness_data, ingredients_data = data.split("\n\n")
    
        # parse data
        freshness_ranges = [tuple(map(int, f.split('-'))) for f in freshness_data.splitlines()]
        freshness_ranges = merge_overlapping_ranges(freshness_ranges)   # optimization: merge overlapping ranges
        ingredients = [int(i) for i in ingredients_data.splitlines()]
    
        # checks if an ingredient is fresh
        # this could've been done with binary search, but linear search is simpler and fast enough
        def is_fresh(ingredient: int) -> bool:
            for start, end in freshness_ranges:
                if start <= ingredient <= end:
                    # found
                    return True
                if start > ingredient:
                    # quit early since we are past any range that could've contained the ingredient
                    return False
            return False
    
        # count all fresh ingredients
        return sum(is_fresh(i) for i in ingredients)
    
    def part2(data: str):
        # split input
        freshness_data, _ = data.split("\n\n")
    
        # parse data
        freshness_ranges = [tuple(map(int, f.split('-'))) for f in freshness_data.splitlines()]
        freshness_ranges = merge_overlapping_ranges(freshness_ranges)   # merge overlapping ranges
    
        # return total count of fresh ingredients
        # we add +1 because both start and end are inclusive
        return sum(end - start + 1 for start, end in freshness_ranges)
    
    sample = """3-5
    10-14
    16-20
    12-18
    
    1
    5
    8
    11
    17
    32"""
    assert part1(sample) == 3
    assert part2(sample) == 14
    
  • janAkali@lemmy.sdf.org
    link
    fedilink
    arrow-up
    1
    ·
    edit-2
    5 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

  • Zikeji@programming.dev
    link
    fedilink
    English
    arrow-up
    1
    ·
    5 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}`);