Thanks to visit codestin.com
Credit goes to programming.dev

  • 2 Posts
  • 85 Comments
Joined 2 年前
Codestin Search App
Cake day: 2023年12月21日

Codestin Search App

  • Back when I was a student, we had to implement the following projects to validate and I think they are a really nice set of training projects for C (memory management, handling your own types, algorithms and off-by-one traps):

    • a basic implementation of diff
    • a fully functional hashmap (subproject of diff actually)

    I personally wrote the followings too:

    • a program to compress/decompress archives using only the LZ77 algorithm
    • math expression evaluator (i.e. a basic interpreter)
    • a basic garbage collector (mark&sweep, mark&copy, whatever grinds your gears)

    If you are already a competent developer in other languages, these should be reasonable projects. The difficulty lies in doing them in C. For the hashmap, the evaluator and the archiver, you have to correctly manage your data and memory. this makes them excellent projects for learning what makes C… C. For the garbage collector… well… you are the memory manager. Basic GC algorithms are easy to understand but what you want to learn here is the actual management of the memory. See it as a more advanced project if you want to really grok dirty C.

    Most importantly: have fun :)




  • Go

    Well… I was about to dive into bin packing and stuff. I started by pruning the obvious candidates (those which can fit all the shapes one next to the other, without more computation) so save CPU time for the real stuff. I ran my code on the real input just to see and… what to do mean there are no candidate left? I write the number of obvious boys into the website’s input box, just to check and… Ah. I understand why people on reddit said they felt dirty x)

    Anyway the code:

    day12.go
    package main
    
    import (
    	"aoc/utils"
    	"fmt"
    	"slices"
    	"strconv"
    	"strings"
    )
    
    type shape [3][3]bool
    
    func parseShape(input chan string) shape {
    	// remove the header
    	_ = <-input
    
    	sh := shape{}
    	idx := 0
    	for line := range input {
    		if line == "" {
    			break
    		}
    
    		row := [3]bool{}
    		for idx, c := range []rune(line) {
    			if c == '#' {
    				row[idx] = true
    			}
    		}
    
    		sh[idx] = row
    		idx++
    	}
    
    	return sh
    }
    
    func (sh shape) usedArea() int {
    	sum := 0
    	for _, row := range sh {
    		for _, cell := range row {
    			if cell {
    				sum++
    			}
    		}
    	}
    	return sum
    }
    
    type regionConstraints struct {
    	width, height int
    	shapes        []int
    }
    
    func parseRegionConstraint(line string) (rc regionConstraints) {
    	parts := strings.Split(line, ":")
    	dims := strings.Split(parts[0], "x")
    	rc.width, _ = strconv.Atoi(dims[0])
    	rc.height, _ = strconv.Atoi(dims[1])
    
    	shapes := strings.Fields(parts[1])
    	rc.shapes = make([]int, len(shapes))
    	for idx, shape := range shapes {
    		rc.shapes[idx], _ = strconv.Atoi(shape)
    	}
    	return rc
    }
    
    type problem struct {
    	shapes      []shape
    	constraints []regionConstraints
    }
    
    func newProblem(input chan string) problem {
    	shapes := make([]shape, 6)
    	for idx := range 6 {
    		shapes[idx] = parseShape(input)
    	}
    
    	regionConstraints := []regionConstraints{}
    	for line := range input {
    		rc := parseRegionConstraint(line)
    		regionConstraints = append(regionConstraints, rc)
    	}
    
    	return problem{shapes, regionConstraints}
    }
    
    func (pb *problem) pruneRegionsTooSmall() {
    	toPrune := []int{}
    	for idx, rc := range pb.constraints {
    		availableArea := rc.height * rc.width
    		neededArea := 0
    		for shapeId, count := range rc.shapes {
    			neededArea += pb.shapes[shapeId].usedArea() * count
    			if neededArea > availableArea {
    				toPrune = append(toPrune, idx)
    				break
    			}
    		}
    	}
    
    	slices.Reverse(toPrune)
    	for _, idx := range toPrune {
    		pb.constraints = slices.Delete(pb.constraints, idx, idx+1)
    	}
    }
    
    func (pb *problem) pruneObviousCandidates() int {
    	toPrune := []int{}
    
    	for idx, rc := range pb.constraints {
    		maxShapePlacements := (rc.width / 3) * (rc.height / 3)
    		shapeCount := 0
    		for _, count := range rc.shapes {
    			shapeCount += count
    		}
    		if maxShapePlacements >= shapeCount {
    			toPrune = append(toPrune, idx)
    		}
    	}
    
    	slices.Reverse(toPrune)
    	for _, idx := range toPrune {
    		pb.constraints = slices.Delete(pb.constraints, idx, idx+1)
    	}
    
    	return len(toPrune)
    }
    
    func stepOne(input chan string) (int, error) {
    	pb := newProblem(input)
    	pb.pruneRegionsTooSmall()
    	obviousCandidates := pb.pruneObviousCandidates()
    
    	fmt.Println(pb)
    	fmt.Println(obviousCandidates)
    	return 0, nil
    }
    
    func stepTwo(input chan string) (int, error) {
    	return 0, nil
    }
    
    func main() {
    	input, err := utils.DownloadTodaysInputFile()
    	if err != nil {
    		_ = fmt.Errorf("error fetching the input: %v", err)
    	}
    
    	utils.RunStep(utils.ONE, input, stepOne)
    	utils.RunStep(utils.TWO, input, stepTwo)
    }
    


  • Go

    Yeeeaaay graphs! This one has been a pleasure to program and here comes my solution, with pruning of irrelevant branches (cursed nodes) and memoïzation for better performance.

    day11.go
    package main
    
    import (
    	"aoc/utils"
    	"fmt"
    	"slices"
    	"strings"
    )
    
    type graph map[string][]string
    
    func graphFromInput(input chan string) graph {
    	g := graph{}
    	for line := range input {
    		firstSplit := strings.Split(line, ":")
    		currentNode := firstSplit[0]
    		followers := strings.Fields(firstSplit[1])
    		g[currentNode] = followers
    	}
    	return g
    }
    
    var memo = make(map[string]map[string]int)
    
    func (g *graph) dfsUntil(currentNode, targetNode string, cursedNodes map[string]any) int {
    	subMemo, ok := memo[currentNode]
    	if !ok {
    		memo[currentNode] = make(map[string]int)
    	} else {
    		sum, ok := subMemo[targetNode]
    		if ok {
    			return sum
    		}
    	}
    
    	followers, _ := (*g)[currentNode]
    	sum := 0
    	for _, follower := range followers {
    		if follower == targetNode {
    			memo[currentNode][targetNode] = 1
    			return 1
    		} else if _, ok := cursedNodes[follower]; ok {
    			continue
    		}
    		sum += g.dfsUntil(follower, targetNode, cursedNodes)
    	}
    
    	memo[currentNode][targetNode] = sum
    	return sum
    }
    
    func stepOne(input chan string) (int, error) {
    	g := graphFromInput(input)
    	return g.dfsUntil("you", "out", make(map[string]any)), nil
    }
    
    func (g *graph) dfsFromSvrToOut(
    	currentNode string, hasDac, hasFft bool, cursedNodes map[string]any) int {
    
    	if currentNode == "dac" && hasFft {
    		return g.dfsUntil("dac", "out", cursedNodes)
    	}
    
    	hasDac = hasDac || currentNode == "dac"
    	hasFft = hasFft || currentNode == "fft"
    
    	followers := (*g)[currentNode]
    	sum := 0
    	for _, follower := range followers {
    		switch follower {
    		case "out":
    			if hasDac && hasFft {
    				return 1
    			} else {
    				return 0
    			}
    		default:
    			sum += g.dfsFromSvrToOut(follower, hasDac, hasFft, cursedNodes)
    		}
    	}
    	return sum
    }
    
    func (g *graph) getCursedNodes() map[string]any {
    	cursedNodes := make(map[string]any)
    
    	for node := range *g {
    		if node == "dac" || node == "fft" || node == "svr" {
    			continue
    		}
    
    		fftToNode := g.dfsUntil("fft", node, cursedNodes)
    		dacToNode := g.dfsUntil("dac", node, cursedNodes)
    		nodeToFft := g.dfsUntil(node, "fft", cursedNodes)
    		nodeToDac := g.dfsUntil(node, "dac", cursedNodes)
    
    		if dacToNode > 0 {
    			continue
    		}
    
    		if fftToNode > 0 && nodeToDac > 0 {
    			continue
    		}
    
    		if nodeToFft == 0 {
    			cursedNodes[node] = nil
    		}
    	}
    
    	return cursedNodes
    }
    
    func stepTwo(input chan string) (int, error) {
    	g := graphFromInput(input)
    	cursedNodes := g.getCursedNodes()
    	for cursedKey := range cursedNodes {
    		delete(g, cursedKey)
    		for gkey := range g {
    			g[gkey] = slices.DeleteFunc(g[gkey], func(n string) bool {
    				return n == cursedKey
    			})
    		}
    	}
    	memo = make(map[string]map[string]int)
    	sum := g.dfsUntil("svr", "out", nil)
    	return sum, nil
    }
    
    func main() {
    	input, err := utils.DownloadTodaysInputFile()
    	if err != nil {
    		_ = fmt.Errorf("%v\n", err)
    	}
    
    	utils.RunStep(utils.ONE, input, stepOne)
    	utils.RunStep(utils.TWO, input, stepTwo)
    }
    

    I have also finally written a function to automatically download the input file (which will prove useful for… ehm… tomorrow):

    download today's input file
    func DownloadTodaysInputFile() (FilePath, error) {
    	today := time.Now().Day()
    	url := fmt.Sprintf("https://adventofcode.com/2025/day/%d/input", today)
    
    	client := &http.Client{}
    	req, err := http.NewRequest("GET", url, nil)
    	if err != nil {
    		return FilePath(""), err
    	}
    
    	// const sessionValue = 'hehehehehehe'
    	req.Header.Set("Cookie", fmt.Sprintf("session=%s", sessionValue))
    	resp, err := client.Do(req)
    	if err != nil {
    		return FilePath(""), err
    	}
    
    	data, err := io.ReadAll(resp.Body)
    	if err != nil {
    		return FilePath(""), err
    	}
    
    	path := fmt.Sprintf("day%d.txt", today)
    	f, err := os.Create(path)
    	if err != nil {
    		return FilePath(""), err
    	}
    	defer f.Close()
    
    	_, err = f.Write(data)
    	if err != nil {
    		return FilePath(""), err
    	}
    
    	return FilePath(path), nil
    }
    

  • Go

    Linear programming all over again! I solved part 1 with a BFS like many. But for part 2 a proper solver was needed. I’m terrible at LP so I tried to use a simplex implementation, but it was not enough to find a solution, Z3 was needed but I couldn’t bother to install anything so… screw it.

    Here is my proposal part 2 doesn’t work but the modelisation is interesting enough that it is worth posting:

    day10.go
    package main
    
    import (
    	"aoc/utils"
    	"fmt"
    	"math"
    	"regexp"
    	"slices"
    	"strconv"
    	"strings"
    	"sync"
    
    	"gonum.org/v1/gonum/mat"
    	"gonum.org/v1/gonum/optimize/convex/lp"
    )
    
    type button []int8
    
    type problem struct {
    	want                int
    	buttons             []button
    	joltageRequirements []int8
    }
    
    func parseLights(lights string) (want int) {
    	want = 0
    	for idx, ch := range lights {
    		if ch == '#' {
    			want += (1 << idx)
    		}
    	}
    	return want
    }
    
    func parseProblem(line string) problem {
    	reg := regexp.MustCompile(`\[([.#]+)\] ((?:(?:\([^)]+\))\s*)+) {([^}]+)}`)
    	matches := reg.FindStringSubmatch(line)
    
    	lights := matches[1]
    	buttonString := matches[2]
    	joltage := matches[3]
    
    	buttonString = string(slices.DeleteFunc(
    		[]rune(buttonString),
    		func(ch rune) bool {
    			return ch == '(' || ch == ')'
    		}))
    
    	buttons := []button{}
    	for switchString := range strings.SplitSeq(buttonString, " ") {
    		switches := []int8{}
    		for sw := range strings.SplitSeq(switchString, ",") {
    			val, _ := strconv.Atoi(sw)
    			switches = append(switches, int8(val))
    		}
    		buttons = append(buttons, switches)
    	}
    
    	joltageRequirements := []int8{}
    	for req := range strings.SplitSeq(joltage, ",") {
    		val, _ := strconv.Atoi(req)
    		joltageRequirements = append(joltageRequirements, int8(val))
    	}
    
    	return problem{
    		want:                parseLights(lights),
    		buttons:             buttons,
    		joltageRequirements: joltageRequirements,
    	}
    }
    
    func getProblemChannel(input chan string) chan problem {
    	ch := make(chan problem, cap(input))
    	go func() {
    		for line := range input {
    			ch <- parseProblem(line)
    		}
    		close(ch)
    	}()
    	return ch
    }
    
    func (b button) value() (val int) {
    	val = 0
    	for _, sw := range b {
    		val += 1 << sw
    	}
    	return val
    }
    
    func bfs(goal int, buttons []button) int {
    	type bfsState struct {
    		epoch int8
    		value int
    	}
    
    	visited := []int{0}
    	queue := []bfsState{}
    
    	for _, bt := range buttons {
    		value := bt.value()
    		queue = append(queue, bfsState{epoch: 1, value: value})
    		visited = append(visited, value)
    	}
    
    	for len(queue) > 0 {
    		state := queue[0]
    		queue = queue[1:]
    
    		if state.value == goal {
    			return int(state.epoch)
    		}
    
    		for _, bt := range buttons {
    			nextValue := state.value ^ bt.value()
    			if !slices.Contains(visited, nextValue) {
    				visited = append(visited, nextValue)
    				queue = append(queue, bfsState{
    					epoch: state.epoch + 1,
    					value: nextValue,
    				})
    			}
    		}
    	}
    
    	return math.MaxInt
    }
    
    func (p problem) solve() int {
    	depth := bfs(p.want, p.buttons)
    	return int(depth)
    }
    
    func parallel(threadCount int, f func(thrId int)) {
    	var wg sync.WaitGroup
    	for id := range threadCount {
    		wg.Go(func() {
    			f(id)
    		})
    	}
    	wg.Wait()
    }
    
    func stepOne(input chan string) (int, error) {
    	sum := 0
    	pbChan := getProblemChannel(input)
    	for pb := range pbChan {
    		depth := pb.solve()
    		fmt.Println(depth)
    		sum += depth
    	}
    	return sum, nil
    }
    
    type linearEquation struct {
    	solution int
    	coeffs   []int
    }
    
    func (p problem) toLinearEquations() linearProblem {
    	equations := make([]linearEquation, len(p.joltageRequirements))
    	varCount := len(p.buttons)
    
    	for equIdx, solution := range p.joltageRequirements {
    		equ := linearEquation{
    			solution: int(solution),
    			coeffs:   make([]int, varCount),
    		}
    
    		for btIdx, bt := range p.buttons {
    			if slices.Contains(bt, int8(equIdx)) {
    				equ.coeffs[btIdx] = 1
    			}
    		}
    		equations[equIdx] = equ
    	}
    	return equations
    }
    
    type linearProblem []linearEquation
    
    func (lp linearProblem) A() mat.Matrix {
    	rows := len(lp)
    	cols := len(lp[0].coeffs)
    	data := make([]float64, rows*cols)
    
    	for r := range rows {
    		for c := range cols {
    			data[r*cols+c] = float64(lp[r].coeffs[c])
    		}
    		fmt.Println(data[r*cols : (r+1)*cols])
    	}
    
    	return mat.NewDense(rows, cols, data)
    }
    
    func (lp linearProblem) c() []float64 {
    	count := len(lp[0].coeffs)
    	consts := make([]float64, count)
    	for idx := range count {
    		consts[idx] = 1.0
    	}
    	return consts
    }
    
    func (lp linearProblem) b() []float64 {
    	bees := make([]float64, len(lp))
    	for idx := range bees {
    		bees[idx] = float64(lp[idx].solution)
    	}
    	return bees
    }
    
    func stepTwo(input chan string) (int, error) {
    	sum := 0
    	pbChan := getProblemChannel(input)
    	for pb := range pbChan {
    		lpb := pb.toLinearEquations()
    		c := lpb.c()
    		A := lpb.A()
    		b := lpb.b()
    
    		optF, _, err := lp.Simplex(c, A, b, 1.0, nil)
    		if err != nil {
    			fmt.Errorf("simplex error: %v\n", err)
    		}
    		fmt.Println(optF)
    		sum += int(optF)
    	}
    
    	return sum, nil
    }
    
    func main() {
    	input := utils.FilePath("day10.txt")
    	utils.RunStep(utils.ONE, input, stepOne)
    	utils.RunStep(utils.TWO, input, stepTwo)
    }
    

  • Go

    Oh my god. I’m calling: tomorrow will probably be the day I fail lmao. It has been so hard to get a correct result. Then I spent a solid half hour to optimize the solution so that :

    • it terminates
    • it doesn’t need 600GB of RAM

    It seems that using a int8 to encode color instead of the default int64 was the right idea lmao. I’m also happy to have understood how to multithread a Go application and I’m totally bought by the simplicity. This language really helped me by abstracting everything in goroutines and call it a day.

    I have also been able to create small goroutines which job was to generate values one by one and abstract the real control-flow in order not to allocate a whole big array at once, reducing further my memory footprint.

    This is the second time in my life I want to say I loved using Go. Last time was when I waited to pretty print json files, my colleague told me how to write a wannabe-oneliner in Go to do it myself. Never looked back.

    day09.go
    package main
    
    import (
    	"aoc/utils"
    	"math"
    	"slices"
    	"strconv"
    	"strings"
    	"sync"
    )
    
    type point struct {
    	x, y int
    }
    
    func (p point) areaUntil(other point) int {
    	dx := other.x - p.x
    	if dx < 0 {
    		dx = -dx
    	}
    	dy := other.y - p.y
    	if dy < 0 {
    		dy = -dy
    	}
    
    	return (dx + 1) * (dy + 1)
    }
    
    func parsePoint(line string) point {
    	parts := strings.Split(line, ",")
    	x, _ := strconv.Atoi(parts[0])
    	y, _ := strconv.Atoi(parts[1])
    	return point{x, y}
    }
    
    func getPointChannel(input chan string) chan point {
    	ch := make(chan point, cap(input))
    
    	go func() {
    		for line := range input {
    			ch <- parsePoint(line)
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    func stepOne(input chan string) (int, error) {
    	points := []point{}
    	maxArea := math.MinInt
    
    	for p := range getPointChannel(input) {
    		points = append(points, p)
    
    		if len(points) == 1 {
    			continue
    		}
    
    		areas := make([]int, len(points))
    		for idx, p2 := range points {
    			areas[idx] = p.areaUntil(p2)
    		}
    
    		max := slices.Max(areas)
    		if max > maxArea {
    			maxArea = max
    		}
    	}
    
    	return maxArea, nil
    }
    
    type color int8
    
    const (
    	white color = iota
    	green
    	red
    )
    
    type matrix struct {
    	tiles  [][]color
    	points []point
    }
    
    func min(x, y int) int {
    	if x < y {
    		return x
    	} else {
    		return y
    	}
    }
    
    func max(x, y int) int {
    	if x > y {
    		return x
    	} else {
    		return y
    	}
    }
    
    func (m *matrix) squareContainsWhiteTiles(sq segment) bool {
    	xMin := min(sq[0].x, sq[1].x)
    	yMin := min(sq[0].y, sq[1].y)
    	xMax := max(sq[0].x, sq[1].x)
    	yMax := max(sq[0].y, sq[1].y)
    
    	for y := yMin; y < yMax; y++ {
    		if m.tiles[y][xMin] == white || m.tiles[y][xMax] == white {
    			return true
    		}
    	}
    
    	for x := xMin; x < xMax; x++ {
    		if m.tiles[yMin][x] == white || m.tiles[yMax][x] == white {
    			return true
    		}
    	}
    
    	return false
    }
    
    func (m *matrix) addReds() {
    	for _, p := range m.points {
    		m.addRed(p)
    	}
    }
    
    func newMatrix(points []point) (m matrix) {
    	coords := getMatrixSize(points)
    	rows := coords[1] + 1
    	cols := coords[0] + 1
    	array := make([][]color, rows)
    	for idx := range array {
    		array[idx] = make([]color, cols)
    	}
    	m = matrix{
    		tiles:  array,
    		points: points,
    	}
    
    	return m
    }
    
    func (m *matrix) addRed(p point) {
    	m.tiles[p.y][p.x] = red
    }
    
    func (m *matrix) makeBorders() {
    	for i := 0; i < len(m.points)-1; i++ {
    		m.makeBorder(m.points[i], m.points[i+1])
    	}
    	m.makeBorder(m.points[len(m.points)-1], m.points[0])
    }
    
    func (m *matrix) makeBorder(p1 point, p2 point) {
    	if p1.x == p2.x {
    		m.makeVerticalBorder(p1.x, p1.y, p2.y)
    	} else {
    		m.makeHorizontalBorder(p1.x, p2.x, p1.y)
    	}
    }
    
    func (m *matrix) makeHorizontalBorder(x1, x2, y int) {
    	if x1 > x2 {
    		tmp := x1
    		x1 = x2
    		x2 = tmp
    	}
    
    	for i := x1; i <= x2; i++ {
    		if m.tiles[y][i] == white {
    			m.tiles[y][i] = green
    		}
    	}
    }
    
    func (m *matrix) makeVerticalBorder(x, y1, y2 int) {
    	if y1 > y2 {
    		tmp := y1
    		y1 = y2
    		y2 = tmp
    	}
    
    	for i := y1; i <= y2; i++ {
    		if m.tiles[i][x] == white {
    			m.tiles[i][x] = green
    		}
    	}
    }
    
    func (m *matrix) makeGreens() {
    	m.makeBorders()
    
    	iterCount := len(m.tiles) / MagicThreadCount
    
    	parallel(func(thrId int) {
    		for i := range iterCount {
    			row := m.tiles[iterCount*thrId+i]
    			inside := false
    			lastWasWhite := false
    
    			for idx, cell := range row {
    				switch cell {
    				case white:
    					if inside {
    						row[idx] = green
    					}
    				default:
    					if lastWasWhite {
    						inside = !inside
    					}
    				}
    				lastWasWhite = cell == white
    			}
    		}
    	})
    }
    
    func getMatrixSize(points []point) [2]int {
    	var x, y int
    	for _, p := range points {
    		if p.x > x {
    			x = p.x
    		}
    		if p.y > y {
    			y = p.y
    		}
    	}
    	return [2]int{x, y}
    }
    
    func (m *matrix) String() string {
    	iterCount := len(m.points) / MagicThreadCount
    	lines := make([]string, len(m.tiles))
    
    	parallel(func(thrId int) {
    		for i := range iterCount {
    			row := m.tiles[iterCount*thrId+i]
    			runes := make([]rune, len(row))
    			for idx, col := range row {
    				switch col {
    				case white:
    					runes[idx] = '.'
    				case green:
    					runes[idx] = 'X'
    				case red:
    					runes[idx] = '#'
    				}
    			}
    			lines[iterCount*thrId+i] = string(runes)
    		}
    	})
    
    	return strings.Join(lines, "\n")
    }
    
    type segment [2]point
    
    func newSegment(p1, p2 point) segment {
    	seg := [2]point{p1, p2}
    	return seg
    }
    
    func (m *matrix) allValidSquaresWith(p1 point) chan segment {
    	ch := make(chan segment)
    
    	go func() {
    		for _, p2 := range m.points {
    			seg := newSegment(p1, p2)
    			if p2 != p1 {
    				ch <- seg
    			}
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    // 495 == 55 * 9
    // 495 == 99 * 5
    // 495 == 165 * 3
    var MagicThreadCount = 9
    
    func parallel(f func(int)) {
    	var wg sync.WaitGroup
    	for thrId := range MagicThreadCount {
    		wg.Go(func() {
    			f(thrId)
    		})
    	}
    
    	wg.Wait()
    }
    
    func stepTwo(input chan string) (int, error) {
    	points := []point{}
    	for p := range getPointChannel(input) {
    		points = append(points, p)
    	}
    	m := newMatrix(points)
    	m.addReds()
    	m.makeGreens()
    
    	iterCount := len(m.points) / MagicThreadCount
    	boys := make([]int, MagicThreadCount)
    
    	parallel(func(thrId int) {
    		largestBoy := math.MinInt
    		for i := range iterCount {
    			p := m.points[iterCount*thrId+i]
    
    			squareCh := m.allValidSquaresWith(p)
    			for sq := range squareCh {
    				area := sq[0].areaUntil(sq[1])
    				if area > largestBoy && !m.squareContainsWhiteTiles(sq) {
    					largestBoy = area
    				}
    			}
    		}
    		boys[thrId] = largestBoy
    	})
    
    	return slices.Max(boys), nil
    }
    
    func main() {
    	input := utils.FilePath("day09.txt")
    	utils.RunStep(utils.ONE, input, stepOne)
    	utils.RunStep(utils.TWO, input, stepTwo)
    }
    

  • Go

    God damn it, I thought I would never see the end of part 1. I had a hard time finding a good representation and then I failed at not eliminating valid distances etc. The code is quite messy, but I did it!!!

    day08.go
    package main
    
    import (
    	"aoc/utils"
    	"cmp"
    	"math"
    	"slices"
    	"strconv"
    	"strings"
    )
    
    type pos3D struct {
    	x, y, z int
    }
    
    func (p pos3D) Compare(other pos3D) int {
    	dx := cmp.Compare(p.x, other.x)
    	if dx != 0 {
    		return dx
    	}
    
    	dy := cmp.Compare(p.y, other.y)
    	if dy != 0 {
    		return dy
    	}
    
    	return cmp.Compare(p.z, other.z)
    }
    
    func (p pos3D) distance(other pos3D) float64 {
    	dx := float64(other.x - p.x)
    	dy := float64(other.y - p.y)
    	dz := float64(other.z - p.z)
    
    	d2 := math.Pow(dx, 2.0) + math.Pow(dy, 2.0) + math.Pow(dz, 2.0)
    	return math.Sqrt(d2)
    }
    
    func getPosChannel(input chan string) chan pos3D {
    	ch := make(chan pos3D, cap(input))
    
    	go func() {
    		for line := range input {
    			parts := strings.Split(line, ",")
    			x, _ := strconv.Atoi(parts[0])
    			y, _ := strconv.Atoi(parts[1])
    			z, _ := strconv.Atoi(parts[2])
    			ch <- pos3D{x, y, z}
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    type circuits struct {
    	circuits map[pos3D]int
    	nextID   int
    }
    
    func (cc *circuits) newCircuit() (id int) {
    	id = cc.nextID
    	cc.nextID++
    	return id
    }
    
    func (cc *circuits) mergeCircuits(id1, id2 int) {
    	for p, id := range cc.circuits {
    		if id == id2 {
    			cc.circuits[p] = id1
    		}
    	}
    }
    
    func createSingletonCircuits(points []pos3D) (cc circuits) {
    	cc.circuits = make(map[pos3D]int)
    	for _, p := range points {
    		id := cc.newCircuit()
    		cc.circuits[p] = id
    	}
    	return cc
    }
    
    func (cc circuits) reverseMap() (m map[int][]pos3D) {
    	m = make(map[int][]pos3D)
    
    	for p, id := range cc.circuits {
    		if _, ok := m[id]; !ok {
    			m[id] = []pos3D{}
    		}
    
    		m[id] = append(m[id], p)
    	}
    
    	return m
    }
    
    func (cc circuits) sizeMap() map[int]int {
    	circuitSizeMap := make(map[int]int)
    	for _, id := range cc.circuits {
    		circuitSizeMap[id]++
    	}
    	return circuitSizeMap
    }
    
    type targetedDistance struct {
    	distance float64
    	p1, p2   pos3D
    }
    
    func (p pos3D) distanceWithAll(
    	points []pos3D,
    	alreadyVisited *map[pos3D]any,
    ) (tds []targetedDistance) {
    	tds = []targetedDistance{}
    	for _, op := range points {
    		if _, ok := (*alreadyVisited)[op]; ok {
    			continue
    		}
    
    		var td targetedDistance
    		td.distance = math.MaxFloat64
    		td.p1 = p
    		d := p.distance(op)
    		if d < td.distance {
    			td.distance = d
    			td.p2 = op
    		}
    		tds = append(tds, td)
    	}
    	return tds
    }
    
    type pointPair [2]pos3D
    
    func (pp pointPair) equals(other pointPair) bool {
    	pp1 := pp[0]
    	pp2 := pp[1]
    	o1 := other[0]
    	o2 := other[1]
    	return (pp1 == o1 && pp2 == o2) || (pp1 == o2 && pp2 == o1)
    }
    
    var IterationCount = 1000
    
    func stepOne(input chan string) (int, error) {
    	ch := getPosChannel(input)
    
    	points := []pos3D{}
    	for p := range ch {
    		points = append(points, p)
    	}
    	slices.SortFunc(points, pos3D.Compare)
    	cc := createSingletonCircuits(points)
    
    	alreadyVisited := make(map[pos3D]any)
    	tds := []targetedDistance{}
    	for _, p := range points {
    		alreadyVisited[p] = nil
    		dsts := p.distanceWithAll(points, &alreadyVisited)
    		tds = append(tds, dsts...)
    	}
    
    	slices.SortFunc(tds, func(a, b targetedDistance) int {
    		return cmp.Compare(a.distance, b.distance)
    	})
    
    	for idx := range IterationCount {
    		td := tds[idx]
    		cc.mergeCircuits(cc.circuits[td.p1], cc.circuits[td.p2])
    	}
    
    	circuitSizeMap := cc.sizeMap()
    
    	circuitSizes := []int{}
    	for _, v := range circuitSizeMap {
    		circuitSizes = append(circuitSizes, v)
    	}
    	slices.Sort(circuitSizes)
    	largestThree := circuitSizes[len(circuitSizes)-3:]
    
    	product := 1
    	for _, v := range largestThree {
    		product *= v
    	}
    
    	return product, nil
    }
    
    func stepTwo(input chan string) (int, error) {
    	ch := getPosChannel(input)
    
    	points := []pos3D{}
    	for p := range ch {
    		points = append(points, p)
    	}
    	slices.SortFunc(points, pos3D.Compare)
    	cc := createSingletonCircuits(points)
    
    	alreadyVisited := make(map[pos3D]any)
    	tds := []targetedDistance{}
    	for _, p := range points {
    		alreadyVisited[p] = nil
    		dsts := p.distanceWithAll(points, &alreadyVisited)
    		tds = append(tds, dsts...)
    	}
    
    	slices.SortFunc(tds, func(a, b targetedDistance) int {
    		return cmp.Compare(a.distance, b.distance)
    	})
    
    	idx := 0
    	var lastConnection pointPair
    
    	for {
    		td := tds[idx]
    		idx++
    		cc.mergeCircuits(cc.circuits[td.p1], cc.circuits[td.p2])
    
    		circuitSizeMap := cc.sizeMap()
    		if len(circuitSizeMap) == 1 {
    			lastConnection = pointPair{td.p1, td.p2}
    			break
    		}
    	}
    
    	return lastConnection[0].x * lastConnection[1].x, nil
    }
    
    func main() {
    	inputFile := utils.FilePath("day08.txt")
    	utils.RunStep(utils.ONE, inputFile, stepOne)
    	utils.RunStep(utils.TWO, inputFile, stepTwo)
    }
    



  • Go

    Oh my scheduling God! Part 1 was easy. Then for part 2 I started tracing each particle one by one using goroutines, but spawning billions of goroutines seemed to make my poor laptop sad. So I implemented a whole thread pool with process management and stuff, but nothing worked. So at the end I started doing the unthinkable: using my brain.

    It seems we can just reuse the same idea as part 1 one but with a clever counting scheme. Thing works and is as fast as part 1. I’m both happy and deeply sad not to have been able to leverage Go’s supposed killer features - which are actually rarely useful when programming things other than servers tbh.

    Here we goooooo:

    day07.go
    package main
    
    import (
    	"aoc/utils"
    )
    
    func parseStartLine(line string) []bool {
    	runes := []rune(line)
    	beams := make([]bool, len(runes))
    
    	for idx, char := range runes {
    		if char == 'S' {
    			beams[idx] = true
    		}
    	}
    
    	return beams
    }
    
    func stepOne(input chan string) (int, error) {
    	beams := parseStartLine(<-input)
    	splitCount := 0
    
    	for line := range input {
    		runes := []rune(line)
    		for idx, char := range runes {
    			if char == '^' && beams[idx] {
    				splitCount++
    				if idx > 0 {
    					beams[idx-1] = true
    				}
    				if idx < len(runes)-1 {
    					beams[idx+1] = true
    				}
    				beams[idx] = false
    			}
    		}
    	}
    
    	return splitCount, nil
    }
    
    func valueBeams(beams []bool) []int {
    	valBeams := make([]int, len(beams))
    
    	for idx, b := range beams {
    		val := 0
    		if b {
    			val = 1
    		}
    		valBeams[idx] = val
    	}
    
    	return valBeams
    }
    
    func stepTwo(input chan string) (int, error) {
    	beams := valueBeams(parseStartLine(<-input))
    
    	for line := range input {
    		runes := []rune(line)
    		for idx, char := range runes {
    			bc := beams[idx]
    			if char == '^' && bc > 0 {
    				beams[idx] = 0
    				if idx > 0 {
    					beams[idx-1] += bc
    				}
    				if idx < len(runes)-1 {
    					beams[idx+1] += bc
    				}
    			}
    		}
    	}
    
    	sum := 0
    	for _, bc := range beams {
    		sum += bc
    	}
    
    	return sum, nil
    }
    
    func main() {
    	inputFile := utils.FilePath("day07.txt")
    	utils.RunStep(utils.ONE, inputFile, stepOne)
    	utils.RunStep(utils.TWO, inputFile, stepTwo)
    }
    
    


  • Go

    Damn, I actually reeaally enjoyed this one! I didn’t expect the twist of part 2, but somehow it wasn’t that hard to manage.

    Here is my modern solution:

    day06.go
    package main
    
    import (
    	"aoc/utils"
    	"fmt"
    	"regexp"
    	"slices"
    	"strconv"
    	"strings"
    )
    
    type operation int
    
    func (o operation) compute(values []int) int {
    	switch o {
    	case add:
    		sum := 0
    		for _, val := range values {
    			sum += val
    		}
    		return sum
    	case mul:
    		product := 1
    		for _, val := range values {
    			product *= val
    		}
    		return product
    	}
    
    	return 0
    }
    
    const (
    	add operation = iota
    	mul
    )
    
    var allOperationSymbols = []string{"+", "*"}
    
    func operationFromSymbol(sym string) operation {
    	switch sym {
    	case "+":
    		return add
    	case "*":
    		return mul
    	default:
    		panic(fmt.Sprintf("wtf is a %s?", sym))
    	}
    }
    
    type problems struct {
    	values     [][]int
    	operations []operation
    }
    
    func (p *problems) feed(column string) {
    	last := string(column[len(column)-1])
    	done := false
    
    	if slices.Contains(allOperationSymbols, last) {
    		p.operations = append(p.operations, operationFromSymbol(last))
    		column = column[:len(column)-1]
    		done = true
    	}
    
    	val, _ := strconv.Atoi(strings.TrimSpace(column))
    	idx := len(p.values) - 1
    	p.values[idx] = append(p.values[idx], val)
    
    	if done {
    		p.values = append(p.values, []int{})
    	}
    }
    
    func (p *problems) addLine(line string) (done bool) {
    	parts := strings.Split(line, " ")
    	parts = slices.DeleteFunc(parts, func(elem string) bool {
    		return elem == ""
    	})
    
    	if slices.Contains(allOperationSymbols, parts[0]) {
    		p.operations = make([]operation, len(parts))
    		for idx, sym := range parts {
    			p.operations[idx] = operationFromSymbol(sym)
    		}
    		done = true
    	} else {
    		if len(p.values) == 0 {
    			lenparts := len(parts)
    			p.values = make([][]int, lenparts)
    			for idx := range lenparts {
    				p.values[idx] = []int{}
    			}
    		}
    
    		for idx, part := range parts {
    			num, _ := strconv.Atoi(part)
    			p.values[idx] = append(p.values[idx], num)
    		}
    		done = false
    	}
    
    	return done
    }
    
    func (p problems) solve() []int {
    	solutions := make([]int, len(p.values))
    
    	for idx, values := range p.values {
    		op := p.operations[idx]
    		solutions[idx] = op.compute(values)
    	}
    
    	return solutions
    }
    
    func stepOne(input chan string) (int, error) {
    	modernProblems := problems{}
    	for line := range input {
    		done := modernProblems.addLine(line)
    		if done {
    			break
    		}
    	}
    
    	modernSolutions := modernProblems.solve()
    	sum := 0
    	for _, solution := range modernSolutions {
    		sum += solution
    	}
    
    	return sum, nil
    }
    
    func transposeInputChan(input chan string) []string {
    	lines := [][]rune{}
    	for line := range input {
    		lines = append(lines, []rune(line))
    	}
    
    	linecount := len(lines)
    	columncount := len(lines[0])
    	transposed := make([][]rune, columncount)
    
    	for idx := range transposed {
    		transposed[idx] = make([]rune, linecount)
    	}
    
    	for row, line := range lines {
    		for col, char := range line {
    			transposed[col][row] = char
    		}
    	}
    
    	columns := make([]string, len(transposed))
    	for idx, col := range transposed {
    		columns[idx] = string(col)
    	}
    
    	return columns
    }
    
    func stepTwo(input chan string) (int, error) {
    	transposedInput := transposeInputChan(input)
    	slices.Reverse(transposedInput)
    
    	// problem-set with one empty problem.
    	modernProblems := problems{
    		values: [][]int{[]int{}},
    	}
    
    	for _, column := range transposedInput {
    		if matched, _ := regexp.MatchString("^\\s*$", column); matched {
    			continue
    		}
    
    		modernProblems.feed(column)
    	}
    
    	// Remove last useless empty problem.
    	modernProblems.values = modernProblems.values[:len(modernProblems.values)-1]
    
    	modernSolutions := modernProblems.solve()
    	sum := 0
    	for _, solution := range modernSolutions {
    		sum += solution
    	}
    
    	return sum, nil
    }
    
    func main() {
    	inputFile := utils.FilePath("day06.txt")
    	utils.RunStep(utils.ONE, inputFile, stepOne)
    	utils.RunStep(utils.TWO, inputFile, stepTwo)
    }
    

  • Go

    I started by not sorting the ranges in the input stream and try to merge everything together. It didn’t work, but the same algorithm with sorted input does. I guess I’m missing something important, but I tested my code and can’t find the corner case that make it fail. Bah…

    day05.go
    package main
    
    import (
    	"aoc/utils"
    	"cmp"
    	"fmt"
    	"slices"
    	"strconv"
    	"strings"
    )
    
    type idrange struct {
    	start, end int
    }
    
    func (rng idrange) contains(entry int) bool {
    	return rng.start <= entry && entry <= rng.end
    }
    
    func (rng idrange) length() int {
    	return (rng.end - rng.start) + 1
    }
    
    type rangeSet []idrange
    
    func (r *rangeSet) addRange(rng idrange) {
    	containsStartIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    		return elem.contains(rng.start)
    	})
    	containsEndIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    		return elem.contains(rng.end)
    	})
    
    	// The range overlaps to other ranges.
    	if containsStartIdx != -1 && containsEndIdx != -1 {
    		// If it is in fact contained inside one range, it is ignored.
    		if containsStartIdx == containsEndIdx {
    			return
    		}
    
    		before := (*r)[containsStartIdx]
    		after := (*r)[containsEndIdx]
    		before.end = after.end
    		(*r)[containsStartIdx] = before
    		*r = slices.Delete(*r, containsStartIdx+1, containsEndIdx+1)
    	} else if containsEndIdx != -1 {
    		// If the range's end overlaps with another range, that range is
    		// extended on the front to start like the range in argument.
    		after := (*r)[containsEndIdx]
    		after.start = rng.start
    		(*r)[containsEndIdx] = after
    
    		smallestAfterIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    			return rng.start < elem.start
    		})
    
    		if smallestAfterIdx != -1 && smallestAfterIdx != containsEndIdx+1 {
    			*r = slices.Delete(*r, smallestAfterIdx, containsEndIdx)
    		}
    	} else if containsStartIdx != -1 {
    		// If the range's start overlaps with another range, that range is
    		// extended on the back to start like the range in argument.
    		before := (*r)[containsStartIdx]
    		before.end = rng.end
    		(*r)[containsStartIdx] = before
    
    		smallestAfterIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    			return rng.end < elem.end
    		})
    
    		if smallestAfterIdx != -1 && smallestAfterIdx != containsStartIdx+1 {
    			*r = slices.Delete(*r, containsStartIdx+1, smallestAfterIdx)
    		}
    	} else {
    		// If the range is standalone, it is added at the right position.
    		afterIdx := slices.IndexFunc(*r, func(elem idrange) bool {
    			return rng.start < elem.start
    		})
    
    		if afterIdx == -1 {
    			*r = append(*r, rng)
    		} else {
    			*r = slices.Insert(*r, afterIdx, rng)
    		}
    	}
    }
    
    func (r rangeSet) contains(entry int) bool {
    	for _, rng := range r {
    		if rng.contains(entry) {
    			return true
    		}
    	}
    
    	return false
    }
    
    func (r rangeSet) length() int {
    	sum := 0
    	for _, rng := range r {
    		sum += rng.length()
    	}
    	return sum
    }
    
    func parseRange(line string) (idrange, error) {
    	bounds := strings.Split(line, "-")
    	start, err1 := strconv.Atoi(bounds[0])
    	end, err2 := strconv.Atoi(bounds[1])
    
    	if err1 != nil {
    		return idrange{}, err1
    	}
    	if err2 != nil {
    		return idrange{}, err2
    	}
    
    	return idrange{
    		start: start,
    		end:   end,
    	}, nil
    }
    
    func parseRangeSet(input chan string) (rangeSet, error) {
    	rangeSet := rangeSet{}
    	ranges := []idrange{}
    
    	for line := range input {
    		if line == "" {
    			break
    		}
    
    		rng, err := parseRange(line)
    		if err != nil {
    			return nil, err
    		}
    
    		ranges = append(ranges, rng)
    	}
    
    	slices.SortFunc(ranges, func(a, b idrange) int {
    		return cmp.Compare(a.start, b.start)
    	})
    
    	for _, rng := range ranges {
    		rangeSet.addRange(rng)
    	}
    
    	return rangeSet, nil
    }
    
    func getNumberChannel(input chan string) chan int {
    	ch := make(chan int, cap(input))
    
    	go func() {
    		for line := range input {
    			num, _ := strconv.Atoi(line)
    			ch <- num
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    func stepOne(input chan string) (int, error) {
    	rngSet, errParse := parseRangeSet(input)
    	if errParse != nil {
    		return 0, errParse
    	}
    
    	numCh := getNumberChannel(input)
    	count := 0
    
    	for entry := range numCh {
    		if rngSet.contains(entry) {
    			count++
    		}
    	}
    
    	return count, nil
    }
    
    func stepTwo(input chan string) (int, error) {
    	rngSet, err := parseRangeSet(input)
    	if err != nil {
    		return 0, err
    	}
    
    	return rngSet.length(), nil
    }
    
    func main() {
    	inputFile := utils.FilePath("day05.txt")
    	utils.RunStep(utils.ONE, inputFile, stepOne)
    	utils.RunStep(utils.TWO, inputFile, stepTwo)
    }
    


  • Je suis 100% d’accord et je vais pas du tout minimiser les faits, au contraire c’est le signe d’à quel point la culture du viol est ancrée et banalisée dans notre société.

    Mais du coup, c’est assez classique chez les victimes de se percevoir comme bien moins victime qu’elles ne sont. C’est aussi un moyen de se protéger. Qui plus est quand l’agression semble si peu “invasive”, surtout comparé à l’image fantasmé qu’on a d’une agression sexuelle violente pénétrative etc etc.

    J’ai aussi subi des agressions, des plus ou moins “charnelles”, d’autres où on m’a à peine touchée. Une qu’on peut caractériser de viol. Si j’en parle, si j’explique ce qui s’est passé à des gens IRL, on minimise. On m’explique que c’est pas ça. Que c’est une mauvaise blague, un geste déplacé etc. Et c’est pareil pour tout le monde. Donc on intériorise, on fait sien ce récit.

    Là cet homme ne l’a même pas touchée, elle sait même pas quoi faire de ce qu’elle a subi. C’est pas nommable dans le paradigme de la culture du viol (si, mais tu m’as comprise). Je serais pas étonnée qu’il lui ait fallu des années pour ne serait-ce que comprendre que c’était une agression (sans même parler du côté sexuel). En tout cas je la comprends, je compatis. J’espère qu’elle trouvera toute l’aide dont elle a besoin.

    Et lui… Bah, certaines femmes ont trouvé d’autres méthodes que la “justice”, je me demande si j’avais vu passer l’article ici ou sur reddit. Mais voilà


  • Go

    package main
    
    import (
    	"aoc/utils"
    	"fmt"
    )
    
    type rollMap [][]bool
    
    func (rm *rollMap) addRow(line string) {
    	runes := []rune(line)
    	row := make([]bool, len(runes))
    
    	for idx, r := range runes {
    		row[idx] = r == '@'
    	}
    
    	*rm = append(*rm, row)
    }
    
    func (rm rollMap) getAdjacentContents(row, col int) []bool {
    	contents := []bool{}
    	lenrow := len(rm[0])
    	lenrm := len(rm)
    
    	for _, i := range []int{-1, 0, 1} {
    		for _, j := range []int{-1, 0, 1} {
    			if i == 0 && j == 0 {
    				continue
    			}
    
    			r := row + i
    			c := col + j
    			if r >= 0 && r < lenrm && c >= 0 && c < lenrow {
    				contents = append(contents, rm[r][c])
    			}
    		}
    	}
    
    	return contents
    }
    
    func countTrue(array []bool) int {
    	count := 0
    	for _, val := range array {
    		if val {
    			count++
    		}
    	}
    	return count
    }
    
    func createMap(input chan string) rollMap {
    	rm := rollMap{}
    
    	for line := range input {
    		rm.addRow(line)
    	}
    
    	return rm
    }
    
    func stepOne(input chan string) (int, error) {
    	rm := createMap(input)
    	lenrow := len(rm[0])
    	accessibleRolls := 0
    
    	for row := range rm {
    		for col := range lenrow {
    			if rm[row][col] {
    				adj := rm.getAdjacentContents(row, col)
    				numRolls := countTrue(adj)
    				if numRolls < 4 {
    					accessibleRolls++
    				}
    			}
    		}
    	}
    
    	return accessibleRolls, nil
    }
    
    type position struct {
    	row, col int
    }
    
    func stepTwo(input chan string) (int, error) {
    	rm := createMap(input)
    	lenrow := len(rm[0])
    	accessibleRolls := 0
    
    	for true {
    		accessedRolls := []position{}
    		for row := range rm {
    			for col := range lenrow {
    				if rm[row][col] {
    					adj := rm.getAdjacentContents(row, col)
    					numRolls := countTrue(adj)
    					if numRolls < 4 {
    						accessibleRolls++
    						accessedRolls = append(accessedRolls, position{
    							row: row,
    							col: col,
    						})
    					}
    				}
    			}
    		}
    		if len(accessedRolls) == 0 {
    			break
    		} else {
    			for _, pos := range accessedRolls {
    				rm[pos.row][pos.col] = false
    			}
    		}
    	}
    
    	return accessibleRolls, nil
    }
    
    func main() {
    	input := utils.FilePath("day04.txt")
    	ch1, err1 := input.GetLineChannel()
    	if err1 != nil {
    		fmt.Errorf("step one error: %s\n", err1)
    		return
    	}
    
    	val1, errStep1 := stepOne(ch1)
    	if errStep1 != nil {
    		fmt.Errorf("step one error: %s\n", errStep1)
    		return
    	}
    
    	fmt.Printf("Step one result: %d\n", val1)
    
    	ch2, err2 := input.GetLineChannel()
    	if err2 != nil {
    		fmt.Errorf("step two error: %s\n", err2)
    		return
    	}
    
    	val2, errStep2 := stepTwo(ch2)
    	if errStep2 != nil {
    		fmt.Errorf("step two error: %s\n", errStep2)
    		return
    	}
    
    	fmt.Printf("Step two result: %d\n", val2)
    }