mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
2018-day20: prompt was really confusing, it's not generating recursive branches, it just visits the branches
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
input.txt
|
input.txt
|
||||||
|
prompt.*
|
||||||
|
|||||||
+88
-186
@@ -3,8 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/alexchao26/advent-of-code-go/util"
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
)
|
)
|
||||||
@@ -15,30 +13,24 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
fmt.Println("Running part", part)
|
fmt.Println("Running part", part)
|
||||||
|
|
||||||
if part == 1 {
|
ans := part1And2(util.ReadFile("./input.txt"), part)
|
||||||
ans := part1(util.ReadFile("./input.txt"))
|
fmt.Println("Output:", ans)
|
||||||
util.CopyToClipboard(fmt.Sprintf("%v", 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 {
|
// The code is nearly identical for both, so part # is passed in as an arg
|
||||||
var furthest int
|
func part1And2(input string, part int) int {
|
||||||
|
coordsToRooms := generateRoomMap(input)
|
||||||
|
|
||||||
coordsToRooms := parseInput(input)
|
|
||||||
|
|
||||||
roomsVisited := make(map[[2]int]bool)
|
|
||||||
queue := [][3]int{{0, 0, 0}} // coord, coord, distance
|
|
||||||
// dijkstra traverse
|
// dijkstra traverse
|
||||||
|
var furthest int // part 1
|
||||||
|
var countFarRooms int // part 2
|
||||||
|
queue := [][3]int{{0, 0, 0}} // queue node is [3]int{row, col, distance}
|
||||||
|
roomsVisited := make(map[[2]int]bool, len(coordsToRooms))
|
||||||
for len(queue) != 0 {
|
for len(queue) != 0 {
|
||||||
front := queue[0]
|
front := queue[0]
|
||||||
currentCoords := [2]int{front[0], front[1]}
|
currentCoords := [2]int{front[0], front[1]}
|
||||||
currentDistance := front[2]
|
currentDistance := front[2]
|
||||||
currentRoom := coordsToRooms[[2]int{front[0], front[1]}]
|
currentRoom := coordsToRooms[currentCoords]
|
||||||
|
|
||||||
// do not visit the same room twice
|
// do not visit the same room twice
|
||||||
if roomsVisited[currentCoords] {
|
if roomsVisited[currentCoords] {
|
||||||
@@ -46,9 +38,15 @@ func part1(input string) int {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// part 1 check for furthest room
|
||||||
if furthest < currentDistance {
|
if furthest < currentDistance {
|
||||||
furthest = currentDistance
|
furthest = currentDistance
|
||||||
}
|
}
|
||||||
|
// part 2 check for rooms at least 1000 doors away
|
||||||
|
if currentDistance >= 1000 {
|
||||||
|
countFarRooms++
|
||||||
|
}
|
||||||
|
|
||||||
roomsVisited[currentCoords] = true
|
roomsVisited[currentCoords] = true
|
||||||
|
|
||||||
if currentRoom.northDoor {
|
if currentRoom.northDoor {
|
||||||
@@ -67,14 +65,10 @@ func part1(input string) int {
|
|||||||
queue = queue[1:]
|
queue = queue[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
return furthest
|
if part == 1 {
|
||||||
}
|
return furthest
|
||||||
|
}
|
||||||
func part2(input string) int {
|
return countFarRooms
|
||||||
parsed := parseInput(input)
|
|
||||||
_ = parsed
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type room struct {
|
type room struct {
|
||||||
@@ -82,175 +76,83 @@ type room struct {
|
|||||||
northDoor, eastDoor, southDoor, westDoor bool
|
northDoor, eastDoor, southDoor, westDoor bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type roomMap map[[2]int]*room
|
// String receiver method to satisfy Stringer interface, for easier debugging
|
||||||
|
func (r room) String() string {
|
||||||
// String method for Stringer interface for easier debugging of a pointer-ed
|
return fmt.Sprintf("%v: N %v S %v E %v W %v", r.coords, r.northDoor, r.southDoor, r.eastDoor, r.westDoor)
|
||||||
// struct so it won't print as just an address
|
|
||||||
func (rm roomMap) String() string {
|
|
||||||
ans := "{\n"
|
|
||||||
for _, room := range rm {
|
|
||||||
ans += fmt.Sprintf("coords: %v, NSEW doors: %v %v %v %v\n",
|
|
||||||
room.coords, room.northDoor, room.eastDoor, room.southDoor, room.westDoor)
|
|
||||||
}
|
|
||||||
return ans + "}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dirToDiff = map[string][2]int{
|
// returns slice of rooms that represent the ends of child paths, these need
|
||||||
"N": [2]int{-1, 0},
|
// to be extended upon
|
||||||
"S": [2]int{1, 0},
|
func generateRoomMap(input string) map[[2]int]*room {
|
||||||
"E": [2]int{0, 1},
|
coordsToRooms := map[[2]int]*room{
|
||||||
"W": [2]int{0, -1},
|
[2]int{0, 0}: &room{}, // starting room, all zero values are applicable
|
||||||
}
|
|
||||||
|
|
||||||
func parseInput(input string) map[[2]int]*room {
|
|
||||||
coordsToRooms := roomMap{
|
|
||||||
{0, 0}: {coords: [2]int{0, 0}},
|
|
||||||
}
|
}
|
||||||
|
iter := coordsToRooms[[2]int{0, 0}]
|
||||||
|
var stack []*room
|
||||||
|
|
||||||
paths := flattenRegexpPaths(input[1 : len(input)-1])
|
for _, r := range input[1 : len(input)-1] {
|
||||||
|
switch dir := string(r); dir {
|
||||||
// inputRegexp to validate all generated paths - beauty of input being regexp
|
case "N":
|
||||||
inputRegexp := regexp.MustCompile(input)
|
nextCoords := [2]int{iter.coords[0] - 1, iter.coords[1]}
|
||||||
|
// add room to map if it's no in there already
|
||||||
for _, p := range paths {
|
if _, ok := coordsToRooms[nextCoords]; !ok {
|
||||||
if !inputRegexp.MatchString(p) {
|
coordsToRooms[nextCoords] = &room{
|
||||||
panic("BAD PATH: " + p)
|
coords: nextCoords,
|
||||||
}
|
|
||||||
|
|
||||||
// step through path and update te coordsToRooms map
|
|
||||||
currentCoords := [2]int{0, 0}
|
|
||||||
for _, dirRune := range p {
|
|
||||||
dir := string(dirRune)
|
|
||||||
|
|
||||||
// if next room is not in map, add it
|
|
||||||
nextRoomCoords := [2]int{
|
|
||||||
currentCoords[0] + dirToDiff[dir][0],
|
|
||||||
currentCoords[1] + dirToDiff[dir][1],
|
|
||||||
}
|
|
||||||
if _, ok := coordsToRooms[nextRoomCoords]; !ok {
|
|
||||||
coordsToRooms[nextRoomCoords] = &room{
|
|
||||||
coords: nextRoomCoords,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// update valid doors, next's south, current's north
|
||||||
switch dir {
|
nextRoom := coordsToRooms[nextCoords]
|
||||||
case "N":
|
nextRoom.southDoor = true
|
||||||
coordsToRooms[currentCoords].northDoor = true
|
iter.northDoor = true
|
||||||
coordsToRooms[nextRoomCoords].southDoor = true
|
// move to next room
|
||||||
case "S":
|
iter = nextRoom
|
||||||
coordsToRooms[currentCoords].southDoor = true
|
case "S":
|
||||||
coordsToRooms[nextRoomCoords].northDoor = true
|
nextCoords := [2]int{iter.coords[0] + 1, iter.coords[1]}
|
||||||
case "E":
|
if _, ok := coordsToRooms[nextCoords]; !ok {
|
||||||
coordsToRooms[currentCoords].eastDoor = true
|
coordsToRooms[nextCoords] = &room{
|
||||||
coordsToRooms[nextRoomCoords].westDoor = true
|
coords: nextCoords,
|
||||||
case "W":
|
}
|
||||||
coordsToRooms[currentCoords].westDoor = true
|
|
||||||
coordsToRooms[nextRoomCoords].eastDoor = true
|
|
||||||
default:
|
|
||||||
panic("INVALID DIRECTION" + string(dir))
|
|
||||||
}
|
}
|
||||||
currentCoords = nextRoomCoords
|
nextRoom := coordsToRooms[nextCoords]
|
||||||
|
nextRoom.northDoor = true
|
||||||
|
iter.southDoor = true
|
||||||
|
iter = nextRoom
|
||||||
|
case "E":
|
||||||
|
nextCoords := [2]int{iter.coords[0], iter.coords[1] + 1}
|
||||||
|
if _, ok := coordsToRooms[nextCoords]; !ok {
|
||||||
|
coordsToRooms[nextCoords] = &room{
|
||||||
|
coords: nextCoords,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextRoom := coordsToRooms[nextCoords]
|
||||||
|
nextRoom.westDoor = true
|
||||||
|
iter.eastDoor = true
|
||||||
|
iter = nextRoom
|
||||||
|
case "W":
|
||||||
|
nextCoords := [2]int{iter.coords[0], iter.coords[1] - 1}
|
||||||
|
if _, ok := coordsToRooms[nextCoords]; !ok {
|
||||||
|
coordsToRooms[nextCoords] = &room{
|
||||||
|
coords: nextCoords,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextRoom := coordsToRooms[nextCoords]
|
||||||
|
nextRoom.eastDoor = true
|
||||||
|
iter.westDoor = true
|
||||||
|
iter = nextRoom
|
||||||
|
case "(":
|
||||||
|
// push onto stack
|
||||||
|
stack = append(stack, iter)
|
||||||
|
case "|":
|
||||||
|
// reset to top of stack
|
||||||
|
iter = stack[len(stack)-1]
|
||||||
|
case ")":
|
||||||
|
// backtrack and pop off stack
|
||||||
|
iter = stack[len(stack)-1]
|
||||||
|
stack = stack[:len(stack)-1]
|
||||||
|
default:
|
||||||
|
panic("unhandled character: " + string(r))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return coordsToRooms
|
return coordsToRooms
|
||||||
}
|
}
|
||||||
|
|
||||||
var noNestedStepsRegexp = regexp.MustCompile("^[NS|EW]*$")
|
|
||||||
|
|
||||||
// generates a string of all possible paths, inefficient but easier to parse through
|
|
||||||
func flattenRegexpPaths(input string) []string {
|
|
||||||
if noNestedStepsRegexp.MatchString(input) {
|
|
||||||
return strings.Split(input, "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
var paths []string
|
|
||||||
topLevelOptions := breakIntoTopLevelOptions(input)
|
|
||||||
|
|
||||||
for _, step := range topLevelOptions {
|
|
||||||
rootStr, branchStr := ingestNextBalancedParen(step)
|
|
||||||
if rootStr[0] == '(' {
|
|
||||||
rootStr = rootStr[1 : len(rootStr)-1]
|
|
||||||
}
|
|
||||||
rootPaths := flattenRegexpPaths(rootStr)
|
|
||||||
branchPaths := flattenRegexpPaths(branchStr)
|
|
||||||
|
|
||||||
for _, r := range rootPaths {
|
|
||||||
for _, b := range branchPaths {
|
|
||||||
paths = append(paths, r+b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
|
|
||||||
var parensMap = map[string]int{
|
|
||||||
"(": 1,
|
|
||||||
")": -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
// break into all balanced parens separated by a pipe
|
|
||||||
func breakIntoTopLevelOptions(input string) []string {
|
|
||||||
var topLevel []string
|
|
||||||
|
|
||||||
var left, right, parenBalance int
|
|
||||||
for right < len(input) {
|
|
||||||
char := string(input[right])
|
|
||||||
if char == "|" && parenBalance == 0 {
|
|
||||||
topLevel = append(topLevel, input[left:right])
|
|
||||||
right++
|
|
||||||
left = right
|
|
||||||
} else {
|
|
||||||
right++
|
|
||||||
parenBalance += parensMap[char]
|
|
||||||
}
|
|
||||||
|
|
||||||
parenBalance += parensMap[char]
|
|
||||||
}
|
|
||||||
|
|
||||||
if parenBalance != 0 {
|
|
||||||
fmt.Println("top level", topLevel)
|
|
||||||
panic(fmt.Sprintf("paren balance off for %q, %d", input, parenBalance))
|
|
||||||
}
|
|
||||||
|
|
||||||
// append on last option
|
|
||||||
topLevel = append(topLevel, input[left:])
|
|
||||||
|
|
||||||
return topLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
// splits the string into the first section that's a balanced paren & the rest
|
|
||||||
// both sections will be balanced parens
|
|
||||||
//
|
|
||||||
// if the first character is a paren, it will parse until the string is balanced
|
|
||||||
// else it splits at the first paren
|
|
||||||
func ingestNextBalancedParen(input string) (balanced string, remainder string) {
|
|
||||||
if len(input) == 0 {
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not opening paren, parse up to first open paren
|
|
||||||
if input[0] != '(' {
|
|
||||||
firstParenIndex := strings.Index(input, "(")
|
|
||||||
if firstParenIndex == -1 {
|
|
||||||
return input, ""
|
|
||||||
}
|
|
||||||
return input[:firstParenIndex], input[firstParenIndex:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise first character is an opening paren
|
|
||||||
index := 1
|
|
||||||
parenBalance := 1
|
|
||||||
|
|
||||||
// assume all in puts are valid - i.e. a panic for out of range input[index]
|
|
||||||
// is a good warning
|
|
||||||
for parenBalance != 0 {
|
|
||||||
char := string(input[index])
|
|
||||||
parenBalance += parensMap[char]
|
|
||||||
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
return input[:index], input[index:]
|
|
||||||
}
|
|
||||||
|
|||||||
+17
-99
@@ -1,115 +1,33 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/alexchao26/advent-of-code-go/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var tests1 = []struct {
|
func Test_part1And2(t *testing.T) {
|
||||||
name string
|
type args struct {
|
||||||
want int
|
input string
|
||||||
input string
|
part int
|
||||||
// add extra args if needed
|
|
||||||
}{
|
|
||||||
{"example", 3, "^WNE$"},
|
|
||||||
{"example", 10, "^ENWWW(NEEE|SSE(EE|N))$"},
|
|
||||||
{"example", 18, "^ENNWSWW(NEWS|)SSSEEN(WNSE|)EE(SWEN|)NNN$"},
|
|
||||||
{"example", 23, "^ESSWWN(E|NNENN(EESS(WNSE|)SSS|WWWSSSSE(SW|NNNE)))$"},
|
|
||||||
{"example", 31, "^WSSEESWWWNW(S|NENNEEEENN(ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))))$"},
|
|
||||||
// {"actual", ACTUAL_ANSWER, util.ReadFile("input.txt")},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPart1(t *testing.T) {
|
|
||||||
for _, tt := range tests1 {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := part1(tt.input); got != tt.want {
|
|
||||||
t.Errorf("part1() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var tests2 = []struct {
|
|
||||||
name string
|
|
||||||
want int
|
|
||||||
input string
|
|
||||||
// add extra args if needed
|
|
||||||
}{
|
|
||||||
// {"actual", ACTUAL_ANSWER, util.ReadFile("input.txt")},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPart2(t *testing.T) {
|
|
||||||
for _, tt := range tests2 {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_flattenRegexpPaths(t *testing.T) {
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args string
|
args args
|
||||||
want []string
|
want int
|
||||||
}{
|
}{
|
||||||
{"no children", "NSEW", []string{"NSEW"}},
|
{"part1 example 1", args{"^WNE$", 1}, 3},
|
||||||
{"no children", "NSEW|SS|EWN", []string{"NSEW", "SS", "EWN"}},
|
{"part1 example 2", args{"^ENWWW(NEEE|SSE(EE|N))$", 1}, 10},
|
||||||
{"one child", "NSEW(EE|WW|SS)", []string{"NSEWEE", "NSEWWW", "NSEWSS"}},
|
{"part1 example 3", args{"^ENNWSWW(NEWS|)SSSEEN(WNSE|)EE(SWEN|)NNN$", 1}, 18},
|
||||||
{"nested", "NS(EE(WW|SS))WW", []string{"NSEEWWWW", "NSEESSWW"}},
|
{"part1 example 4", args{"^ESSWWN(E|NNENN(EESS(WNSE|)SSS|WWWSSSSE(SW|NNNE)))$", 1}, 23},
|
||||||
{"optional path", "NS(EE(WW|SS|))WW", []string{"NSEEWWWW", "NSEESSWW", "NSEEWW"}},
|
{"part1 example 5", args{"^WSSEESWWWNW(S|NENNEEEENN(ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))))$", 1}, 31},
|
||||||
|
{"part1 actual", args{util.ReadFile("input.txt"), 1}, 4121},
|
||||||
|
{"part2 actual", args{util.ReadFile("input.txt"), 2}, 8636},
|
||||||
}
|
}
|
||||||
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 := flattenRegexpPaths(tt.args); !reflect.DeepEqual(got, tt.want) {
|
if got := part1And2(tt.args.input, tt.args.part); got != tt.want {
|
||||||
t.Errorf("flattenRegexpPaths() = %v, want %v", got, tt.want)
|
t.Errorf("part1And2() = %v, want %v", got, tt.want)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_ingestNextBalancedParen(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args string
|
|
||||||
wantBalanced string
|
|
||||||
wantRemainder string
|
|
||||||
}{
|
|
||||||
{"nonparen start", "NEWS(NEWS)", "NEWS", "(NEWS)"},
|
|
||||||
{"paren start", "(NEWS|NEWS)NEWS(NEWS|)", "(NEWS|NEWS)", "NEWS(NEWS|)"},
|
|
||||||
{"nested", "(NEWS(NEW|NEW))NEWS(NEWS|)", "(NEWS(NEW|NEW))", "NEWS(NEWS|)"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
gotBalanced, gotRemainder := ingestNextBalancedParen(tt.args)
|
|
||||||
if gotBalanced != tt.wantBalanced {
|
|
||||||
t.Errorf("ingestNextBalancedParen() gotBalanced = %v, want %v", gotBalanced, tt.wantBalanced)
|
|
||||||
}
|
|
||||||
if gotRemainder != tt.wantRemainder {
|
|
||||||
t.Errorf("ingestNextBalancedParen() gotRemainder = %v, want %v", gotRemainder, tt.wantRemainder)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_breakIntoTopLevelOptions(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args string
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{"flat w/o or", "NEWS", []string{"NEWS"}},
|
|
||||||
{"flat w/ or", "NEWS|NWW", []string{"NEWS", "NWW"}},
|
|
||||||
{"parens w/ or", "NEWS(NE|NW)|NWW", []string{"NEWS(NE|NW)", "NWW"}},
|
|
||||||
{"from example", "ESSSSW(NWSW|SSEN)|WSWWN(E|WWS(E|SS))", []string{
|
|
||||||
"ESSSSW(NWSW|SSEN)", "WSWWN(E|WWS(E|SS))",
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := breakIntoTopLevelOptions(tt.args); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("breakIntoTopLevelOptions() = %v, want %v", got, tt.want)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user