Inlägg

Inlägg som MultiMike har skrivit i forumet
Av MultiMike
Skrivet av Yoshman:

Ett mål med årets AoC var att lösa alla 25 luckorna på under 1 sekund, det med indata liggande på fil så inga specialoptimeringar för just min indata.

Blev inte så utmanande, även om ett par dagar vara hyfsat beräkningsintensiva. I slutändan räckte det med en RPi4 för att komma under 1 sekund, kan lösa alla dagar på ~850 ms där medan det tar ~150 ms på M1.

Längst tid (strax över 50 % av totaltiden) tog dag 23.

Starkt jobbat!
Rust går verkligen att få prestanda ur om man håller tungan rätt i mun.

Kan absolut förstå tjusningen i att optimera körtiden. Själv försökte jag mest nå i mål i första hand, och få till kod som jag inte behövde skämmas för som bonus. Helst med funktionella lösningar, med icke muterande datastrukturer där det var görbart.

Även om jag de sista dagarna använt alldeles för många timmar mot vad jag räknade med att lägga innan allt drog igång, så tycker att det var nyttigt att motionera hjärnan med dessa övningar. Det har också varit väldigt lärorikt och intressant att se andras lösningar på problemen. Flera vassa programmerare häckar här tycker jag som har fiffiga lösningar!

Måste ändå lovorda Scala som jag använde för mina lösningar. Det är en fröjd att programmera i. Typsäkert och "concise". Flera gånger har jag jämfört med pythonlösningarna, då det är någon form av "måttstock" på hur kompakt det kan bli, och tycker Scala inte ligger långt efter trots typsäkerhet. Ser fram emot att börja programmera Scala 3, som blir ännu trevligare vad gäller läsbarhet.

Av MultiMike

Dag 24
Språk: Svenska

Väldigt klurig och kul dag tycker jag!

Byggde en ALU, men fattade inte så noga vad jag skulle använda den till, så övergick till att studera koden istället.

Ganska snart ser man att alla siffror hanteras snarlikt i en 14 stegs loop.
Ett tal ackumuleras mellan varje iterationssteg. Och koden avslöjar att det har bas 26.
Skriver man ut det ackumulerade talet mellan varje iteration (dvs för varje siffra i input) så ser man att det fungerar som en stack, där föregående tal kan multipliceras med 26 eller delas bort, beroende på ett villkor på input. Vissa steg i itertionen kan inte reducera stacken, då divisionen är 1 i dem. Dessa kommer bygga stacken oavsett och man kan maximera eller minimera dem endast med hänseende till att dess stackbidrag senare är reducerbart.
I sista iterationen måste "stacken" vara mellan 13-21 för att kunna delas bort för att nå 0.

Genom att studera additions-konstanterna så kan man lösa ut största och minsta giltiga tal!

(När nu algoritmen är avslöjad, kan man säkert fiffla ihop ett program som löser det också, men ser ingen anledning till det nu.)

Dold text
Av MultiMike
Skrivet av Yoshman:

Dag: 22
Språk: Rust

Nu börjar luckorna regelmässigt ta ms och inte µs... Denna tar 8-9 ms (lite beroende på om man kör på M1 eller Ryzen).

Insåg redan från första exemplet att brute-force inte var realistiskt i del 2, går annars att lösa del 1 rätt snabbt genom att representera x-axeln med ett 128-bitars tal (moderna CPUer stödjer det via SIMD) där varje bit representerar en kub. Det hela kan då representeras i 2D där Y/Z är dimensioner med ett 128-bitars tal per cell.

Men löste det i stället med "positiva" resp "negativa" volymer. Varje rad i indata beskriver ett rätblock, "on" block har positiv volym medan "off" block har negativ volym.

Snittet mellan två "on" block lägger till ett "off" block, detta då man annars räknar den överlappande volymen två gånger. "off" blocket kompenserar för det.

Snittet mellan ett "on" block och ett "off" block blir också ett "off" block, skillnaden att man vid hantering av ett "off" block inte lägger till "off" blocket självt. Resultatet blir att man minskar volymen på existerande "on" block med volymen av snittet.

Snittet mellan ett inlagt "off" block och ett nytt "on" block blir ett "on" block, det för att "lägga till" den volym i "off" blocket som nu slagit på.

