Skip to content

Commit

Permalink
finish day 5
Browse files Browse the repository at this point in the history
  • Loading branch information
devries committed Dec 5, 2023
1 parent 11ae102 commit b137845
Show file tree
Hide file tree
Showing 6 changed files with 410 additions and 2 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Advent of Code 2023

[![Tests](https://github.com/devries/advent_of_code_2023/actions/workflows/main.yml/badge.svg)](https://github.com/devries/advent_of_code_2023/actions/workflows/main.yml)
[![Stars: 8](https://img.shields.io/badge/⭐_Stars-8-yellow)](https://adventofcode.com/2023)
[![Stars: 10](https://img.shields.io/badge/⭐_Stars-10-yellow)](https://adventofcode.com/2023)

## Plan for This Year

Expand Down Expand Up @@ -69,3 +69,15 @@ opportunity to experiment a bit with code generation.
array just had the count minus 1 so that a multiplier of 0 would mean 1 copy.
Everything seems to have worked out in the end, though I still don't have
power, and it's not getting any warmer.

- [Day 5: If You Give A Seed A Fertilizer](https://adventofcode.com/2023/day/5) - [part 1](day05p1/solution.go), [part 2](day05p2/solution.go)

This is the sort of problem where it is prohibitively large to calculate the
conversion of every individual element in a range of integers, however the
given ranges of integers need to be handled in different ways depending on
where they exist within portions of those ranges. Rather then iterate through
each range, the key is finding the subranges which are handled in the same way
and calculate how that range as a whole will be modified. As you continue to
do this the number of ranges grows, but it will always be far fewer
calculations than tracking how each individual element is handled.

106 changes: 106 additions & 0 deletions day05p1/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package day05p1

import (
"io"
"regexp"
"sort"
"strconv"
"strings"

"aoc/utils"
)

func Solve(r io.Reader) any {
lines := utils.ReadLines(r)

// Get seeds from first line
parts := strings.Fields(lines[0])

values := make([]int64, len(parts)-1)

for i := 0; i < len(values); i++ {
v, err := strconv.ParseInt(parts[i+1], 10, 64)
utils.Check(err, "Unable to convert %s to int64", parts[i+1])

values[i] = v
}

// For remaining lines create conversions and convert values
conversions := []Conversion{}

// three positive integers
re := regexp.MustCompile(`\d+\s+\d+\s+\d+`)

for _, ln := range lines[2:] {
switch {
case re.MatchString(ln):
parts = strings.Fields(ln)

components := make([]int64, len(parts))

for i, s := range parts {
var err error
components[i], err = strconv.ParseInt(s, 10, 64)
utils.Check(err, "Unable to convert %s to int64", s)
}
c := Conversion{Start: components[1], End: components[1] + components[2], Delta: components[0] - components[1]}
conversions = append(conversions, c)

case ln == "":
sort.Slice(conversions, func(i, j int) bool { return conversions[i].Start < conversions[j].Start })

for i, v := range values {
delta := getDelta(conversions, v)
values[i] = v + delta
}

conversions = []Conversion{}
}
}

if len(conversions) > 0 {
sort.Slice(conversions, func(i, j int) bool { return conversions[i].Start < conversions[j].Start })

for i, v := range values {
delta := getDelta(conversions, v)
values[i] = v + delta
}
}

min := values[0]

for _, v := range values[1:] {
if v < min {
min = v
}
}

return min
}

type Conversion struct {
Start int64
End int64
Delta int64
}

// Get the delta value by performing a binary search over sorted array of conversions
func getDelta(arr []Conversion, val int64) int64 {
low, high := 0, len(arr)-1

for low <= high {
mid := low + (high-low)/2

if arr[mid].Start <= val && arr[mid].End > val {
return arr[mid].Delta
}

if arr[mid].Start > val {
high = mid - 1
} else if arr[mid].End <= val {
low = mid + 1
}
}

return 0
}
65 changes: 65 additions & 0 deletions day05p1/solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package day05p1

import (
"strings"
"testing"

"aoc/utils"
)

var testInput = `seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4`

func TestSolve(t *testing.T) {
tests := []struct {
input string
answer int64
}{
{testInput, 35},
}

if testing.Verbose() {
utils.Verbose = true
}

for _, test := range tests {
r := strings.NewReader(test.input)

result := Solve(r).(int64)

if result != test.answer {
t.Errorf("Expected %d, got %d", test.answer, result)
}
}
}
160 changes: 160 additions & 0 deletions day05p2/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package day05p2

import (
"io"
"regexp"
"sort"
"strconv"
"strings"

"aoc/utils"
)

func Solve(r io.Reader) any {
lines := utils.ReadLines(r)

// Get seeds from first line
parts := strings.Fields(lines[0])

values := make([]int64, len(parts)-1)

for i := 0; i < len(values); i++ {
v, err := strconv.ParseInt(parts[i+1], 10, 64)
utils.Check(err, "Unable to convert %s to int64", parts[i+1])

values[i] = v
}

// For remaining lines create conversions and convert values
conversions := []Conversion{}

// three positive integers
re := regexp.MustCompile(`\d+\s+\d+\s+\d+`)

for _, ln := range lines[2:] {
switch {
case re.MatchString(ln):
// Build up the conversion array
parts = strings.Fields(ln)

components := make([]int64, len(parts))

for i, s := range parts {
var err error
components[i], err = strconv.ParseInt(s, 10, 64)
utils.Check(err, "Unable to convert %s to int64", s)
}
c := Conversion{Start: components[1], End: components[1] + components[2], Delta: components[0] - components[1]}
conversions = append(conversions, c)

case ln == "":
// Conversion array complete, calculate conversions
sort.Slice(conversions, func(i, j int) bool { return conversions[i].Start < conversions[j].Start })
newvalues := []int64{}

for i := 0; i < len(values); i += 2 {
// For each input value and range we convert to a new value and range.
// If the range is longer than the valid interval of the conversion we split up the range into two intervals
// and then convert the second value and range as well... if that one is longer than the valid interval we repeat
start, length := values[i], values[i+1]

for {
delta, interval := getDeltaInterval(conversions, start)
newvalues = append(newvalues, start+delta)
if length <= interval || interval == 0 { // 0 interval means the rest of the numbers follow that delta
// The length of the input value range is less than the conversion interval
newvalues = append(newvalues, length)
break
} else {
// The length of the input value range is greater than the remaining
// conversion interval, we need to split the solution up into multiple
// ranges
newvalues = append(newvalues, interval)
start = start + interval
length = length - interval
}
}
}
values = newvalues

conversions = []Conversion{}
}
}

if len(conversions) > 0 {
// Do last conversion
sort.Slice(conversions, func(i, j int) bool { return conversions[i].Start < conversions[j].Start })
newvalues := []int64{}

for i := 0; i < len(values); i += 2 {
// For each input value and range we convert to a new value and range.
// If the range is longer than the valid interval of the conversion we split up the range into two intervals
// and then convert the second value and range as well... if that one is longer than the valid interval we repeat
start, length := values[i], values[i+1]

for {
delta, interval := getDeltaInterval(conversions, start)
newvalues = append(newvalues, start+delta)
if length <= interval || interval == 0 { // 0 interval means the rest of the numbers follow that delta
// The length of the input value range is less than the conversion interval
newvalues = append(newvalues, length)
break
} else {
// The length of the input value range is greater than the remaining
// conversion interval, we need to split the solution up into multiple
// ranges
newvalues = append(newvalues, interval)
start = start + interval
length = length - interval
}
}
}

values = newvalues
}

min := values[0]

for i := 2; i < len(values); i += 2 {
if values[i] < min {
min = values[i]
}
}

return min
}

type Conversion struct {
Start int64
End int64
Delta int64
}

// Get the delta value by performing a binary search over sorted array of conversions
// also get the range of subsequent numbers for which it is valid
func getDeltaInterval(arr []Conversion, val int64) (int64, int64) {
low, high := 0, len(arr)-1
var mid int

for low <= high {
mid = low + (high-low)/2

if arr[mid].Start <= val && arr[mid].End > val {
return arr[mid].Delta, arr[mid].End - val
}

if arr[mid].Start > val {
high = mid - 1
} else if arr[mid].End <= val {
low = mid + 1
}
}

if arr[mid].Start > val {
return 0, arr[mid].Start - val
} else if mid < len(arr)-1 {
return 0, arr[mid+1].Start - val
} else {
return 0, 0
}
}
Loading

0 comments on commit b137845

Please sign in to comment.