Browse Source

Day 17: exhaustively search for a solution

master
Chris Smith 4 years ago
parent
commit
8d2c5768db
Signed by: Chris Smith <chris@chameth.com> GPG Key ID: 3A2D4BBDC4A3C9A9
3 changed files with 209 additions and 62 deletions
  1. 89
    0
      17/compress.go
  2. 112
    0
      17/compress_test.go
  3. 8
    62
      17/main.go

+ 89
- 0
17/compress.go View File

@@ -0,0 +1,89 @@
1
+package main
2
+
3
+import "strings"
4
+
5
+func compress(parts []string) (main, a, b, c string) {
6
+	for _, candidateA := range prefixes(parts) {
7
+		remainderA := replace(parts, candidateA, "A")
8
+		for _, candidateB := range prefixes(remainderA) {
9
+			remainderB := replace(remainderA, candidateB, "B")
10
+			for _, candidateC := range prefixes(remainderB) {
11
+				remainderC := replace(remainderB, candidateC, "C")
12
+				if acceptableRoutine(remainderC) {
13
+					return strings.Join(remainderC, ","), strings.Join(candidateA, ","), strings.Join(candidateB, ","), strings.Join(candidateC, ",")
14
+				}
15
+			}
16
+		}
17
+	}
18
+
19
+	return
20
+}
21
+
22
+func replace(parts []string, function []string, name string) []string {
23
+	var res []string
24
+	for start := 0; start < len(parts); start++ {
25
+		if start+len(function) <= len(parts) && equals(parts[start:start+len(function)], function) {
26
+			res = append(res, name)
27
+			start += len(function) - 1
28
+		} else {
29
+			res = append(res, parts[start])
30
+		}
31
+	}
32
+	return res
33
+}
34
+
35
+func equals(a, b []string) bool {
36
+	for i, p := range a {
37
+		if b[i] != p {
38
+			return false
39
+		}
40
+	}
41
+	return true
42
+}
43
+
44
+func prefixes(parts []string) [][]string {
45
+	start := 0
46
+	for start < len(parts) && (parts[start] == "A" || parts[start] == "B") {
47
+		start++
48
+	}
49
+	if start == len(parts) {
50
+		return nil
51
+	}
52
+
53
+	var res [][]string
54
+	for end := start + 1; end <= len(parts) && acceptableFunction(parts[start:end]); end++ {
55
+		res = append(res, parts[start:end])
56
+	}
57
+
58
+	// Reverse the slice so the longest element is first
59
+	for i, j := 0, len(res)-1; i < j; i, j = i+1, j-1 {
60
+		res[i], res[j] = res[j], res[i]
61
+	}
62
+
63
+	return res
64
+}
65
+
66
+func acceptableRoutine(parts []string) bool {
67
+	for _, p := range parts {
68
+		if p != "A" && p != "B" && p != "C" {
69
+			return false
70
+		}
71
+	}
72
+	return true
73
+}
74
+
75
+func acceptableFunction(parts []string) bool {
76
+	length := 0
77
+	for i, part := range parts {
78
+		if i > 0 {
79
+			length++
80
+		}
81
+		length += len(part)
82
+
83
+		if strings.ContainsAny(part, "ABC") || length > 20 {
84
+			return false
85
+		}
86
+	}
87
+
88
+	return true
89
+}

+ 112
- 0
17/compress_test.go View File

