mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 20:53:30 +02:00
2020-day17: generalized solution for both parts, 4D game of life
This commit is contained in:
+52
-194
@@ -15,235 +15,93 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
fmt.Println("Running part", part)
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
if part == 1 {
|
ans := conwayCubes(util.ReadFile("./input.txt"), part)
|
||||||
ans := part1(util.ReadFile("./input.txt"))
|
fmt.Println("Output:", ans)
|
||||||
fmt.Println("Output:", ans)
|
|
||||||
} else {
|
|
||||||
ans := part2(util.ReadFile("./input.txt"))
|
|
||||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
|
||||||
fmt.Println("Output:", ans)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func part1(input string) int {
|
var diffs = [3]int{-1, 0, 1}
|
||||||
nodes := parseInput3D(input)
|
|
||||||
|
|
||||||
for cycles := 0; cycles < 6; cycles++ {
|
func conwayCubes(input string, part int) int {
|
||||||
toCheck := map[[3]int]bool{}
|
activeNodes := parseInput(input)
|
||||||
for _, node := range nodes {
|
|
||||||
for _, dir := range directions {
|
|
||||||
x, y, z := node.x+dir[0], node.y+dir[1], node.z+dir[2]
|
|
||||||
toCheck[[3]int{x, y, z}] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nextState := map[[3]int]*node3D{}
|
|
||||||
for coord := range toCheck {
|
|
||||||
// check all neighbors around this coord
|
|
||||||
var countNeighbors int
|
|
||||||
for _, d := range directions {
|
|
||||||
x, y, z := coord[0]+d[0], coord[1]+d[1], coord[2]+d[2]
|
|
||||||
neighCoord := [3]int{x, y, z}
|
|
||||||
if neigh, ok := nodes[neighCoord]; ok {
|
|
||||||
if neigh.active {
|
|
||||||
countNeighbors++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stateInNext := node3D{
|
|
||||||
x: coord[0],
|
|
||||||
y: coord[1],
|
|
||||||
z: coord[2],
|
|
||||||
active: false,
|
|
||||||
}
|
|
||||||
if n, ok := nodes[coord]; ok && n.active {
|
|
||||||
if countNeighbors == 2 || countNeighbors == 3 {
|
|
||||||
stateInNext.active = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// inactive originally
|
|
||||||
if countNeighbors == 3 {
|
|
||||||
stateInNext.active = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nextState[coord] = &stateInNext
|
|
||||||
}
|
|
||||||
nodes = nextState
|
|
||||||
|
|
||||||
|
diffsW := []int{0}
|
||||||
|
if part == 2 {
|
||||||
|
diffsW = []int{-1, 0, 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node.active {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// cubes after 6 cycles
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
func generate3DDirections() [][3]int {
|
|
||||||
directions := [][3]int{}
|
|
||||||
for i := -1; i < 2; i++ {
|
|
||||||
for j := -1; j < 2; j++ {
|
|
||||||
for k := -1; k < 2; k++ {
|
|
||||||
if !(i == 0 && j == 0 && k == 0) {
|
|
||||||
directions = append(directions, [3]int{i, j, k})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return directions
|
|
||||||
}
|
|
||||||
|
|
||||||
var directions = generate3DDirections()
|
|
||||||
|
|
||||||
type node3D struct {
|
|
||||||
x, y, z int
|
|
||||||
active bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseInput3D(input string) map[[3]int]*node3D {
|
|
||||||
nodes := map[[3]int]*node3D{}
|
|
||||||
lines := strings.Split(input, "\n")
|
|
||||||
for i, l := range lines {
|
|
||||||
for j, cell := range strings.Split(l, "") {
|
|
||||||
n := &node3D{
|
|
||||||
x: i, y: j, z: 0, active: false,
|
|
||||||
}
|
|
||||||
if cell == "#" {
|
|
||||||
n.active = true
|
|
||||||
}
|
|
||||||
nodes[[3]int{i, j, 0}] = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func part2(input string) int {
|
|
||||||
nodes := parseInput4D(input)
|
|
||||||
|
|
||||||
for cycles := 0; cycles < 6; cycles++ {
|
for cycles := 0; cycles < 6; cycles++ {
|
||||||
toCheck := map[[4]int]bool{}
|
toCheck := map[[4]int]bool{}
|
||||||
for _, node := range nodes {
|
|
||||||
for _, dir := range directions4D {
|
for coord := range activeNodes {
|
||||||
x, y, z, w := node.x+dir[0], node.y+dir[1], node.z+dir[2], node.w+dir[3]
|
for _, dx := range diffs {
|
||||||
toCheck[[4]int{x, y, z, w}] = true
|
for _, dy := range diffs {
|
||||||
|
for _, dz := range diffs {
|
||||||
|
for _, dw := range diffsW {
|
||||||
|
toCheck[[4]int{
|
||||||
|
coord[0] + dx,
|
||||||
|
coord[1] + dy,
|
||||||
|
coord[2] + dz,
|
||||||
|
coord[3] + dw}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextState := map[[4]int]*node4D{}
|
nextState := map[[4]int]bool{}
|
||||||
for coord := range toCheck {
|
for coord := range toCheck {
|
||||||
// check all neighbors around this coord
|
// check all neighbors around this coord
|
||||||
var countNeighbors int
|
var countNeighbors int
|
||||||
for _, d := range directions4D {
|
for _, dx := range diffs {
|
||||||
x, y, z, w := coord[0]+d[0], coord[1]+d[1], coord[2]+d[2], coord[3]+d[3]
|
for _, dy := range diffs {
|
||||||
neighCoord := [4]int{x, y, z, w}
|
for _, dz := range diffs {
|
||||||
if neigh, ok := nodes[neighCoord]; ok {
|
for _, dw := range diffsW {
|
||||||
if neigh.active {
|
if dx != 0 || dy != 0 || dz != 0 || dw != 0 {
|
||||||
countNeighbors++
|
x, y, z, w := coord[0]+dx, coord[1]+dy, coord[2]+dz, coord[3]+dw
|
||||||
|
neighCoord := [4]int{x, y, z, w}
|
||||||
|
if isActive, ok := activeNodes[neighCoord]; ok && isActive {
|
||||||
|
countNeighbors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stateInNext := node4D{
|
if wasActive, ok := activeNodes[coord]; ok && wasActive {
|
||||||
x: coord[0],
|
|
||||||
y: coord[1],
|
|
||||||
z: coord[2],
|
|
||||||
w: coord[3],
|
|
||||||
active: false,
|
|
||||||
}
|
|
||||||
if n, ok := nodes[coord]; ok && n.active {
|
|
||||||
if countNeighbors == 2 || countNeighbors == 3 {
|
if countNeighbors == 2 || countNeighbors == 3 {
|
||||||
stateInNext.active = true
|
nextState[coord] = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// inactive originally
|
// inactive originally
|
||||||
if countNeighbors == 3 {
|
if countNeighbors == 3 {
|
||||||
stateInNext.active = true
|
nextState[coord] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nextState[coord] = &stateInNext
|
|
||||||
|
|
||||||
}
|
}
|
||||||
nodes = nextState
|
|
||||||
|
|
||||||
|
activeNodes = nextState
|
||||||
}
|
}
|
||||||
|
|
||||||
var count int
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node.active {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// cubes after 6 cycles
|
// cubes after 6 cycles
|
||||||
return count
|
return len(activeNodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
type node4D struct {
|
// this is not perfectly generalized because arrays in go have to be sized at compile
|
||||||
x, y, z, w int
|
// time, and slices can't be used to map keys because they're not trivial to compare
|
||||||
active bool
|
// they could be compared by converting it into a string... but that's annoying
|
||||||
}
|
func parseInput(input string) map[[4]int]bool {
|
||||||
|
setActiveNodes := map[[4]int]bool{}
|
||||||
|
|
||||||
|
for i, line := range strings.Split(input, "\n") {
|
||||||
|
for j, cell := range strings.Split(line, "") {
|
||||||
|
|
||||||
func parseInput4D(input string) map[[4]int]*node4D {
|
|
||||||
nodes := map[[4]int]*node4D{}
|
|
||||||
lines := strings.Split(input, "\n")
|
|
||||||
for i, l := range lines {
|
|
||||||
for j, cell := range strings.Split(l, "") {
|
|
||||||
n := &node4D{
|
|
||||||
x: i, y: j, z: 0, w: 0, active: false,
|
|
||||||
}
|
|
||||||
if cell == "#" {
|
if cell == "#" {
|
||||||
n.active = true
|
// start z and w coords at zero
|
||||||
}
|
n := [4]int{i, j, 0, 0}
|
||||||
nodes[[4]int{i, j, 0, 0}] = n
|
setActiveNodes[n] = true
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func generate4DDirections() [][4]int {
|
|
||||||
directions := [][4]int{}
|
|
||||||
for i := -1; i < 2; i++ {
|
|
||||||
for j := -1; j < 2; j++ {
|
|
||||||
for k := -1; k < 2; k++ {
|
|
||||||
for w := -1; w < 2; w++ {
|
|
||||||
if !(i == 0 && j == 0 && k == 0 && w == 0) {
|
|
||||||
directions = append(directions, [4]int{i, j, k, w})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return directions
|
return setActiveNodes
|
||||||
}
|
|
||||||
|
|
||||||
var directions4D = generate4DDirections()
|
|
||||||
|
|
||||||
func makeDirections(length int) [][]int {
|
|
||||||
perms := [][]int{
|
|
||||||
make([]int, length),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
for _, p := range perms {
|
|
||||||
copy1, copy2 := make([]int, length), make([]int, length)
|
|
||||||
copy(copy1, p)
|
|
||||||
copy(copy2, p)
|
|
||||||
copy1[i] = -1
|
|
||||||
copy2[i] = 1
|
|
||||||
perms = append(perms, copy1, copy2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return perms[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func getStringKey(slice []int) string {
|
|
||||||
var key string
|
|
||||||
for _, v := range slice {
|
|
||||||
key += fmt.Sprintf("%d-", v)
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-23
@@ -10,37 +10,22 @@ var example = `.#.
|
|||||||
..#
|
..#
|
||||||
###`
|
###`
|
||||||
|
|
||||||
func Test_part1(t *testing.T) {
|
func Test_conwayCubes(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input string
|
input string
|
||||||
|
part int
|
||||||
want int
|
want int
|
||||||
}{
|
}{
|
||||||
{"example", example, 112},
|
{"example_part1", example, 1, 112},
|
||||||
{"actual", util.ReadFile("input.txt"), 388},
|
{"actual_part1", util.ReadFile("input.txt"), 1, 388},
|
||||||
|
{"example_part2", example, 2, 848},
|
||||||
|
{"actual_part2", util.ReadFile("input.txt"), 2, 2280},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if got := part1(tt.input); got != tt.want {
|
if got := conwayCubes(tt.input, tt.part); got != tt.want {
|
||||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
t.Errorf("conwayCubes() = %v, want %v", got, tt.want)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_part2(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
input string
|
|
||||||
want int
|
|
||||||
}{
|
|
||||||
{"example", example, 848},
|
|
||||||
{"actual", util.ReadFile("input.txt"), 2280},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := part2(tt.input); got != tt.want {
|
|
||||||
t.Errorf("part2() = %v, want %v", got, tt.want)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user