239 lines
4.6 KiB
Go
239 lines
4.6 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
doPart := 2
|
||
|
inp := "vbqugkhl" // My puzzle input
|
||
|
if len(os.Args) > 1 {
|
||
|
if os.Args[1] == "-1" {
|
||
|
doPart = 1
|
||
|
} else {
|
||
|
inp = strings.ToLower(os.Args[1])
|
||
|
}
|
||
|
}
|
||
|
if doPart == 1 {
|
||
|
part1(inp)
|
||
|
} else {
|
||
|
part2(inp)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func part1(inp string) {
|
||
|
var diskRows []string
|
||
|
for i := 0; i < 128; i++ {
|
||
|
diskRows = append(diskRows, KnotHash(fmt.Sprintf("%s-%d", inp, i)))
|
||
|
}
|
||
|
var usedSquares int
|
||
|
for i := range diskRows {
|
||
|
bin := GetBinaryString(diskRows[i])
|
||
|
usedSquares += strings.Count(bin, "1")
|
||
|
}
|
||
|
fmt.Println(usedSquares, "used squares")
|
||
|
}
|
||
|
|
||
|
var diskGrid map[string]bool
|
||
|
var groups map[string]int
|
||
|
|
||
|
func part2(inp string) {
|
||
|
diskGrid = make(map[string]bool)
|
||
|
groups = make(map[string]int)
|
||
|
|
||
|
fmt.Println("Building DiskGrid...")
|
||
|
var grpCnt int
|
||
|
for i := 0; i < 128; i++ {
|
||
|
row := GetBinaryString(KnotHash(fmt.Sprintf("%s-%d", inp, i)))
|
||
|
for j := range row {
|
||
|
diskGrid[cs(i, j)] = (row[j] == '1')
|
||
|
if row[j] == '1' {
|
||
|
groups[cs(i, j)] = grpCnt
|
||
|
grpCnt++
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var iters int
|
||
|
red := true
|
||
|
for red {
|
||
|
red = ReduceGroups()
|
||
|
iters++
|
||
|
}
|
||
|
fmt.Println("Reduced", iters, "times")
|
||
|
fmt.Println(GetGroupCount(), "regions")
|
||
|
}
|
||
|
|
||
|
func ReduceGroups() bool {
|
||
|
var ret bool
|
||
|
for x := 0; x < 128; x++ {
|
||
|
for y := 0; y < 128; y++ {
|
||
|
if oV, oOk := diskGrid[cs(x, y)]; oOk && oV {
|
||
|
if dV, dOk := diskGrid[cs(x, y-1)]; dOk && dV && groups[cs(x, y-1)] != groups[cs(x, y)] {
|
||
|
CombineBlockGroups(cs(x, y), cs(x, y-1))
|
||
|
ret = true
|
||
|
}
|
||
|
if dV, dOk := diskGrid[cs(x-1, y)]; dOk && dV && groups[cs(x-1, y)] != groups[cs(x, y)] {
|
||
|
CombineBlockGroups(cs(x, y), cs(x-1, y))
|
||
|
ret = true
|
||
|
}
|
||
|
if dV, dOk := diskGrid[cs(x+1, y)]; dOk && dV && groups[cs(x+1, y)] != groups[cs(x, y)] {
|
||
|
CombineBlockGroups(cs(x+1, y), cs(x, y))
|
||
|
ret = true
|
||
|
}
|
||
|
if dV, dOk := diskGrid[cs(x, y+1)]; dOk && dV && groups[cs(x, y+1)] != groups[cs(x, y)] {
|
||
|
CombineBlockGroups(cs(x, y+1), cs(x, y))
|
||
|
ret = true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func CombineBlockGroups(b1, b2 string) {
|
||
|
if groups[b1] < groups[b2] {
|
||
|
groups[b1] = groups[b2]
|
||
|
} else {
|
||
|
groups[b2] = groups[b1]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func GetGroupCount() int {
|
||
|
var gps []int
|
||
|
for i := range groups {
|
||
|
var have bool
|
||
|
for j := range gps {
|
||
|
if groups[i] == gps[j] {
|
||
|
have = true
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if !have {
|
||
|
gps = append(gps, groups[i])
|
||
|
}
|
||
|
}
|
||
|
return len(gps)
|
||
|
}
|
||
|
|
||
|
func FindGroup(x, y int) (int, error) {
|
||
|
key := cs(x, y)
|
||
|
if v, ok := groups[key]; ok {
|
||
|
return v, nil
|
||
|
}
|
||
|
return -1, errors.New("Not in a group")
|
||
|
}
|
||
|
|
||
|
func PrintUsageChunk(stX, stY, endX, endY int, showGroups bool) {
|
||
|
for x := stX; x < endX; x++ {
|
||
|
for y := stY; y < endY; y++ {
|
||
|
spot := "."
|
||
|
if v, ok := groups[cs(x, y)]; ok {
|
||
|
if showGroups {
|
||
|
v = v % 16
|
||
|
spot = fmt.Sprintf("%x", v)
|
||
|
} else {
|
||
|
spot = "#"
|
||
|
}
|
||
|
}
|
||
|
fmt.Printf(spot)
|
||
|
}
|
||
|
fmt.Println("")
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Get a map coordinate string for x, y
|
||
|
func cs(x, y int) string {
|
||
|
return fmt.Sprint(x, "-", y)
|
||
|
}
|
||
|
|
||
|
// Get the x, y coordinate from a string
|
||
|
func sc(c string) (int, int) {
|
||
|
pts := strings.Split(c, "-")
|
||
|
return Atoi(pts[0]), Atoi(pts[1])
|
||
|
}
|
||
|
|
||
|
func KnotHash(inp string) string {
|
||
|
var idx, skip int
|
||
|
var list []int
|
||
|
for i := 0; i < 256; i++ {
|
||
|
list = append(list, i)
|
||
|
}
|
||
|
inpBts := []byte(inp)
|
||
|
inpBts = append(inpBts, []byte{17, 31, 73, 47, 23}...)
|
||
|
for j := 0; j < 64; j++ {
|
||
|
for i := range inpBts {
|
||
|
idx, skip, list = khRound(int(inpBts[i]), idx, skip, list)
|
||
|
}
|
||
|
}
|
||
|
// Now calculate the dense hash
|
||
|
var dense []byte
|
||
|
for i := 0; i < len(list); i += 16 {
|
||
|
dense = append(dense, xorList(list[i:i+16]))
|
||
|
}
|
||
|
return fmt.Sprintf("%x", dense)
|
||
|
}
|
||
|
|
||
|
func khRound(i, idx, skip int, list []int) (int, int, []int) {
|
||
|
// if idx+i overflows, pull from the front
|
||
|
var revList []int
|
||
|
for j := idx; j < idx+i; j++ {
|
||
|
revList = append([]int{list[j%256]}, revList...)
|
||
|
}
|
||
|
for j := 0; j < len(revList); j++ {
|
||
|
list[(idx+j)%256] = revList[j]
|
||
|
}
|
||
|
|
||
|
idx += i + skip
|
||
|
skip++
|
||
|
return idx, skip, list
|
||
|
}
|
||
|
|
||
|
func xorList(inp []int) byte {
|
||
|
var ret byte
|
||
|
for i := range inp {
|
||
|
ret ^= byte(inp[i])
|
||
|
}
|
||
|
return ret
|
||
|
}
|
||
|
|
||
|
func GetBinaryString(inp string) string {
|
||
|
var bin string
|
||
|
for i := range inp {
|
||
|
var v int
|
||
|
if inp[i] >= '0' && inp[i] <= '9' {
|
||
|
v = int(inp[i] - '0')
|
||
|
} else if inp[i] >= 'a' && inp[i] <= 'f' {
|
||
|
v = int(inp[i] - 'a' + 10)
|
||
|
}
|
||
|
nibble := fmt.Sprintf("%04s", strconv.FormatInt(int64(v), 2))
|
||
|
bin += nibble
|
||
|
}
|
||
|
return bin
|
||
|
}
|
||
|
|
||
|
func StdinToString() string {
|
||
|
var input string
|
||
|
scanner := bufio.NewScanner(os.Stdin)
|
||
|
for scanner.Scan() {
|
||
|
input = scanner.Text()
|
||
|
}
|
||
|
return input
|
||
|
}
|
||
|
|
||
|
func Atoi(i string) int {
|
||
|
var ret int
|
||
|
var err error
|
||
|
if ret, err = strconv.Atoi(i); err != nil {
|
||
|
log.Fatal("Invalid Atoi")
|
||
|
}
|
||
|
return ret
|
||
|
}
|