mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
2023-22 straightforward grid problem but plenty of bugs to workout
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alexchao26/advent-of-code-go/algos"
|
||||
"github.com/alexchao26/advent-of-code-go/cast"
|
||||
"github.com/alexchao26/advent-of-code-go/util"
|
||||
)
|
||||
|
||||
//go:embed input.txt
|
||||
var input string
|
||||
|
||||
func init() {
|
||||
// do this in init (not main) so test file has same input
|
||||
input = strings.TrimRight(input, "\n")
|
||||
if len(input) == 0 {
|
||||
panic("empty input.txt file")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var part int
|
||||
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
||||
flag.Parse()
|
||||
fmt.Println("Running part", part)
|
||||
|
||||
if part == 1 {
|
||||
ans := part1(input)
|
||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||
fmt.Println("Output:", ans)
|
||||
} else {
|
||||
ans := part2(input)
|
||||
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||
fmt.Println("Output:", ans)
|
||||
}
|
||||
}
|
||||
|
||||
func part1(input string) int {
|
||||
bricks := parseInput(input)
|
||||
|
||||
dropBricks(bricks)
|
||||
|
||||
// basically have a graph...
|
||||
// if a brick supports nothing, it can be removed
|
||||
// if a brick supports
|
||||
removableBricks := map[int]bool{}
|
||||
for _, brick := range bricks {
|
||||
if len(brick.supports) == 0 {
|
||||
// supports nothing
|
||||
removableBricks[brick.index] = true
|
||||
} else {
|
||||
// check ALL supported bricks, if this is the ONLY support for those
|
||||
// then this brick CANNOT be removed
|
||||
hasUniqueDependency := false
|
||||
for supportedBrickIndex := range brick.supports {
|
||||
if len(bricks[supportedBrickIndex].supportedBy) == 1 {
|
||||
hasUniqueDependency = true
|
||||
}
|
||||
}
|
||||
if !hasUniqueDependency {
|
||||
removableBricks[brick.index] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len(removableBricks)
|
||||
}
|
||||
|
||||
func dropBricks(bricks []*brick) {
|
||||
// process bricks by lowest z values
|
||||
sort.Slice(bricks, func(i, j int) bool {
|
||||
return bricks[i].start[2] < bricks[j].start[2]
|
||||
})
|
||||
|
||||
// re-index, pesky bug...
|
||||
for i, brick := range bricks {
|
||||
brick.index = i
|
||||
}
|
||||
|
||||
// all bricks are < 10 units of volume, so can store their coords in a map...
|
||||
// also all bricks in the input are straight lines, only one dimension will be > 1
|
||||
occupiedCells := map[[3]int]int{}
|
||||
for _, brick := range bricks {
|
||||
|
||||
isBlocked := false
|
||||
for brick.start[2] > 1 && !isBlocked {
|
||||
for _, coord := range brick.coords {
|
||||
downOne := [3]int{coord[0], coord[1], coord[2] - 1}
|
||||
if index, ok := occupiedCells[downOne]; ok {
|
||||
isBlocked = true
|
||||
|
||||
brick.supportedBy[index] = true
|
||||
bricks[index].supports[brick.index] = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isBlocked {
|
||||
for i := range brick.coords {
|
||||
brick.coords[i][2]--
|
||||
}
|
||||
brick.start[2]--
|
||||
brick.end[2]--
|
||||
}
|
||||
}
|
||||
|
||||
for _, coord := range brick.coords {
|
||||
occupiedCells[coord] = brick.index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
bricks := parseInput(input)
|
||||
dropBricks(bricks)
|
||||
|
||||
total := 0
|
||||
|
||||
// chain reaction
|
||||
for i := range bricks {
|
||||
bricksCopy := copyAllBricks(bricks)
|
||||
startingBrick := bricksCopy[i]
|
||||
|
||||
queueToRemove := []*brick{}
|
||||
for in := range startingBrick.supports {
|
||||
delete(bricksCopy[in].supportedBy, startingBrick.index)
|
||||
queueToRemove = append(queueToRemove, bricksCopy[in])
|
||||
}
|
||||
|
||||
removed := 0
|
||||
for len(queueToRemove) > 0 {
|
||||
br := queueToRemove[0]
|
||||
queueToRemove = queueToRemove[1:]
|
||||
|
||||
if len(br.supportedBy) > 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
removed++
|
||||
|
||||
// check every brick it supports, remove self from it's supportedBy map
|
||||
// then add to queue to be checked
|
||||
for supportedBrickIndex := range br.supports {
|
||||
delete(bricksCopy[supportedBrickIndex].supportedBy, br.index)
|
||||
if len(bricksCopy[supportedBrickIndex].supportedBy) == 0 {
|
||||
queueToRemove = append(queueToRemove, bricksCopy[supportedBrickIndex])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += removed
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
func copyAllBricks(bricks []*brick) []*brick {
|
||||
copiedBricks := []*brick{}
|
||||
for _, b := range bricks {
|
||||
newBrick := &brick{
|
||||
// start: []int{},
|
||||
// end: []int{},
|
||||
index: b.index,
|
||||
// coords: [][3]int{},
|
||||
supportedBy: map[int]bool{},
|
||||
supports: map[int]bool{},
|
||||
}
|
||||
// need full copies of these otherwise they'll point to the same underlying maps
|
||||
for k, v := range b.supportedBy {
|
||||
newBrick.supportedBy[k] = v
|
||||
}
|
||||
for k, v := range b.supports {
|
||||
newBrick.supports[k] = v
|
||||
}
|
||||
copiedBricks = append(copiedBricks, newBrick)
|
||||
}
|
||||
return copiedBricks
|
||||
}
|
||||
|
||||
type brick struct {
|
||||
start, end []int
|
||||
index int
|
||||
coords [][3]int
|
||||
supportedBy map[int]bool
|
||||
supports map[int]bool
|
||||
}
|
||||
|
||||
func parseInput(input string) (ans []*brick) {
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
coords := [6]int{}
|
||||
for i, part := range algos.SplitStringOn(line, []string{",", "~"}) {
|
||||
coords[i] = cast.ToInt(part)
|
||||
}
|
||||
|
||||
if coords[0] > coords[3] || coords[1] > coords[4] || coords[2] > coords[5] {
|
||||
panic("unordered input")
|
||||
}
|
||||
|
||||
allCoords := [][3]int{}
|
||||
for x := coords[0]; x <= coords[3]; x++ {
|
||||
for y := coords[1]; y <= coords[4]; y++ {
|
||||
for z := coords[2]; z <= coords[5]; z++ {
|
||||
allCoords = append(allCoords, [3]int{x, y, z})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ans = append(ans, &brick{
|
||||
start: coords[:3],
|
||||
end: coords[3:],
|
||||
index: len(ans),
|
||||
coords: allCoords,
|
||||
supportedBy: map[int]bool{},
|
||||
supports: map[int]bool{},
|
||||
})
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = `1,0,1~1,2,1
|
||||
0,0,2~2,0,2
|
||||
0,2,3~2,2,3
|
||||
0,0,4~0,2,4
|
||||
2,0,5~2,2,5
|
||||
0,1,6~2,1,6
|
||||
1,1,8~1,1,9`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 471,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_part2(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 7,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 68525,
|
||||
},
|
||||
}
|
||||
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