Files
aoc2024/day9/main.go
2024-12-10 00:34:03 +01:00

216 lines
3.9 KiB
Go

package main
import (
"bufio"
"fmt"
"os"
"strings"
)
const SENTINEL int = -2
const FREE_BLOCK int = -1
type Block struct {
Prev, Next *Block
Size int
FileId int
}
func (before *Block) InsertAfter(after *Block) {
before.Next.Prev = after
after.Next, before.Next = before.Next, after
after.Prev = before
}
func (block *Block) Detach() {
block.Prev.Next, block.Next.Prev = block.Next, block.Prev
block.Prev, block.Next = nil, nil
}
func (block *Block) TryMergeFreeWithNext() {
if block.FileId == FREE_BLOCK && block.Next.FileId == FREE_BLOCK {
block.Size += block.Next.Size
block.Next.Detach()
}
}
func (block *Block) TryMergeFree() *Block {
block.TryMergeFreeWithNext()
if block.Prev.FileId == FREE_BLOCK {
block = block.Prev
block.TryMergeFreeWithNext()
}
return block
}
func (block *Block) Split(Size int) {
if Size >= block.Size {
panic("please")
}
if Size == block.Size {
return
}
newBlock := &Block{nil, nil, block.Size - Size, block.FileId}
block.Size = Size
block.InsertAfter(newBlock)
}
type Disk struct {
Sentinel *Block
}
func readInt(b byte) int {
return int(b - '0')
}
func MakeDisk(description string) (disk Disk) {
disk.Sentinel = new(Block)
disk.Sentinel.Size = 0
disk.Sentinel.FileId = SENTINEL
disk.Sentinel.Next = disk.Sentinel
disk.Sentinel.Prev = disk.Sentinel
nextFileId := 0
nextIsFree := false
head := disk.Sentinel
for _, b := range []byte(description) {
block := &Block{
Prev: nil, Next: nil, Size: readInt(b),
}
if block.Size == 0 {
nextIsFree = !nextIsFree
continue
}
if nextIsFree {
block.FileId = FREE_BLOCK
nextIsFree = false
} else {
block.FileId = nextFileId
nextFileId++
nextIsFree = true
}
head.InsertAfter(block)
head = block
}
return
}
func (disk *Disk) Print() {
fmt.Println("===============")
for b := disk.Sentinel.Next; b != disk.Sentinel; b = b.Next {
fmt.Println(b)
}
fmt.Println("===============")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
func (disk *Disk) Compactify() {
head := disk.Sentinel.Next
tail := disk.Sentinel.Prev
for head != tail {
if head.FileId >= 0 {
head = head.Next
continue
}
if tail.FileId == FREE_BLOCK {
tail = tail.Prev
continue
}
if tail.Size > head.Size {
tail.Split(tail.Size - head.Size)
tail = tail.Next
}
if head.Size > tail.Size {
head.Split(tail.Size)
}
head.FileId, tail.FileId = tail.FileId, head.FileId
tail = tail.TryMergeFree()
}
}
func (disk *Disk) Defragment() {
tail := disk.Sentinel.Prev
for tail != disk.Sentinel {
if tail.FileId == FREE_BLOCK {
tail = tail.Prev
continue
}
var freeBlock *Block = nil
for head := disk.Sentinel.Next; head != tail; head = head.Next {
if head.FileId == FREE_BLOCK && head.Size >= tail.Size {
freeBlock = head
break
}
}
if freeBlock == nil {
tail = tail.Prev
continue
}
if freeBlock.Size > tail.Size {
freeBlock.Split(tail.Size)
}
freeBlock.FileId, tail.FileId = tail.FileId, freeBlock.FileId
tail = tail.TryMergeFree()
}
}
func (disk *Disk) ComputeChecksum() (sum int64) {
pos := 0
for b := disk.Sentinel.Next; b != disk.Sentinel; b = b.Next {
if b.FileId >= 0 {
for i := 0; i < b.Size; i++ {
sum += int64(pos * b.FileId)
pos++
}
} else {
pos += b.Size
}
}
return
}
func main() {
disk := MakeDisk(readData("example.txt"))
disk.Compactify()
fmt.Println(disk.ComputeChecksum(), 1928)
disk = MakeDisk(readData("data.txt"))
disk.Compactify()
fmt.Println(disk.ComputeChecksum(), 6415184586041)
disk = MakeDisk(readData("example.txt"))
disk.Defragment()
fmt.Println(disk.ComputeChecksum(), 2858)
disk = MakeDisk(readData("data.txt"))
disk.Defragment()
fmt.Println(disk.ComputeChecksum())
}
func readData(fileName string) string {
resultBytes, err := os.ReadFile(fileName)
if err != nil {
panic(err)
}
return strings.TrimSpace(string(resultBytes))
}