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

  • 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)
    }