use std::{ str::FromStr, cmp::{max, min} }; use crate::solution::Solution; struct Day22 { blocks: Vec<Block>, } impl Solution for Day22 { fn part1(&mut self) -> String { self.count_on_cubes(false).to_string() } fn part2(&mut self) -> String { self.count_on_cubes(true).to_string() } } impl Day22 { fn count_on_cubes(&self, include_all_cubes: bool) -> u64 { let mut new_blocks = Vec::new(); let mut blocks = Vec::new(); blocks.reserve(60_000); for &block in self.blocks.iter() { if !include_all_cubes && !block.is_50_50() { continue; } if block.is_on { new_blocks.push(block) } for rhs in blocks.iter() { if let Some(isect_block) = block.intersection(rhs) { new_blocks.push(isect_block); } } blocks.append(&mut new_blocks); } blocks .iter() .fold(0, |volume, block| { if block.is_on { volume + block.volume() } else { volume - block.volume() } }) } } pub fn new(input: &str) -> Box<dyn Solution> { Box::new(Day22 { blocks: input.lines().filter_map(|line| line.parse().ok()).collect(), }) } #[derive(Clone, Copy)] struct Block { is_on: bool, x_lim: (i32, i32), y_lim: (i32, i32), z_lim: (i32, i32), } impl Block { fn is_50_50(&self) -> bool { return self.x_lim.0 >= -50 && self.x_lim.1 <= 51 && self.y_lim.0 >= -50 && self.y_lim.1 <= 51 && self.z_lim.0 >= -50 && self.z_lim.1 <= 51 } fn volume(&self) -> u64 { (self.x_lim.1 - self.x_lim.0) as u64 * (self.y_lim.1 - self.y_lim.0) as u64 * (self.z_lim.1 - self.z_lim.0) as u64 } fn intersection(&self, rhs: &Block) -> Option<Block> { if self.x_lim.0 >= rhs.x_lim.1 || self.x_lim.1 <= rhs.x_lim.0 || self.y_lim.0 >= rhs.y_lim.1 || self.y_lim.1 <= rhs.y_lim.0 || self.z_lim.0 >= rhs.z_lim.1 || self.z_lim.1 <= rhs.z_lim.0 { return None; } let x0 = max(self.x_lim.0, rhs.x_lim.0); let x1 = min(self.x_lim.1, rhs.x_lim.1); let y0 = max(self.y_lim.0, rhs.y_lim.0); let y1 = min(self.y_lim.1, rhs.y_lim.1); let z0 = max(self.z_lim.0, rhs.z_lim.0); let z1 = min(self.z_lim.1, rhs.z_lim.1); Some(Block { is_on: !rhs.is_on, x_lim: (x0, x1), y_lim: (y0, y1), z_lim: (z0, z1), }) } } impl FromStr for Block { type Err = &'static str; fn from_str(s: &str) -> Result<Self, Self::Err> { let (mode, x0, x1, y0, y1, z0, z1) = scan_fmt!( s, "{} x={d}..{d},y={d}..{d},z={d}..{d}", String, i32, i32, i32, i32, i32, i32 ) .expect("Invalid block format"); Ok(Block { is_on: mode == "on", x_lim: (x0, x1 + 1), y_lim: (y0, y1 + 1), z_lim: (z0, z1 + 1), }) } }

Dold text

Bra tänkt! Enkel och elegant algoritm.
Nu känner man sig lite snopen att man inte kom på den själv

Av MultiMike

Dag: 22
Språk: Scala

Del 2 löste jag genom att bara identifiera alla positioner på de tre axlarna där någon kuboid har sin kant. Tar några minuter att köra, men blev den lösningen av slapphet.
Kanske orkar att komma tillbaka och genomföra min första tanke att splittra kuboiderna i mindre konvexa kuboider där de träffar varandra. Skulle förmodligen bli en vädligt mycket snabbare körtid på den lösningen.