@@ -0,0 +1,112 @@
1
+package main
2
+
3
+import (
4
+	"reflect"
5
+	"strings"
6
+	"testing"
7
+)
8
+
9
+func Test_replace(t *testing.T) {
10
+	type args struct {
11
+		parts    []string
12
+		function []string
13
+		name     string
14
+	}
15
+	tests := []struct {
16
+		name string
17
+		args args
18
+		want []string
19
+	}{
20
+		{"replace entire set", args{[]string{"R", "1", "L", "2"}, []string{"R", "1", "L", "2"}, "A"}, []string{"A"}},
21
+		{"replace prefix", args{[]string{"R", "1", "L", "2"}, []string{"R", "1", "L"}, "A"}, []string{"A", "2"}},
22
+		{"replace middle", args{[]string{"R", "1", "L", "2"}, []string{"1", "L"}, "A"}, []string{"R", "A", "2"}},
23
+		{"replace suffix", args{[]string{"R", "1", "L", "2"}, []string{"L", "2"}, "A"}, []string{"R", "1", "A"}},
24
+		{"replace multi", args{[]string{"R", "1", "L", "R", "1", "2", "R", "1"}, []string{"R", "1"}, "A"}, []string{"A", "L", "A", "2", "A"}},
25
+		{"replace adjacent", args{[]string{"R", "1", "R", "1"}, []string{"R", "1"}, "A"}, []string{"A", "A"}},
26
+	}
27
+	for _, tt := range tests {
28
+		t.Run(tt.name, func(t *testing.T) {
29
+			if got := replace(tt.args.parts, tt.args.function, tt.args.name); !reflect.DeepEqual(got, tt.want) {
30
+				t.Errorf("replace() = %v, want %v", got, tt.want)
31
+			}
32
+		})
33
+	}
34
+}
35
+
36
+func Test_compress(t *testing.T) {
37
+	tests := []struct {
38
+		name     string
39
+		args     string
40
+		wantMain string
41
+		wantA    string
42
+		wantB    string
43
+		wantC    string
44
+	}{
45
+		{
46
+			name:     "simple",
47
+			args:     "R,L,1",
48
+			wantMain: "A,B,C",
49
+			wantA:    "R",
50
+			wantB:    "L",
51
+			wantC:    "1",
52
+		},
53
+		{
54
+			name:     "reddit1",
55
+			args:     "L,12,R,4,R,4,L,6,L,12,R,4,R,4,R,12,L,12,R,4,R,4,L,6,L,10,L,6,R,4,L,12,R,4,R,4,L,6,L,12,R,4,R,4,R,12,L,10,L,6,R,4,L,12,R,4,R,4,R,12,L,10,L,6,R,4,L,12,R,4,R,4,L,6",
56
+			wantMain: "A,B,A,C,A,B,C,B,C,A",
57
+			wantA:    "L,12,R,4,R,4,L,6",
58
+			wantB:    "L,12,R,4,R,4,R,12",
59
+			wantC:    "L,10,L,6,R,4",
60
+		},
61
+		{
62
+			name:     "reddit2",
63
+			args:     "L,6,R,12,L,4,L,6,R,6,L,6,R,12,R,6,L,6,R,12,L,6,L,10,L,10,R,6,L,6,R,12,L,4,L,6,R,6,L,6,R,12,L,6,L,10,L,10,R,6,L,6,R,12,L,4,L,6,R,6,L,6,R,12,L,6,L,10,L,10,R,6",
64
+			wantMain: "A,B,B,C,A,B,C,A,B,C",
65
+			wantA:    "L,6,R,12,L,4,L,6",
66
+			wantB:    "R,6,L,6,R,12",
67
+			wantC:    "L,6,L,10,L,10,R,6",
68
+		},
69
+		{
70
+			name:     "reddit3",
71
+			args:     "L,12,L,12,R,4,R,10,R,6,R,4,R,4,L,12,L,12,R,4,R,6,L,12,L,12,R,10,R,6,R,4,R,4,L,12,L,12,R,4,R,10,R,6,R,4,R,4,R,6,L,12,L,12,R,6,L,12,L,12,R,10,R,6,R,4,R,4",
72
+			wantMain: "A,B,A,C,B,A,B,C,C,B",
73
+			wantA:    "L,12,L,12,R,4",
74
+			wantB:    "R,10,R,6,R,4,R,4",
75
+			wantC:    "R,6,L,12,L,12",
76
+		},
77
+	}
78
+	for _, tt := range tests {
79
+		t.Run(tt.name, func(t *testing.T) {
80
+			gotMain, gotA, gotB, gotC := compress(strings.Split(tt.args, ","))
81
+			if gotMain != tt.wantMain {
82
+				t.Errorf("compress() gotMain = %v, want %v", gotMain, tt.wantMain)
83
+			}
84
+			if gotA != tt.wantA {
85
+				t.Errorf("compress() gotA = %v, want %v", gotA, tt.wantA)
86
+			}
87
+			if gotB != tt.wantB {
88
+				t.Errorf("compress() gotB = %v, want %v", gotB, tt.wantB)
89
+			}
90
+			if gotC != tt.wantC {
91
+				t.Errorf("compress() gotC = %v, want %v", gotC, tt.wantC)
92
+			}
93
+		})
94
+	}
95
+}
96
+
97
+func Test_prefixes(t *testing.T) {
98
+	tests := []struct {
99
+		name string
100
+		args []string
101
+		want [][]string
102
+	}{
103
+		{"simple", []string{"1", "2", "3"}, [][]string{{"1", "2", "3"}, {"1", "2"}, {"1"}}},
104
+	}
105
+	for _, tt := range tests {
106
+		t.Run(tt.name, func(t *testing.T) {
107
+			if got := prefixes(tt.args); !reflect.DeepEqual(got, tt.want) {
108
+				t.Errorf("prefixes() = %v, want %v", got, tt.want)
109
+			}
110
+		})
111
+	}
112
+}

