183 lines
4.5 KiB
Go
183 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"unicode"
|
|
|
|
"git.mstar.dev/mstar/aoc24/util"
|
|
"git.mstar.dev/mstar/goutils/other"
|
|
"git.mstar.dev/mstar/goutils/sliceutils"
|
|
)
|
|
|
|
const EmptyBlockId = -1
|
|
|
|
func findFirstEmptyIndex(list []int) (int, bool) {
|
|
for i, v := range list {
|
|
if v == EmptyBlockId {
|
|
return i, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func findLastUsedIndex(list []int) (int, bool) {
|
|
for i := len(list) - 1; i >= 0; i-- {
|
|
if list[i] != EmptyBlockId {
|
|
return i, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func indicesToBlocks(indices []int) []int {
|
|
blocks := []int{}
|
|
// indices are len of block, len of empty, len of block, len of empty
|
|
isFile := true
|
|
// block id increments each time a block with a file completes
|
|
i := 0
|
|
for _, size := range indices {
|
|
// Write the full block
|
|
for range size {
|
|
if isFile {
|
|
blocks = append(blocks, i)
|
|
} else {
|
|
blocks = append(blocks, EmptyBlockId)
|
|
}
|
|
}
|
|
// If current block is a file, increment id counter
|
|
if isFile {
|
|
i++
|
|
}
|
|
// Swap state
|
|
isFile = !isFile
|
|
}
|
|
return blocks
|
|
}
|
|
|
|
func compactBlocks1(blocks []int) (compacted []int) {
|
|
compacted = slices.Clone(blocks)
|
|
|
|
firstEmpty, _ := findFirstEmptyIndex(compacted)
|
|
lastUsed, _ := findLastUsedIndex(compacted)
|
|
for firstEmpty < lastUsed {
|
|
util.SliceSwap(compacted, firstEmpty, lastUsed)
|
|
firstEmpty, _ = findFirstEmptyIndex(compacted)
|
|
lastUsed, _ = findLastUsedIndex(compacted)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func checksumBlocks(blocks []int) uint64 {
|
|
var total uint64 = 0
|
|
for i, v := range blocks {
|
|
if v == EmptyBlockId {
|
|
continue
|
|
}
|
|
total += uint64(i * v)
|
|
}
|
|
return total
|
|
}
|
|
|
|
func findFirstEmptyBlockOfLength(list []int, length int) (int, bool) {
|
|
lenCounter := 0
|
|
for i, v := range list {
|
|
if v == EmptyBlockId {
|
|
lenCounter++
|
|
} else {
|
|
lenCounter = 0
|
|
}
|
|
if lenCounter == length {
|
|
// Start of empty block with (at least) target length is at i - (lenCounter-1), since lenCounter starts at 0
|
|
return i - lenCounter + 1, true
|
|
}
|
|
}
|
|
return -1, false
|
|
}
|
|
|
|
func findBlockById(list []int, blockId int) (start, length int, found bool) {
|
|
length = 0
|
|
for i := len(list) - 1; i >= 0; i-- {
|
|
if list[i] == blockId {
|
|
length++
|
|
} else {
|
|
if length != 0 {
|
|
return i + 1, length, true
|
|
}
|
|
}
|
|
}
|
|
return -1, -1, false
|
|
}
|
|
|
|
func compactBlocks2(list []int) (compacted []int) {
|
|
compacted = slices.Clone(list)
|
|
lastBlockIndex, _ := findLastUsedIndex(compacted)
|
|
lastBlockId := compacted[lastBlockIndex]
|
|
// fmt.Printf("i1 Checking block id %d\n", lastBlockId)
|
|
lastUsedStart, length, _ := findBlockById(compacted, lastBlockId)
|
|
firstEmpty, found := findFirstEmptyBlockOfLength(compacted, length)
|
|
for !found {
|
|
lastBlockId--
|
|
if lastBlockId < 0 {
|
|
return
|
|
}
|
|
// fmt.Printf("i2 Checking block id %d\n", lastBlockId)
|
|
lastUsedStart, length, _ = findBlockById(compacted, lastBlockId)
|
|
firstEmpty, found = findFirstEmptyBlockOfLength(compacted, length)
|
|
}
|
|
for {
|
|
// fmt.Printf(
|
|
// "Moving block %d starting at %d with len %d to %d\n",
|
|
// lastBlockId,
|
|
// lastUsedStart,
|
|
// length,
|
|
// firstEmpty,
|
|
// )
|
|
for i := range length {
|
|
util.SliceSwap(compacted, lastUsedStart+i, firstEmpty+i)
|
|
}
|
|
lastBlockId--
|
|
if lastBlockId < 0 {
|
|
return
|
|
}
|
|
// fmt.Printf("l1 Checking block id %d\n", lastBlockId)
|
|
lastUsedStart, length, _ = findBlockById(compacted, lastBlockId)
|
|
firstEmpty, found = findFirstEmptyBlockOfLength(compacted, length)
|
|
// startLastEmpty := findStartLastEmptyBlock(compacted)
|
|
// fmt.Printf("First empty found: %d, start last empty: %d\n", firstEmpty, startLastEmpty)
|
|
for !found || firstEmpty > lastUsedStart {
|
|
// fmt.Printf("No space for id %d found\n", lastBlockId)
|
|
lastBlockId--
|
|
if lastBlockId < 0 {
|
|
return
|
|
}
|
|
// fmt.Printf("l2 Checking block id %d\n", lastBlockId)
|
|
lastUsedStart, length, _ = findBlockById(compacted, lastBlockId)
|
|
firstEmpty, found = findFirstEmptyBlockOfLength(compacted, length)
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
rawInput := []rune(string(util.LoadFileFromArgs()))
|
|
allNums := sliceutils.Map(
|
|
sliceutils.Filter(rawInput, func(t rune) bool { return unicode.IsNumber(t) }),
|
|
func(t rune) int { return other.Must(strconv.Atoi(string(t))) },
|
|
)
|
|
blocks := indicesToBlocks(allNums)
|
|
// fmt.Printf("Parsed blocks: %v\n", blocks)
|
|
compacted := compactBlocks1(blocks)
|
|
// fmt.Printf("Compacted blocks: %v\n", compacted)
|
|
onlySetBlocks := sliceutils.Filter(compacted, func(t int) bool { return t != EmptyBlockId })
|
|
checksum1 := checksumBlocks(onlySetBlocks)
|
|
fmt.Printf("Task 1: %d\n", checksum1)
|
|
compacted2 := compactBlocks2(blocks)
|
|
fmt.Printf(
|
|
"Task 2: %d\n",
|
|
checksumBlocks(
|
|
compacted2,
|
|
),
|
|
)
|
|
}
|