diff --git a/:w b/:w new file mode 100644 index 0000000..6a54c17 --- /dev/null +++ b/:w @@ -0,0 +1,68 @@ +package day12 + +import ( + "bufio" + "fmt" + "log" + "os" + "strconv" + "strings" +) + +type Row struct { + rawSeq []string + damagedGroups []int +} + +func Solution1(filepath string) int { + m := parseInput(filepath) + printMatrix(m) + return -1 +} + +func Solution2(filepath string) int { + return -1 +} + +func parseInput(filepath string) []Row { + file, err := os.Open(filepath) + if err != nil { + log.Fatalf("Failed to open the input file with: %v\n", err.Error()) + } + + var m []Row + scanner := bufio.NewScanner(file) + for scanner.Scan() { + divInput := strings.Split(scanner.Text(), " ") + seq := strings.Split(divInput[0], "") + dmgGroups := strings.Split(divInput[1], ",") + m = append(m, Row{rawSeq: seq, damagedGroups: parseInts(dmgGroups)}) + } + if err = scanner.Err(); err != nil { + log.Fatalf("Error during input file read: %v\n", err.Error()) + } + + return m +} + +func printRows(rows []Row) { + fmt.Println("-----MATRIX START-----") + for _, row := range rows { + fmt.Println(row) + } + fmt.Println("------MATRIX END-----") +} + +func parseInts(sNums []string) []int { + var nums []int + for _, sNum := range sNums { + num64, err := strconv.ParseInt(sNum, 10, 64) + if err != nil { + log.Fatalf("Failed to parse number %v with: %v\n", sNum, err.Error()) + } + + nums = append(nums, int(num64)) + } + + return nums +} diff --git a/day12/day12.go b/day12/day12.go new file mode 100644 index 0000000..c83f98a --- /dev/null +++ b/day12/day12.go @@ -0,0 +1,196 @@ +package day12 + +import ( + "bufio" + "fmt" + "log" + "os" + "regexp" + "slices" + "strconv" + "strings" +) + +type Row struct { + rawSeq []string + damagedGroups []int +} + +func Solution1(filepath string) int { + rows := parseInput(filepath) + printRows(rows) + + result := 0 + for _, r := range rows { + result += countCombinations(r) + } + return result +} + +func Solution2(filepath string) int { + return -1 +} + +func countCombinations(r Row) int { + unsolvedChunks := getUnsolvedChunkRanges(r.rawSeq) + + // { startIdx: []{all possible substrings of len()=length}, ... } + chunkVars := make(map[int][]string) + for idx, length := range unsolvedChunks { + chunkVars[idx] = getPossibleCombinations(length, "") + } + + // chunkVars to generate full row strings of all potential combinations of . and # in place of ? + var chunksOrder []int + for chunkIdx, _ := range chunkVars { + chunksOrder = append(chunksOrder, chunkIdx) + } + slices.Sort(chunksOrder) + + varStrings := getAllPossibleStrings(strings.Join(r.rawSeq, ""), chunkVars, chunksOrder) + // fmt.Println("ALL STRINGS:") + // for _, vs := range varStrings { + // fmt.Println(vs) + // } + // fmt.Println("--------------") + + // count valid strings + result := 0 + expectedGroups := r.damagedGroups + + for _, v := range varStrings { + dmgRegex := regexp.MustCompile(`#+`) + matches := dmgRegex.FindAllString(v, -1) + var groups []int + for _, m := range matches { + groups = append(groups, len(m)) + } + + if slices.Equal(expectedGroups, groups) { + result += 1 + } + } + + return result +} + +func getUnsolvedChunkRanges(s []string) map[int]int { + // { startIdx: length } + unsolvedChunks := make(map[int]int) + + start := -1 + currentSlice := false + for i, char := range s { + if char == "?" { + if !currentSlice { + currentSlice = true + start = i + } + } else { + if currentSlice { + unsolvedChunks[start] = i - start + currentSlice = false + start = -1 + } + } + } + if currentSlice { + unsolvedChunks[start] = len(s) - start + } + + return unsolvedChunks +} + +func getAllPossibleStrings(baseString string, chunkVariations map[int][]string, chunksOrder []int) []string { + var result []string + + if len(chunksOrder) == 0 { + return []string{baseString} + } + + idx := chunksOrder[0] + for _, v := range chunkVariations[idx] { + newBaseString := "" + if idx > 0 { + newBaseString = baseString[:idx] + } + newBaseString += v + if insertEndIdx := idx + len([]rune(v)); insertEndIdx < len([]rune(baseString)) { + newBaseString += baseString[insertEndIdx:] + } + + var newOrderChunk []int + if len(chunksOrder) > 0 { + newOrderChunk = chunksOrder[1:] + } else { + newOrderChunk = make([]int, 0) + } + result = append( + result, + getAllPossibleStrings(newBaseString, chunkVariations, newOrderChunk)..., + ) + } + return result +} + +func getPossibleCombinations(length int, currentComb string) []string { + var combs []string + + if length < 0 { + log.Fatalln("bruh") + } + + if length == 1 { + combA := currentComb + "." + combB := currentComb + "#" + return []string{combA, combB} + } + + combs = append(combs, getPossibleCombinations(length-1, currentComb+".")...) + combs = append(combs, getPossibleCombinations(length-1, currentComb+"#")...) + + return combs +} + +func parseInput(filepath string) []Row { + file, err := os.Open(filepath) + if err != nil { + log.Fatalf("Failed to open the input file with: %v\n", err.Error()) + } + + var m []Row + scanner := bufio.NewScanner(file) + for scanner.Scan() { + divInput := strings.Split(scanner.Text(), " ") + seq := strings.Split(divInput[0], "") + dmgGroups := strings.Split(divInput[1], ",") + m = append(m, Row{rawSeq: seq, damagedGroups: parseInts(dmgGroups)}) + } + if err = scanner.Err(); err != nil { + log.Fatalf("Error during input file read: %v\n", err.Error()) + } + + return m +} + +func printRows(rows []Row) { + fmt.Println("-----MATRIX START-----") + for i, row := range rows { + fmt.Printf("Row %d: %v | %v\n", i, strings.Join(row.rawSeq, ""), row.damagedGroups) + } + fmt.Println("------MATRIX END-----") +} + +func parseInts(sNums []string) []int { + var nums []int + for _, sNum := range sNums { + num64, err := strconv.ParseInt(sNum, 10, 64) + if err != nil { + log.Fatalf("Failed to parse number %v with: %v\n", sNum, err.Error()) + } + + nums = append(nums, int(num64)) + } + + return nums +} diff --git a/day12/day12_test.go b/day12/day12_test.go new file mode 100644 index 0000000..22b42da --- /dev/null +++ b/day12/day12_test.go @@ -0,0 +1,97 @@ +package day12 + +import ( + "fmt" + "maps" + "slices" + "strings" + "testing" +) + +func TestSolutio1(t *testing.T) { + cases := map[string]int{ + "test_input12": 6, + "test_input11": 21, + } + + for input, expectedResult := range cases { + result := Solution1(input) + + if result == expectedResult { + fmt.Printf("Solution1()=%d, OK\n", result) + } else { + t.Fatalf("Solution1()=%d, expecting %d, FAIL\n", result, expectedResult) + } + } +} + +func TestGetAllPossibleStrings(t *testing.T) { + input := "?..?##.?" + expectedResult := []string{ + "#..###.#", + "#..###..", + "#...##.#", + "#...##..", + "...###.#", + "...###..", + "....##.#", + "....##..", + } + chunkVars := map[int][]string{ + 0: {".", "#"}, + 3: {".", "#"}, + 7: {".", "#"}, + } + + result := getAllPossibleStrings(input, chunkVars, []int{0, 3, 7}) + slices.Sort(expectedResult) + slices.Sort(result) + if slices.Equal(result, expectedResult) { + fmt.Printf("getAllPossibleStrings()=%v; OK\n", result) + } else { + t.Fatalf("getAllPossibleStrings()=%v, expecting %v; FAIL\n", result, expectedResult) + } +} + +func TestGetAllPossibleCombinations(t *testing.T) { + cases := map[int][]string{ + 1: {".", "#"}, + 2: { + "..", + "#.", + ".#", + "##", + }, + } + + for input, expectedResult := range cases { + result := getPossibleCombinations(input, "") + slices.Sort(result) + slices.Sort(expectedResult) + + if slices.Equal(result, expectedResult) { + fmt.Printf("getPossibleCombinations()=%v, OK\n", result) + } else { + t.Fatalf("getPossibleCombinations()=%v, expecting %v, FAIL\n", result, expectedResult) + } + } +} + +func TestGetAllUnsolvedChunkRanges(t *testing.T) { + cases := map[string]map[int]int{ + "???.###": {0: 3}, + ".??..??...?##.": {1: 2, 5: 2, 10: 1}, + "?#?#?": {0: 1, 2: 1, 4: 1}, + "?###???????": {0: 1, 4: 7}, + } + + for input, expectedResult := range cases { + result := getUnsolvedChunkRanges(strings.Split(input, "")) + + if maps.Equal(result, expectedResult) { + fmt.Printf("getUnsolvedChunkRanges()=%v; OK\n", result) + } else { + t.Fatalf("getUnsolvedChunkRanges()=%v, expecting %v; FAIL\n", result, expectedResult) + } + } +} diff --git a/day12/test_input11 b/day12/test_input11 new file mode 100644 index 0000000..e925935 --- /dev/null +++ b/day12/test_input11 @@ -0,0 +1,6 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 +????.#...#... 4,1,1 +????.######..#####. 1,6,5 +?###???????? 3,2,1 diff --git a/day12/test_input12 b/day12/test_input12 new file mode 100644 index 0000000..2edfedb --- /dev/null +++ b/day12/test_input12 @@ -0,0 +1,3 @@ +???.### 1,1,3 +.??..??...?##. 1,1,3 +?#?#?#?#?#?#?#? 1,3,1,6 diff --git a/main.go b/main.go index 80dc4d4..b6a1e55 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ import ( "advent-of-code/day09" "advent-of-code/day10" "advent-of-code/day11" + "advent-of-code/day12" "fmt" ) @@ -48,4 +49,7 @@ func main() { fmt.Printf("Day 11, solution 1: %v\n", day11.Solution1("inputs/day11")) fmt.Printf("Day 11, solution 2: %v\n", day11.Solution2("inputs/day11")) + + fmt.Printf("Day 12, solution 1: %v\n", day12.Solution1("inputs/day12")) + fmt.Printf("Day 12, solution 2: %v\n", day12.Solution2("inputs/day12")) }