object Day22 { val lines = readLines("22/data.txt") case class Rng(l: Int, h: Int) { val length = h - l def disparate(r: Rng) = h <= r.l || l >= r.h def intersects(r: Rng) = !disparate(r) def intersect(r: Rng): Option[Rng] = if(intersects(r)) Some(Rng(max(l, r.l), min(h, r.h))) else None def contains(r: Rng) = l <= r.l && h >= r.h def contains(v: Int) = l <= v && v < h def range = l until h } def part1 = { val valid = Rng(-50, 51) val empty = Rng(0, 0) val cubes = new Array[Int](101 * 101 * 101) for(s"$cmd x=${int(xMin)}..${int(xMax)},y=${int(yMin)}..${int(yMax)},z=${int(zMin)}..${int(zMax)}" <- lines) { val value = if(cmd == "on") 1 else 0 for { z <- Rng(zMin, zMax + 1).intersect(valid).getOrElse(empty).range y <- Rng(yMin, yMax + 1).intersect(valid).getOrElse(empty).range x <- Rng(xMin, xMax + 1).intersect(valid).getOrElse(empty).range } cubes((z + 50) * 101 * 101 + (y + 50) * 101 + x + 50) = value } println("Part 1: " + cubes.count(1.==)) } def part2 = { case class Cuboid(xr: Rng, yr: Rng, zr: Rng) { def contains(x: Int, y: Int, z: Int) = xr.contains(x) && yr.contains(y) && zr.contains(z) } val cuboids = lines.map { case s"$cmd x=${int(xMin)}..${int(xMax)},y=${int(yMin)}..${int(yMax)},z=${int(zMin)}..${int(zMax)}" => Cuboid(Rng(xMin, xMax+1), Rng(yMin, yMax+1), Rng(zMin, zMax+1)) -> (cmd == "on") }.reverse def slices(f: Cuboid => Rng) = SortedSet(cuboids.flatMap { case (c, _) => Seq(f(c).l, f(c).h) }:_*).toIndexedSeq val xs = slices(_.xr) val ys = slices(_.yr) val zs = slices(_.zr) def isOn(x: Int, y: Int, z: Int) = cuboids.find { case (cuboid, _) => cuboid.contains(x, y, z) }.exists(_._2) var count = 0L for { xi <- 0 until xs.length - 1 yi <- 0 until ys.length - 1 zi <- 0 until zs.length - 1 if isOn(xs(xi), ys(yi), zs(zi)) } count += (xs(xi+1) - xs(xi)).toLong * (ys(yi+1) - ys(yi)) * (zs(zi+1) - zs(zi)) println(count) } def main(args: Array[String]): Unit = { part1 part2 } }

Dold text
Av MultiMike

Dag: 21
Språk: Scala

object Day21 { case class Player(idx: Int, pos: Int, score: Int) { def advance(steps: Int): Player = { val p = (pos + steps) % 10 copy(pos = p, score = score + p + 1) } } def main(args: Array[String]): Unit = { part1() part2() } def part1() = { case class Dice() { private var v = -1 var count = 0 def next: Int = { count += 1 v = (v + 1) % 100 v + 1 } } val dice = Dice() val players = Seq(Player(0, 0, 0), Player(1, 5, 0)) def next(players: Seq[Player]): Long = { val p = players.head.advance(dice.next + dice.next + dice.next) if(p.score >= 1000) { players.last.score * dice.count } else { next(Seq(players.last, p)) } } println(next(players)) } def part2() = { val SumFreq = Seq(3 -> 1, 4 -> 3, 5 -> 6, 6 -> 7, 7 -> 6, 8 -> 3, 9 -> 1) val WinnerScore = 21 case class GameState(players: Seq[Player]) { def isFinished = players.exists(_.score >= WinnerScore) def winner: Int = players.find(_.score >= WinnerScore).get.idx def nextTurn(count: Long): Seq[(GameState, Long)] = { SumFreq.map { case (sum, freq) => GameState(Seq(players.last, players.head.advance(sum))) -> freq * count } } } def solve(games: Map[GameState, Long]): Seq[(GameState, Long)] = { if(games.isEmpty) { Seq.empty } else { val next = games.toSeq.flatMap { case (state, count) => state.nextTurn(count) } val (finished, unfinished) = next.groupMapReduce(_._1)(_._2)(_ + _).partition(_._1.isFinished) finished.toSeq ++ solve(unfinished) } } val initialState = GameState(Seq(Player(0, 0, 0), Player(1, 5, 0))) val finished = solve(Map(initialState -> 1L)) println(finished.groupMapReduce(_._1.winner)(_._2)(_ + _).values.max) } }

Dold text
Av MultiMike
Skrivet av Ingetledigtnamn:

Dag: 20
Språk: Python (numpy och scipy)

Oändligt stor, jo tack, och den togglar mellan # och . varannan iteration. Istället för att mecka med det så tog jag en tillräckligt stor oändlighet så att man inte skulle smittas av de fel som propageras från kanterna och beskar före summeringen.

