diff --git a/internal/2023/day_04/in.go b/internal/2023/day_04/in.go index 5216173..bafe420 100644 --- a/internal/2023/day_04/in.go +++ b/internal/2023/day_04/in.go @@ -1,11 +1,98 @@ package day_04 +import ( + "fmt" + "regexp" + "strings" + + "git.rpjosh.de/RPJosh/go-logger" +) + type Day struct{} func (d *Day) Part1(in string) string { - return "" + + // Regex to get all numbers + numberRegex := regexp.MustCompile(`\w*`) + + sum := 0 + for _, val := range strings.Split(in, "\n") { + if val == "" { + continue + } + + // Extract card numbers + numbers := strings.Split(val, ":")[1] + winning := strings.Split(numbers, "|")[0] + elfs := strings.Split(numbers, "|")[1] + + // Loop through all numbers + points := 0 + for _, number := range numberRegex.FindAllString(elfs, -1) { + if number == "" { + continue + } + + if strings.Contains(winning, " "+number+" ") { + logger.Debug("Found matching number: %q (%s)", number, winning) + + if points == 0 { + points = 1 + } else { + points *= 2 + } + } + } + + sum += points + } + + return fmt.Sprintf("%d", sum) } func (d *Day) Part2(in string) string { - return "" + // Regex to get all numbers + numberRegex := regexp.MustCompile(`\w*`) + + cards := 0 + numberOfCards := make(map[int]int, 0) + for i, val := range strings.Split(in, "\n") { + if val == "" { + continue + } + + // Increment cards count + cards++ + replicas := 0 + if val, exists := numberOfCards[i]; exists { + logger.Info("Cards of game %d: %d", i+1, val) + cards += val + replicas = val + } + + // Extract card numbers + numbers := strings.Split(val, ":")[1] + winning := strings.Split(numbers, "|")[0] + elfs := strings.Split(numbers, "|")[1] + + // Loop through all numbers + points := 0 + for _, number := range numberRegex.FindAllString(elfs, -1) { + if number == "" { + continue + } + + if strings.Contains(winning, " "+number+" ") { + logger.Debug("Found matching number: %q (%s)", number, winning) + + // Save number of cards + points++ + numberOfCards[i+points] += 1 + // Each card wins that also + numberOfCards[i+points] += replicas + } + } + } + + return fmt.Sprintf("%d", cards) } diff --git a/internal/2023/day_05/in.go b/internal/2023/day_05/in.go index 7f45e3c..d96b8e7 100644 --- a/internal/2023/day_05/in.go +++ b/internal/2023/day_05/in.go @@ -1,11 +1,165 @@ package day_05 +import ( + "fmt" + "math" + "regexp" + "strings" + "sync" + + "git.rpjosh.de/RPJosh/go-logger" + "rpjosh.de/adventOfCode/pkg/utils" +) + type Day struct{} func (d *Day) Part1(in string) string { - return "" + + // Regex to get all numbers + numberRegex := regexp.MustCompile(`\d+`) + inSplit := strings.Split(in, "\n") + + // Contains the previous values of the last "section" + lastValues := utils.ConvertArrayToInt(numberRegex.FindAllString(inSplit[0], -1)) + + // Copy last values for each mapping. + // It could be possible that an element is persent + // in multiple mapping values + lastValuesCurrMapp := make([]int, len(lastValues)) + copy(lastValuesCurrMapp, lastValues) + + for _, val := range inSplit[1:] { + // Get numbers of input + numbersString := numberRegex.FindAllString(val, -1) + numbers := utils.ConvertArrayToInt(numbersString) + + // Re-Initialize values + if val == "" { + copy(lastValues, lastValuesCurrMapp) + } + + // Don't do anything with invalid lines + if len(numbers) <= 2 { + continue + } + + // Try to map the values + startDest := numbers[0] + startSource := numbers[1] + length := numbers[2] + logger.Debug("Found values %d %d %d", startDest, startSource, length) + + // Try to find the value in lastValues + for v, val := range lastValues { + if val >= startSource && val < startSource+length { + diff := val - startSource + lastValuesCurrMapp[v] = startDest + diff + } + } + } + + return fmt.Sprintf("%d", utils.GetMinValue(lastValues)) } func (d *Day) Part2(in string) string { - return "" + // Regex to get all numbers + numberRegex := regexp.MustCompile(`\d+`) + inSplit := strings.Split(in, "\n") + offset := 0 + + // Multi thread + minValue := math.MaxInt32 + var wg sync.WaitGroup + var mtx sync.Mutex + + // Parse mapping once + mapping := make([][]int, 0) + for _, val := range inSplit[1:] { + // Get numbers of input + numbersString := numberRegex.FindAllString(val, -1) + numbers := utils.ConvertArrayToInt(numbersString) + + if len(numbers) <= 2 && val != "" { + continue + } + + mapping = append(mapping, numbers) + } + + // Unfold paris of seed values + seeds := utils.ConvertArrayToInt(numberRegex.FindAllString(inSplit[offset], -1)) + for i := 0; i+1 < len(seeds); i += 2 { + wg.Add(1) + go func(i int) { + lastMin := seeds[i] + for m := seeds[i]; m < seeds[i]+seeds[i+1]; m++ { + if m%5000000 == 0 { + logger.Debug("%d. Parsing %d", i, m) + } + + // Bulk + if m%100000 == 0 || m == seeds[i]+seeds[i+1]-1 { + wg.Add(1) + go func(min, max int) { + rtc := d.ParseSingleFor2(min, max, &mapping) + mtx.Lock() + if rtc < minValue { + minValue = rtc + } + mtx.Unlock() + wg.Done() + }(lastMin, m) + lastMin = m + } + + } + logger.Info("Finished %d", i) + wg.Done() + }(i) + + // Limit max number of goroutines + if i != 0 && i%10 == 0 { + wg.Wait() + } + } + wg.Wait() + + return fmt.Sprintf("%d", minValue) +} + +func (d *Day) ParseSingleFor2(min int, max int, values *[][]int) int { + + lastValues := make([]int, max-min) + for i := min; i < max; i++ { + lastValues[i-min] = i + } + + // Copy last values for each mapping. + // It could be possible that an element is persent + // in multiple mapping values + lastValuesCurrMapp := make([]int, len(lastValues)) + copy(lastValuesCurrMapp, lastValues) + + for _, val := range *values { + // Re-Initialize values + if len(val) == 0 { + copy(lastValues, lastValuesCurrMapp) + continue + } + + // Try to map the values + startDest := val[0] + startSource := val[1] + length := val[2] + + // Try to find the value in lastValues + for v, val := range lastValues { + if val >= startSource && val < startSource+length { + diff := val - startSource + lastValuesCurrMapp[v] = startDest + diff + } + } + } + + return utils.GetMinValue(lastValues) } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 8d9f5d2..546fce8 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -99,3 +99,39 @@ func IsInt(val string) bool { _, err := strconv.Atoi(val) return err == nil } + +// GetMinValue returns the smallest number +// within the given array +func GetMinValue(values []int) int { + if len(values) == 0 { + return 0 + } + + min := values[0] + for _, val := range values { + if val < min { + min = val + } + } + + return min +} + +// ConvertToInt converts each number within +// []string to a number and returns the resulting array. +// Empty string values are ignored +func ConvertArrayToInt(values []string) []int { + rtc := make([]int, 0) + + // Convert each value + for _, nmbString := range values { + if nmbString == "" { + continue + } + + nmb := ToInt(nmbString) + rtc = append(rtc, nmb) + } + + return rtc +}