+ 8
- 62
17/main.go View File

@@ -5,7 +5,6 @@ import (
5 5
 	"github.com/csmith/aoc-2019/common"
6 6
 	"github.com/csmith/aoc-2019/intcode"
7 7
 	"strconv"
8
-	"strings"
9 8
 )
10 9
 
11 10
 const (
@@ -65,53 +64,6 @@ func follow(picture [][]rune, x, y int, direction rune) (int, int, int) {
65 64
 	return x, y, length
66 65
 }
67 66
 
68
-func compactPass(movement string) string {
69
-	start := 0
70
-	for movement[start] == 'A' || movement[start] == 'B' {
71
-		start += 2
72
-	}
73
-
74
-	end := start + 4
75
-	for movement[end] != ',' {
76
-		end++
77
-	}
78
-
79
-	count := strings.Count(movement, movement[start:end])
80
-
81
-	for {
82
-		newEnd := end + 1
83
-		for movement[newEnd] != ',' {
84
-			newEnd++
85
-		}
86
-		if movement[newEnd-1] == 'A' || movement[newEnd-1] == 'B' {
87
-			break
88
-		}
89
-
90
-		newCount := strings.Count(movement, movement[start:end])
91
-		if newCount == count {
92
-			end = newEnd
93
-		} else {
94
-			break
95
-		}
96
-	}
97
-
98
-	return movement[start:end]
99
-}
100
-
101
-func compact(movement string) (main, a, b, c string) {
102
-	main = movement
103
-
104
-	a = compactPass(main)
105
-	main = strings.ReplaceAll(main, a, "A")
106
-
107
-	b = compactPass(main)
108
-	main = strings.ReplaceAll(main, b, "B")
109
-
110
-	c = compactPass(main)
111
-	main = strings.ReplaceAll(main, c, "C")
112
-	return
113
-}
114
-
115 67
 func readPicture(memory []int) [][]rune {
116 68
 	vm := intcode.NewVirtualMachine(memory)
117 69
 	vm.Input = make(chan int, 1)
@@ -158,11 +110,11 @@ func analysePicture(picture [][]rune) (sum int, robot rune, robotX, robotY int)
158 110
 	return
159 111
 }
160 112
 
161
-func buildRoute(picture [][]rune, robot rune, robotX, robotY int) string {
113
+func buildRoute(picture [][]rune, robot rune, robotX, robotY int) []string {
162 114
 	var (
163
-		length       int
164
-		turn         rune
165
-		routeBuilder strings.Builder
115
+		length int
116
+		turn   rune
117
+		res    []string
166 118
 	)
167 119
 	for {
168 120
 		robot, turn = next(picture, robotX, robotY, robot)
@@ -172,13 +124,10 @@ func buildRoute(picture [][]rune, robot rune, robotX, robotY int) string {
172 124
 			break
173 125
 		}
174 126
 
175
-		if routeBuilder.Len() > 0 {
176
-			routeBuilder.WriteRune(',')
177
-		}
178
-		routeBuilder.WriteRune(turn)
179
-		routeBuilder.WriteString(strconv.Itoa(length))
127
+		res = append(res, string(turn))
128
+		res = append(res, strconv.Itoa(length))
180 129
 	}
181
-	return routeBuilder.String()
130
+	return res
182 131
 }
183 132
 
184 133
 func calculateDust(input []int, m, a, b, c string) int {
@@ -192,9 +141,6 @@ func calculateDust(input []int, m, a, b, c string) int {
192 141
 		for _, line := range []string{m, a, b, c, "n"} {
193 142
 			for _, r := range line {
194 143
 				vm.Input <- int(r)
195
-				if r == 'L' || r == 'R' {
196
-					vm.Input <- ','
197
-				}
198 144
 			}
199 145
 			vm.Input <- '\n'
200 146
 		}
@@ -211,7 +157,7 @@ func main() {
211 157
 	picture := readPicture(input)
212 158
 	sum, robot, robotX, robotY := analysePicture(picture)
213 159
 	route := buildRoute(picture, robot, robotX, robotY)
214
-	m, a, b, c := compact(route)
160
+	m, a, b, c := compress(route)
215 161
 	dust := calculateDust(memory, m, a, b, c)
216 162
 
217 163
 	fmt.Println(sum)

Loading…
Cancel
Save