• 2 Posts
  • 37 Comments
Joined 2 years ago
cake
Cake day: December 21st, 2023

help-circle
  • 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)
    }
    

  • Yeah! I used to look like my father. Like, it seemed like I stole all his genetics. But now, I’m a textbook clone of my mom. Their faces have some similarities, they are not like total opposite do to say. But going from one to the another is wild.

    Meanwhile my brother has always had his own personal 100% authentic face. Bro has so much personality. I’m just mom and dad’s clone lmao.

    (I’m fine with it, my face is cute)



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



  • Oh damn, thank you! I didn’t know that and it will most probably serve as a starting block to create the exact thing I need. Everything is still confuse is my head, I’m still not sure what I really want, but you clearly helped me. Thank you so much.

    If it were me, I would probably create an .html file, add a <script> block, and use the fetch API to fetch the data from the backend and then render/display it via JS/HTML. It’s always possible to size up and add complexity later.

    It is totally the kind of things I’d want to do, just an HTML file, some scripts to populate it and call it a day.


  • Are you saying you could add HTTP endpoints to your services which can then be queried for data? So REST APIs for example?

    Yes, this is more or less what I’m thinking of.

    Do you want live updates on the UI or is a fetch visualization enough?

    Well, depends on what I am doing. Typically, if I’m working on a parser for a new language for instance, i think live update would be a confortable addition. But I guess that any decent JS framework would easily handle processing an input text change event into querying the backend etc.

    I didn’t know Open telemetry. I’ll take a look at it.


    Given that I can setup my backend application to deliver the information (a REST API must be enough most of the time I think), what stack would you use to write the frontend asap? What I mean is I don’t want to create an entire Angular project and setup multiple files of stuff to have a minimal visualization of my outputs. I’ve written some Vue.js back then, it was ok. I wonder if we can do even simpler today (with a framework that handles the dynamic aspects of the application, not with barebones JS).

    Also, do we still use Bootstrap to have an out-of-the-box CSS with minimal effort? Is there something newer or better?

    Thank you so much for your answer



  • Go

    I usually write a little helper library to bootstrap the input reading process and sometimes even the downloading of the input file. So here it is:

    utils.go
    package utils
    
    import (
    	"bufio"
    	"os"
    	"strings"
    )
    
    type Input interface {
    	GetLineChannel() (chan string, error)
    }
    
    type FilePath string
    type InputText string
    
    func (path FilePath) GetLineChannel() (chan string, error) {
    	file, err := os.Open(string(path))
    	if err != nil {
    		return nil, err
    	}
    
    	scanner := bufio.NewScanner(file)
    
    	ch := make(chan string, 1024)
    	go (func() {
    		defer file.Close()
    
    		for scanner.Scan() {
    			ch <- scanner.Text()
    		}
    
    		close(ch)
    	})()
    
    	return ch, nil
    }
    
    func (inputText InputText) GetLineChannel() (chan string, error) {
    	lines := strings.Split(string(inputText), "\n")
    	ch := make(chan string, len(lines))
    
    	go (func() {
    		for _, line := range lines {
    			ch <- line
    		}
    
    		close(ch)
    	})()
    
    	return ch, nil
    }
    

    And here comes the solution to day 3:

    package main
    
    import (
    	"aoc/utils"
    	"errors"
    	"fmt"
    	"math"
    )
    
    const inputText = `987654321111111
    811111111111119
    234234234234278
    818181911112111`
    
    type bank []int
    
    func (bk bank) largestNDigitJoltage(n int) int {
    	digits := make([]int, n)
    	count := 0
    	idx := 0
    
    	lenbk := len(bk)
    
    	for range n {
    		for i := idx; i < lenbk-(n-count-1); i++ {
    			val := bk[i]
    			if val > digits[count] {
    				idx = i + 1
    				digits[count] = val
    			}
    		}
    		count++
    	}
    
    	sum := 0
    	for index, val := range digits {
    		sum += val * int(math.Pow10(n-index-1))
    	}
    
    	return sum
    }
    
    func readBank(line string) (bank, error) {
    	runes := []rune(line)
    	bk := make(bank, len(runes))
    	for idx, c := range runes {
    		switch c {
    		case '0':
    			bk[idx] = 0
    		case '1':
    			bk[idx] = 1
    		case '2':
    			bk[idx] = 2
    		case '3':
    			bk[idx] = 3
    		case '4':
    			bk[idx] = 4
    		case '5':
    			bk[idx] = 5
    		case '6':
    			bk[idx] = 6
    		case '7':
    			bk[idx] = 7
    		case '8':
    			bk[idx] = 8
    		case '9':
    			bk[idx] = 9
    		default:
    			msg := fmt.Sprintf("not a number: %c", c)
    			return bank{}, errors.New(msg)
    		}
    	}
    	return bk, nil
    }
    
    func getBankChannel(input chan string) chan bank {
    	ch := make(chan bank, cap(input))
    
    	go func() {
    		for line := range input {
    			bank, err := readBank(line)
    			if err != nil {
    				fmt.Errorf("error reading line %v: %v\n", line, err)
    				close(ch)
    				return
    			}
    			ch <- bank
    		}
    		close(ch)
    	}()
    
    	return ch
    }
    
    func stepOne(input chan string) (int, error) {
    	ch := getBankChannel(input)
    	sum := 0
    	for bank := range ch {
    		sum += bank.largestNDigitJoltage(2)
    	}
    
    	return sum, nil
    }
    
    func stepTwo(input chan string) (int, error) {
    	ch := getBankChannel(input)
    	sum := 0
    	for bank := range ch {
    		sum += bank.largestNDigitJoltage(12)
    	}
    
    	return sum, nil
    }
    
    func main() {
    	// input2 := utils.InputText(inputText)
    	input := utils.FilePath("day03.txt")
    
    	ch, err := input.GetLineChannel()
    	if err != nil {
    		fmt.Errorf("step one error: %v\n", err)
    		return
    	}
    
    	var one int
    	one, err = stepOne(ch)
    	if err != nil {
    		fmt.Errorf("step one error: %v\n", err)
    		return
    	}
    	fmt.Printf("Step one result: %v\n", one)
    
    	// input2 := utils.InputText(inputText)
    	input2 := utils.FilePath("day03.txt")
    
    	ch, err = input2.GetLineChannel()
    	if err != nil {
    		fmt.Errorf("step two error: %v\n", err)
    		return
    	}
    
    	var two int
    	two, err = stepTwo(ch)
    	if err != nil {
    		fmt.Errorf("step two error: %v\n", err)
    		return
    	}
    	fmt.Printf("Step two result: %v\n", two)
    }
    

    While I am quite an adaptable person and I learn to program quickly in about all the languages I’ve tried, I’m still at the beginning of my journey with Go. It does feel like the language is trying to resist me being clever at every corner. I understand the reasons, why not, but damn it does make the development a bit frustrating at times