Day 9: Movie Theater

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • Camille@lemmy.ml
    link
    fedilink
    arrow-up
    3
    ·
    18 hours ago

    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)
    }