Tack @survivalcode som visade oss den fantastiska convolve2d tidigare.

from scipy.signal import convolve2d import numpy as np bits = np.array([1 << i for i in range(9)]).reshape(3,3) line, image = open("input20").read().split("\n\n") t = np.array([int(c == '#') for c in line.replace('\n', '')]) def solve(iterations): a = np.array([[c == '#' for c in l] for l in image.strip().split('\n')], dtype = int) a = np.pad(a,((iterations * 2,iterations * 2),(iterations * 2,iterations * 2)), mode = 'constant', constant_values = 0) for _ in range(iterations): a = t[convolve2d(a, bits, "same")[:,:]] return a[iterations:-iterations,iterations:-iterations].sum() print(solve(2), solve(50))

Dold text

Blev inspirerad av din lösning att göra en mer kompakt variant i Scala också. Man är lite "tydlighetsskadad" i jobbet ibland, så det kan vara kul att försöka trycka ihop koden lite.
Har dock inga fina libraries som gör matrisoperationer importerade så kör lite hemmabyggt.

import aoc.Util._ import scala.util.Try object Day20b { val lines = readLines("20/data.txt") val kern = (0 until 9).map(i => (i % 3 - 1, i / 3 - 1, 1 << (8 - i))) val toInt = Map('#' -> 1, '.' -> 0) val tbl = lines.head.map(toInt) case class Img(pix: Seq[Seq[Int]], bg: Int) { def lit = pix.flatten.count(1.==) } def enh(img: Img) = { val pixels = for(y <- -1 to img.pix.size; x <- -1 to img.pix.size) yield { tbl(kern.map { case (dx, dy, v) => v * Try(img.pix(y + dy)(x + dx)).getOrElse(img.bg) }.sum) } Img(pixels.grouped(img.pix.size + 2).toSeq, tbl(img.bg * 511)) } def main(args: Array[String]): Unit = { val img = Img(lines.drop(2).map(_.map(toInt)), 0) println(2.repeat(img, enh).lit) // part 1 println(50.repeat(img, enh).lit) // part 2 } }

Dold text
Av MultiMike

Dag: 20
Språk: Scala

Ganska skönt med en som inte tar så lång tid idag, så man får lite "paus"

object Day20 { val lines = readLines("20/data.txt") val enhanchment = lines.head val imageLines = lines.tail.tail type Pos = (Int, Int) case class Image(image: Seq[String], background: Char) { val xSize = image.head.length val ySize = image.length val xRange = 0 until xSize val yRange = 0 until ySize def pixel(p: Pos) = { val (x, y) = p if(xRange.contains(x) && yRange.contains(y)) { image(y)(x) } else background } def index(pos: Pos): Int = { val (x, y) = pos var res = 0 for { dy <- -1 to 1 dx <- -1 to 1 } { val bit = pixel(x + dx, y + dy) match { case '#' => 1 case '.' => 0 } res = (res << 1) + bit } res } def backgroundIndex = index(-2, -2) def enhance(): Image = { val newBackground = enhanchment.charAt(backgroundIndex) val result = Array.fill(ySize + 2, xSize + 2)(newBackground) for { y <- -1 to ySize x <- -1 to xSize } result(y + 1)(x + 1) = enhanchment.charAt(index(x, y)) Image(result.map(chars => new String(chars)), newBackground) } def lit = image.map(_.count('#'.==)).sum override def toString: String = image.mkString("\n") } def main(args: Array[String]): Unit = { // part 1 println(Image(imageLines, '.').enhance().enhance().lit) // part 2 var image = Image(imageLines, '.') for(_ <- 1 to 50) { image = image.enhance() } println(image.lit) } }

Dold text
Av MultiMike

Dag: 19
Språk: Scala

Det var ju fiffigt tänkt med avstånden! Kom inte på det när jag fnulade hur man kunde "indexera" scanners på något vis.

Så det blev en brute-force variant som gör att min dator tuggar nästan tre minuter innan den spottar ur sig svaret...

object Day19 { val data = readLines("19/data.txt") val scanners = data.splitBetween((_, l)=> l.isEmpty).map { lines => val s"--- scanner ${int(scanner)} ---" = lines.head val coords = lines.tail.map { case s"${int(x)},${int(y)},${int(z)}" => (x, y, z) } Scanner(scanner, coords.toSet) } type Coord = (Int, Int, Int) type Transform = Coord => Coord val variations = { val colVariants = Seq[Transform]( { case (x, y, z) => (x, y, z) }, { case (x, y, z) => (x, z, y) }, { case (x, y, z) => (y, x, z) }, { case (x, y, z) => (y, z, x) }, { case (x, y, z) => (z, x, y) }, { case (x, y, z) => (z, y, x) } ) val invVariants: Seq[Transform] = (0 until 8).map { n => { case (x, y, z) => (if((n & 1) != 0) -x else x, if((n & 2) != 0) -y else y, if((n & 4) != 0) -z else z) } } for { col <- colVariants inv <- invVariants } yield col.andThen(inv) } implicit class CoordHelper(c: (Int, Int, Int)) { def +(o: (Int, Int, Int)): (Int, Int, Int) = (c._1 + o._1, c._2 + o._2, c._3 + o._3) def -(o: (Int, Int, Int)): (Int, Int, Int) = (c._1 - o._1, c._2 - o._2, c._3 - o._3) } case class Scanner(id: Int, coords: Set[Coord]) { val coordVariants = variations.map(v => coords.map(v)) } private def findOffset(ref: Set[Coord], other: Set[Coord]): Option[Coord] = { val offsetCandidates = for { c <- ref c2 <- other } yield c - c2 offsetCandidates.find { offset => val o = other.map(_ + offset) (ref intersect o).size >= 12 } } def findTransforms(ref: Set[Coord], scanner: Scanner): Seq[Transform] = { for { (variation, coordVariation) <- variations zip scanner.coordVariants offset <- findOffset(ref, coordVariation) } yield variation.andThen(_ + offset) } def dist(c1: Coord, c2: Coord) = { import math._ val diff = c1 - c2 abs(diff._1) + abs(diff._2) + abs(diff._3) } def main(args: Array[String]): Unit = { val ZERO = (0, 0, 0) var beacons = scanners.head.coords val remaining = ArrayBuffer[Scanner](scanners.tail:_*) val scannerCenters = ArrayBuffer[Coord](ZERO) while(remaining.nonEmpty) { for(i <- remaining.length - 1 to 0 by -1) { val scanner = remaining(i) findTransforms(beacons, scanner).map { transform => val scannerCenter = transform(ZERO) scannerCenters.append(scannerCenter) beacons = beacons union scanner.coords.map(transform) remaining.remove(i) println(s"Found $i. Scannes left ${remaining.length}. Center: " + scannerCenter) } } } // part 1 println("Beacons: " + beacons.size) // part 2 val maxDist = scannerCenters.combinations(2).map(pair => dist(pair.head, pair.last)).max println("Max dist: " + maxDist) } }

Dold text
Av MultiMike

Dag: 14
Språk: Scala

object Day14 { val data = readLines("14/data.txt").iterator val template = (data.next() + " ").sliding(2).toSeq.groupMapReduce(identity)(_ => 1L)(_ + _) data.next() // skip line val rules = data.map { case s"$pair -> ${char(insert)}" => pair -> insert }.toMap def main(args: Array[String]): Unit = { def stepCount(from: Map[String, Long]) = { val res = from.toSeq.flatMap { case (pair, count) => rules.get(pair) match { case Some(insert) => Seq(s"${pair.head}$insert" -> count, s"$insert${pair.last}" -> count) case None => Seq(s"${pair.head} " -> 1L) } } res.groupMapReduce(_._1)(_._2)(_ + _) } def calcDiff(result: Map[String, Long]) = { val counts = result.groupMapReduce(_._1.head)(_._2)(_ + _).values counts.max - counts.min } // step 1 var polymer1 = template for(_ <- 0 until 10) { polymer1 = stepCount(polymer1) } println(calcDiff(polymer1)) // step 2 var polymer2 = template for(_ <- 0 until 40) { polymer2 = stepCount(polymer2) } println(calcDiff(polymer2)) } }

Dold text
Av MultiMike

Dag: 13
Språk: Scala

object Day13 { val data = readLines("13/data.txt") val parts = data.splitBetween((_, s) => s.isEmpty) val dots = parts.head.map { case s"${int(x)},${int(y)}" => (x, y) } .toSet val folds = parts.last def flip(v: Int, axis: Int) = if(v > axis) axis * 2 - v else v def fold(dots: Set[(Int, Int)], instruction: String) = instruction match { case s"fold along x=${int(col)}" => dots.map { case (x, y) => (flip(x, col), y) } case s"fold along y=${int(row)}" => dots.map { case (x, y) => (x, flip(y, row)) } } def main(args: Array[String]): Unit = { // part 1 println(fold(dots, folds.head).size) // part 2 var tDots = dots folds.foreach { instruction => tDots = fold(tDots, instruction) } val maxX = tDots.map(_._1).max val maxY = tDots.map(_._2).max for(y <- 0 to maxY) { val chars = Array.fill[Char](maxX + 1)(' ') tDots.filter(_._2 == y).map(_._1).foreach(x => chars(x) = '#') println(new String(chars)) } } }

Dold text
Av MultiMike

Dag: 12
Språk: Scala

Tips till de som tycker rekursion är jobbigt:
Börja från "andra sidan"! Tänk ut avbrottsvillkoret först, dvs då funktionen inte ska anropa rekursivt.
Då brukar det lösa sig automatiskt hur man ska bryta ner data för att närma sig avbrottsvillkoret.
Och sen är det ju som vanligt, övning ger färdighet.

object Day12 { val data = readLines("12/data.txt") val links = data.flatMap { case s"$from-$to" => Seq(from -> to, to -> from) } .groupMap(_._1)(_._2) def large(cave: String) = cave.head.isUpper def small(cave: String) = !large(cave) def main(args: Array[String]): Unit = { type Path = Seq[String] // part 1 def explore1(path: Path): Seq[Path] = { if(path.last == "end") { Seq(path) } else { val paths = for { cave <- links(path.last) if large(cave) || !path.contains(cave) } yield explore1(path :+ cave) paths.flatten } } println(explore1(Seq("start")).size) // part 2 def explore2(path: Path, allowDuplicates: Boolean = true): Seq[Path] = { if(path.last == "end") { Seq(path) } else { val paths = for { cave <- links(path.last) duplicate = small(cave) && path.contains(cave) if cave != "start" if large(cave) || !duplicate || allowDuplicates } yield explore2(path :+ cave, allowDuplicates && !duplicate) paths.flatten } } println(explore2(Seq("start")).size) } }

Dold text
Av MultiMike

Dag: 11
Språk: Scala

object Day11 { val data = readLines("11/data.txt") case class Elem(idx: Int, value: Int, affect: Seq[Int]) { def reset = copy(value = 0) def inc(count: Int) = copy(value = value + count) } def main(args: Array[String]): Unit = { def idx(x: Int, y: Int) = y * 10 + x val elems = for(y <- 0 until 10; x <- 0 until 10) yield { val value = data(y)(x).toString.toInt val valid = (0 until 10).toSet val affect = for { dy <- -1 to 1 dx <- -1 to 1 if dx != 0 || dy != 0 if valid(x + dx) && valid(y + dy) } yield idx(x + dx, y + dy) Elem(idx(x, y), value, affect) } def step(elems: Seq[Elem], toIncrease: Seq[Int]): (Seq[Elem], Int) = { if(toIncrease.isEmpty) { (elems, 0) } else { val plusOne = elems.map { e => e.inc(toIncrease.count(_ == e.idx)) } val (flashed, other) = plusOne.partition(_.value > 9) val affected = flashed.flatMap(_.affect) val (subSeq, subFlashed) = step(other, affected) (subSeq ++ flashed.map(e => e.reset), flashed.size + subFlashed) } } // part 1 var es: Seq[Elem] = elems var totFlashes = 0 for(_ <- 0 until 100) { val (newEs, flashes) = step(es, 0 until 100) totFlashes += flashes es = newEs } println(totFlashes) // part 2 es = elems val allFlashStepOpt = Iterator.from(1).find { i => val (newEs, flashes) = step(es, 0 until 100) es = newEs flashes == 100 } println(allFlashStepOpt) } }

Dold text
Av MultiMike

Dag: 10
Språk: Scala

object Day10 { val data = readLines("10/data.txt") def main(args: Array[String]): Unit = { case class LexerResult(unexpected: Option[Char], open: List[Char]) def check(remaining: String, open: List[Char] = Nil): LexerResult = { if(remaining.isEmpty) { LexerResult(None, open) } else { remaining.head match { case '(' => check(remaining.tail, ')' :: open) case '[' => check(remaining.tail, ']' :: open) case '{' => check(remaining.tail, '}' :: open) case '<' => check(remaining.tail, '>' :: open) case c if c == open.head => check(remaining.tail, open.tail) case unexpected => LexerResult(Some(unexpected), open) } } } // part 1 def score1(line: String) = check(line).unexpected match { case Some(')') => 3 case Some(']') => 57 case Some('}') => 1197 case Some('>') => 25137 case None => 0 } println(data.map(score1).sum) // part 2 val scores = data.map(line => check(line)) .filter(_.unexpected.isEmpty) .map { res => res.open.foldLeft(0L) { case (score, ch) => score * 5 + (ch match { case ')' => 1 case ']' => 2 case '}' => 3 case '>' => 4 }) } } .sorted println(scores(scores.length / 2)) } }

Dold text
Av MultiMike
Skrivet av GLaDER:

Dag: 9
Språk: Python 3
Lösning: GitHub

Kul med en BFS såhär en torsdagsmorgon.

Dold text

Trodde du missat att slå ihop överlappande basins, men nu när jag läser beskrivningen noggrannare så är det jag som i onödan tog hänsyn till att flera low points kunde hamna i samma basin.

"The devil is in the details", som det heter

Av MultiMike

Dag: 9
Språk: Scala

Idag blev det en del muterbara strukturer, för att det kändes enklare.

object Day9 { val data = readLines("9/data.txt") val lines = data.map(_.map(_.toString.toInt)) implicit class Adder(p: (Int, Int)) { def +(other: (Int, Int)) = (p._1 + other._1, p._2 + other._2) } val surrounding = Seq((-1,0), (0,-1), (1,0), (0,1)) val zeroTo100 = 0 until 100 def valid(p: (Int, Int)) = zeroTo100.contains(p._1) && zeroTo100.contains(p._2) def depth(p: (Int, Int)) = if (valid(p)) lines(p._2)(p._1) else 10 def extend(p: (Int, Int)) = surrounding.map(dp => p + dp) def lowPoint(p: (Int, Int)) = { val center = depth(p) surrounding.forall(dp => depth(p + dp) > center) } val lowPoints = for { y <- lines.indices x <- lines.head.indices if valid(x, y) && lowPoint(x, y) } yield (x, y) def extendToBasin(p: (Int, Int)) = { val pointsToExtend = mutable.Queue(p) val basin = mutable.Set(p) while(pointsToExtend.nonEmpty) { val p = pointsToExtend.dequeue() val candidates = extend(p).filter { p => valid(p) && depth(p) < 9 && !basin.contains(p) } basin.addAll(candidates) pointsToExtend.appendAll(candidates) } basin.toSet } def main(args: Array[String]): Unit = { // part 1 println(lowPoints.map(p => depth(p) + 1).sum) // part 2 val basins = ArrayBuffer[Set[(Int, Int)]]() for(lowPoint <- lowPoints if !basins.exists(_.contains(lowPoint))) { basins.append(extendToBasin(lowPoint)) } val res = basins.sortBy(-_.size) .take(3) .map(_.size) .product println(res) } }

Dold text
Av MultiMike

Dag: 8
Språk: Scala

object Day8 { val data = readLines("8/data.txt") case class Line(input: Seq[String], display: Seq[String]) { val allObservations = input ++ display } val lines = data.map { l => val parts = l.split(" \\| ") Line(parts(0).split(" "), parts(1).split(" ")) } val digits = Seq("abcefg", "cf", "acdeg", "acdfg", "bcdf", "abdfg", "abdefg", "acf", "abcdefg", "abcdfg").map { p => p.map(_.toInt - 'a').toSet } def main(args: Array[String]): Unit = { part1() part2() } def part1() = { val l1478 = Set(2, 4, 3, 7) println(lines.flatMap(_.display).count(p => l1478.contains(p.length))) } def part2() = { def isValidObservation(candidate: String, observation: String): Boolean = { val leds = observation.map { ch => candidate.indexOf(ch) }.toSet digits contains leds } val candidates = "abcdefg".permutations.toSeq def process(line: Line) = { val patternOpt = candidates.find { candidate => line.allObservations.forall(observation => isValidObservation(candidate, observation)) } patternOpt match { case Some(pattern) => val toPos = (pattern.toSeq zip (0 until 7)).map { case (ch, c) => ch -> c }.toMap val ds = line.display.map { obs => digits.indexOf(obs.map(toPos).toSet) } ds.mkString("").toLong case None => throw new RuntimeException(s"Line $line seems to have no solution!") } } println(lines.map(process).sum) } }

Dold text
Av MultiMike
Skrivet av GLaDER:

Dag: 7
Språk: Python 3
Lösning: GitHub.

La säkert 5 minuter på att jag inte fattade varför jag fick fel på alla ökningar av position. Dvs: 16 -> 5 gav rätt svar (enligt testinputen) men 0 -> 5 gav fel. Givetvis var det en parentes som satt fel, aja...

Detta sagt, min lösning är ful. Den är straight forward och jag tror inte det är några problem att förstå vad den gör, men när jag ser lösningar som:

def dist(a, b): return sum(range(1, abs(a-b)+1)) print(min(sum(dist(num, other) for other in nums) for num in nums))

som skrevs av en kille på Kodsnacks slack (Alex Telon, källa) suktar jag efter att få mer intuitiv förståelse för list-operationer och hur man kan använda min och max i kombination med sum, så som han gjorde.

Detta sagt är jag nöjd med att jag identifierade att bränslet i Del 2 beräknas enligt en naturlig serie och således ges av n*(n+1)/2, istället för att använda range() som Alex gjorde.

Edit: Ännu coolare lösning från reddit -> https://www.reddit.com/r/adventofcode/comments/rar7ty/2021_da.... Jag var inne på att använda medelvärdet på första, men tänkte: "Så lätt kan det väl inte vara?" Tror inte jag hade kunnat dra slutsatsen kring median på Del 2 dock.

Dold text

Han gör faktiskt precis tvärt om. Median i del 1, och medel i del 2.

Dold text
Av MultiMike

Dag: 7
Spräk: Scala

Inte så kul idag när det går för fort tycker jag. Hoppas på lite mer utmaningar längre fram..

object Day7 { val data = readLines("7/data.txt") def main(args: Array[String]): Unit = { val crabs = data.head.split(",").map(_.toInt) println(crabs.length + " crabs") def calc(cost: (Int, Int) => Int) = { (0 until crabs.max).map { pos => crabs.map(crab => cost(crab, pos)).sum }.min } // part 1 println(calc((v, c) => abs(v - c))) // part 2 println(calc((c, p) => { val diff = abs(c - p) diff * (diff + 1) / 2 })) } }

Dold text
Av MultiMike
Skrivet av jclr:

Dag: 5
Språk: Scala

Kommenterade raden är det som skiljer mellan lösningen för del1 och del2. Orkade inte skriva om koden till en bättre lösning.

val input = Using.resource(Source.fromFile("5.txt"))(_.getLines().toVector) input .map { case s"$x1,$y1 -> $x2,$y2" => (x1.toInt, x2.toInt, y1.toInt, y2.toInt) } .collect { case (x1, x2, y1, y2) if x1 == x2 => (y1 to y2 by (y2 - y1).sign).map(x1 -> _) case (x1, x2, y1, y2) if y1 == y2 => (x1 to x2 by (x2 - x1).sign).map(_ -> y1) // case (x1, x2, y1, y2) => (x1 to x2 by (x2 - x1).sign).zip(y1 to y2 by (y2 - y1).sign) } .flatten .groupMapReduce(identity)(_ => 1)(_ + _) .values .count(_ >= 2)

Dold text

groupMapReduce, hur har jag kunnat missa den?!
Tack för lektionen!

Av MultiMike
Skrivet av GLaDER:

Dag: 6
Språk: Python 3
Lösning: GitHub.

"80 iterationer, det är väl inget för min gamla bettan här?"
*klappar på datorn och implementerar en naiv array-lösning i Python*

Trodde att idag var min dag då jag fick till en naiv lösning på mindre än en minut. Sen tryckte jag på play, och när programmet inte terminerat på 30 sekunder förstod jag att jag varit -- naiv. Inte ens testinputen terminerade inom rimlig tid

Gav mig i kast med Counter() men jag har lite svårt att förstå den så jag fick inspireras av Internet och ta till en dictionary-lösning istället.

Dold text

Hehe!
Min naiva lösning tuggade faktiskt på till dag 140 inom rimlig tid, sen började datorn tycka att det var jobbigt (det var en ca 600 meg stor array vid det laget)
Men, man var ju tvungen att testa iaf!