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

you are viewing a single comment's thread
view the rest of the comments
[โ€“] 2 points 11 months 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)
}

  • source