From 14a16819c545553a66453651e290ec281b1cc36c Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Sun, 7 Dec 2025 19:51:21 +0000 Subject: [PATCH] solve five --- 2025/five/five.go | 112 ++++++++++++++++++++++++++++++----------- 2025/five/five_test.go | 67 ++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 30 deletions(-) create mode 100644 2025/five/five_test.go diff --git a/2025/five/five.go b/2025/five/five.go index 4fba271..69f3ec7 100644 --- a/2025/five/five.go +++ b/2025/five/five.go @@ -13,49 +13,108 @@ type freshRange struct { start, end int } +type freshDB struct { + ranges []freshRange +} + func (r *freshRange) contains(c int) bool { return c >= r.start && c <= r.end } func (r *freshRange) nInRange() int { - return (r.end - r.start) + 1 + total := (r.end - r.start) + 1 + return total } +/* Check for an error, panic if there is */ func check(err error) { if err != nil { panic(err) } } -func getRanges(scanner *bufio.Scanner) (ranges []freshRange, err error) { - ranges = make([]freshRange, 0, 5) +func dbFromSlice(ranges []int) (db freshDB, err error) { + if len(ranges)%2 != 0 { + return freshDB{}, errors.New("Input slice not of even length") + } + + db.ranges = make([]freshRange, 0, len(ranges)/2) + + for i := 0; i < len(ranges); i += 2 { + db.ranges = append(db.ranges, freshRange{ranges[i], ranges[i+1]}) + } + return db, nil +} + +/* Parse ranges out of a bufio.Scanner */ +func parse(scanner *bufio.Scanner) (db freshDB, err error) { + db.ranges = make([]freshRange, 0, 5) for scanner.Scan() { text := scanner.Text() if text == "" { - return ranges, nil + return db, nil } startEnd := strings.Split(text, "-") if len(startEnd) != 2 { - return nil, errors.New("Fresh range does not contain exactly two numbers") + return db, errors.New("Fresh range does not contain exactly two numbers") } start, err := strconv.Atoi(startEnd[0]) if err != nil { - return nil, err + return db, err } end, err := strconv.Atoi(startEnd[1]) if err != nil { - return nil, err + return db, err } - ranges = append(ranges, freshRange{start, end}) + db.ranges = append(db.ranges, freshRange{start, end}) } - return nil, errors.New("No empty line in file to delimit fresh ranges") + return db, errors.New("No empty line in file to delimit fresh ranges") +} + +func (db *freshDB) insert(toinsert freshRange) { + for i := range db.ranges { + r := &db.ranges[i] + if r.contains(toinsert.start) { + if toinsert.end > r.end { + r.end = toinsert.end + } + return + } + + if r.contains(toinsert.end) { + if toinsert.start < r.start { + r.start = toinsert.start + } + return + } + + if toinsert.contains(r.start) && toinsert.contains(r.end) { + r.start = toinsert.start + r.end = toinsert.end + return + } + } + db.ranges = append(db.ranges, toinsert) +} + +/* Collapse a list of fresh ingredient ranges to avoid overlaps */ +func (db *freshDB) collapse() (before int, after int) { + before = len(db.ranges) + collapsed := freshDB{make([]freshRange, 0, len(db.ranges))} + + for _, r := range db.ranges { + collapsed.insert(r) + } + db.ranges = collapsed.ranges + after = len(db.ranges) + return before, after } func main() { @@ -65,37 +124,30 @@ func main() { defer file.Close() scanner := bufio.NewScanner(file) - ranges, err := getRanges(scanner) + db, err := parse(scanner) check(err) - authRanges := make([]freshRange, 1, len(ranges)) - authRanges[0] = ranges[0] + nRaw := len(db.ranges) -RANGE: - for _, r := range ranges { + for { + before, after := db.collapse() - for _, authR := range authRanges { - if authR.contains(r.start) { - if r.end > authR.end { - authR.end = r.end - } - continue RANGE - } + fmt.Printf("Collapsed %d to %d ranges\n", before, after) - if authR.contains(r.end) { - if r.start < authR.start { - authR.start = r.start - } - continue RANGE - } + if before == after { + break } - authRanges = append(authRanges, r) } potentialNFresh := 0 - for _, r := range authRanges { + for _, r := range db.ranges { potentialNFresh += r.nInRange() } - fmt.Printf("N raw %d and consolidated %d. %d potential fresh ingredients.\n", len(ranges), len(authRanges), potentialNFresh) + fmt.Printf( + "N raw %d and consolidated %d. %d potential fresh ingredients.\n", + nRaw, + len(db.ranges), + potentialNFresh, + ) } diff --git a/2025/five/five_test.go b/2025/five/five_test.go new file mode 100644 index 0000000..4fbefac --- /dev/null +++ b/2025/five/five_test.go @@ -0,0 +1,67 @@ +package main + +import "testing" + +type insertTest struct { + ranges []int + toinsert freshRange + expected []int +} + +var insertTests = []insertTest{ + {[]int{1, 3}, freshRange{4, 10}, []int{1, 3, 4, 10}}, + {[]int{1, 5}, freshRange{4, 10}, []int{1, 10}}, +} + +func TestInsert(t *testing.T) { + for _, test := range insertTests { + inDB, _ := dbFromSlice(test.ranges) + expectedDB, _ := dbFromSlice(test.expected) + inDB.insert(test.toinsert) + + if len(inDB.ranges) != len(expectedDB.ranges) { + t.Errorf("%+v != %+v", expectedDB, inDB) + continue + } + + for i := range inDB.ranges { + if inDB.ranges[i] != expectedDB.ranges[i] { + t.Errorf("%v != %v", expectedDB, inDB) + } + } + } +} + +type collapseTest struct { + ranges []int + expected []int +} + +var collapseTests = []collapseTest{ + {[]int{1, 3, 4, 10}, []int{1, 3, 4, 10}}, + {[]int{1, 3, 4, 10, 2, 5}, []int{1, 5, 4, 10}}, + {[]int{1, 5, 4, 10}, []int{1, 10}}, + {[]int{1, 5, 5, 10}, []int{1, 10}}, + {[]int{2, 5, 1, 10}, []int{1, 10}}, + {[]int{1, 5, 2, 3}, []int{1, 5}}, +} + +func TestCollapse(t *testing.T) { + for _, test := range collapseTests { + inDB, _ := dbFromSlice(test.ranges) + expectedDB, _ := dbFromSlice(test.expected) + + inDB.collapse() + + if len(inDB.ranges) != len(expectedDB.ranges) { + t.Errorf("%v != %v", expectedDB.ranges, inDB.ranges) + continue + } + + for i := range inDB.ranges { + if inDB.ranges[i] != expectedDB.ranges[i] { + t.Errorf("%v != %v", expectedDB.ranges, inDB.ranges) + } + } + } +}