Advent of Code 2022: Day7

Back up

Stream Video

Code

(Note: this is updated slightly from stream’s code. I split the part1/part2 solutions as I have done on previous days. It’s the same algorithm as streamed, just a slight refactor).

package aoc

import (
	"fmt"
	"io"
	"sort"
	"strconv"
	"strings"

	"mono3/advent/lib/lineio"
	"mono3/common/log"
)

type bySize []*entry

func (b bySize) Len() int           { return len(b) }
func (b bySize) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
func (b bySize) Less(i, j int) bool { return b[i].sz < b[j].sz }

type entry struct {
	name string
	sz   int
	c    []*entry
	p    *entry
}

func (e *entry) dump(w io.Writer, indent int) {
	for i := 0; i < indent; i++ {
		w.Write([]byte(" "))
	}
	t := "file"
	if len(e.c) > 0 {
		t = "dir"
	}
	fmt.Fprintf(w, "- %s (%s, size=%d)\n", e.name, t, e.sz)
	for _, child := range e.c {
		child.dump(w, indent+2)
	}
}

func (e *entry) String() string {
	var sb strings.Builder
	e.dump(&sb, 0)
	return sb.String()
}

func (e *entry) GetSize() int {
	if e.sz > 0 {
		return e.sz
	}
	for _, child := range e.c {
		e.sz += child.GetSize()
	}
	return e.sz
}

func (e *entry) GetChild(name string) *entry {
	for _, child := range e.c {
		if child.name == name {
			return child
		}
	}
	return nil
}

func (e *entry) AddChild(child *entry) {
	e.c = append(e.c, child)
}

func (e *entry) walk(fun func(*entry)) {
	fun(e)
	for _, child := range e.c {
		child.walk(fun)
	}
}

// Each part gets the same input with reader pointing at the start of input.
var PuzzleParts = []func(r io.Reader) error{
	PuzzlePart1,
	PuzzlePart2,
}

func PuzzlePart1(r io.Reader) error {
	return solveit(r, 1, func(root *entry) int {
		max := 100000
		sum := 0
		root.walk(func(e *entry) {
			if len(e.c) > 0 && e.sz < max {
				sum += e.sz
			}
		})
		return sum
	})
}

func PuzzlePart2(r io.Reader) error {
	return solveit(r, 2, func(root *entry) int {
		needSpace := 30000000
		freeSpace := 70000000 - root.GetSize()

		if freeSpace < needSpace {
			log.Infof("Still need %d freed", needSpace-freeSpace)

			// find smallest directory that is at least needSpace-freeSpace
			// in size.
			var candidates []*entry
			root.walk(func(e *entry) {
				if len(e.c) == 0 {
					return
				}

				if e.sz >= needSpace-freeSpace {
					candidates = append(candidates, e)
				}
			})
			log.Infof("Candidates:")
			for _, c := range candidates {
				log.Infof("\t%s sz %d", c.name, c.sz)
			}
			sort.Sort(bySize(candidates))
			selected := candidates[0]
			log.Infof("Selected: name=%s", selected.name)

			return selected.sz
		}
		return 0
	})
}

func solveit(r io.Reader, part int, fun func(root *entry) int) error {
	log.Info("Running")

	root := &entry{
		name: "/",
		sz:   0,
	}
	var cur *entry

	if err := lineio.ForEach(r, func(ln int, line string) error {
		if strings.HasPrefix(line, "$ ") {
			// Is a command.
			parts := strings.SplitN(line[2:], " ", 2)
			cmd := parts[0]
			var arg string
			if len(parts) >= 2 {
				arg = parts[1]
			}

			switch cmd {
			case "cd":
				// arg is either: filename or ".." or "/"
				if arg == "/" {
					cur = root
				} else if arg == ".." {
					if cur != nil && cur.p != nil {
						cur = cur.p
					}
				} else if cur != nil {
					child := cur.GetChild(arg)
					cur = child
				} else {
					return fmt.Errorf("cur is nil and this is bad")
				}

			case "ls":
				// Do nothing.

			}
		} else {
			// Is command output, and "ls" is the only command that
			// generates output.
			parts := strings.SplitN(line, " ", 2)
			filename := parts[1]

			if cur == nil {
				return fmt.Errorf("cur is nil this is bad.")
			}

			if parts[0] == "dir" {
				cur.AddChild(&entry{
					name: filename,
					sz:   0,
					c:    nil,
					p:    cur,
				})

			} else {
				// parts[0] is a size, parse that int.
				sz, err := strconv.ParseInt(parts[0], 10, 64)
				if err != nil {
					return fmt.Errorf("err parsing size of %s: %w", filename, err)
				}

				cur.AddChild(&entry{
					name: filename,
					sz:   int(sz),
					c:    nil,
					p:    cur,
				})

			}

		}

		return nil
	}); err != nil {
		return err
	}

	root.GetSize()
	w := log.Writer()
	w.Write([]byte(root.String()))
	w.Close()

	result := fun(root)
	log.Infof("Part %d result: %d", part, result)

	return nil
}

aimeeble@blog

the blog of aimeeble


By Aimee, 2022-12-07


Table of Contents: