Skip to content

Commit 8cce680

Browse files
authored
updated solution day03 (#349)
1 parent ce8e593 commit 8cce680

File tree

1 file changed

+46
-87
lines changed

1 file changed

+46
-87
lines changed

2023/src/day03.scala

Lines changed: 46 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package day03
33

44
import locations.Directory.currentDir
55
import inputs.Input.loadFileSync
6+
import scala.util.matching.Regex.Match
67

78
@main def part1: Unit =
89
println(s"The solution is ${part1(loadInput())}")
@@ -12,95 +13,53 @@ import inputs.Input.loadFileSync
1213

1314
def loadInput(): String = loadFileSync(s"$currentDir/../input/day03")
1415

15-
trait Entity:
16-
def x: Int
17-
def length: Int
16+
case class Coord(x: Int, y: Int):
17+
def within(start: Coord, end: Coord) =
18+
if y < start.y || y > end.y then false
19+
else if x < start.x || x > end.x then false
20+
else true
21+
case class PartNumber(value: Int, start: Coord, end: Coord)
22+
case class Symbol(sym: String, pos: Coord):
23+
def neighborOf(number: PartNumber) = pos.within(
24+
Coord(number.start.x - 1, number.start.y - 1),
25+
Coord(number.end.x + 1, number.end.y + 1)
26+
)
1827

19-
case class Symbol(x: Int, length: Int, charValue: Char) extends Entity
20-
case class Number(x: Int, length: Int, intValue: Int) extends Entity
28+
object IsInt:
29+
def unapply(in: Match): Option[Int] = in.matched.toIntOption
2130

22-
case class Grid(numbers: IArray[IArray[Number]], symbols: IArray[IArray[Symbol]])
23-
case class Box(x: Int, y: Int, w: Int, h: Int)
31+
def findPartsAndSymbols(source: String) =
32+
val extractor = """(\d+)|[^.\d]""".r
33+
source.split("\n").zipWithIndex.flatMap: (line, i) =>
34+
extractor
35+
.findAllMatchIn(line)
36+
.map:
37+
case m @ IsInt(nb) =>
38+
PartNumber(nb, Coord(m.start, i), Coord(m.end - 1, i))
39+
case s => Symbol(s.matched, Coord(s.start, i))
2440

25-
def parse(input: String): Grid =
26-
val (numbers, symbols) = IArray.from(input.linesIterator.map(parseRow(_))).unzip
27-
Grid(numbers = numbers, symbols = symbols)
41+
def part1(input: String) =
42+
val all = findPartsAndSymbols(input)
43+
val symbols = all.collect { case s: Symbol => s }
44+
all
45+
.collect:
46+
case n: PartNumber if symbols.exists(_.neighborOf(n)) =>
47+
n.value
48+
.sum
2849

29-
def surrounds[E <: Entity](y: Int, from: Entity, rows: IArray[IArray[E]]): List[E] =
30-
val boundingBox = Box(x = from.x - 1, y = y - 1, w = from.x + from.length, h = y + 1)
31-
val width = boundingBox.x to boundingBox.w
32-
def overlaps(e: Entity) =
33-
val eWidth = e.x to (e.x + e.length - 1)
34-
width.min <= eWidth.max && width.max >= eWidth.min
35-
def findUp =
36-
if boundingBox.y < 0 then Nil
37-
else rows(boundingBox.y).filter(overlaps).toList
38-
def findMiddle =
39-
rows(y).filter(overlaps).toList
40-
def findDown =
41-
if boundingBox.h >= rows.size then Nil
42-
else rows(boundingBox.h).filter(overlaps).toList
43-
findUp ++ findMiddle ++ findDown
50+
case class Gear(part: PartNumber, symbol: Symbol)
4451

45-
def solution(input: String, summarise: Grid => IterableOnce[Int]): Int =
46-
summarise(parse(input)).sum
47-
48-
def part1(input: String): Int =
49-
solution(input, findPartNumbers)
50-
51-
def part2(input: String): Int =
52-
solution(input, findGearRatios)
53-
54-
def findPartNumbers(grid: Grid) =
55-
for
56-
(numbers, y) <- grid.numbers.iterator.zipWithIndex
57-
number <- numbers
58-
if surrounds(y, number, grid.symbols).sizeIs > 0
59-
yield
60-
number.intValue
61-
62-
def findGearRatios(grid: Grid) =
63-
for
64-
(symbols, y) <- grid.symbols.iterator.zipWithIndex
65-
symbol <- symbols
66-
if symbol.charValue == '*'
67-
combined = surrounds(y, symbol, grid.numbers)
68-
if combined.sizeIs == 2
69-
yield
70-
combined.map(_.intValue).product
71-
72-
def parseRow(row: String): (IArray[Number], IArray[Symbol]) =
73-
val buf = StringBuilder()
74-
val numbers = IArray.newBuilder[Number]
75-
val symbols = IArray.newBuilder[Symbol]
76-
var begin = -1 // -1 = not building an entity, >= 0 = start of an entity
77-
var knownSymbol = -1 // trinary: -1 = unknown, 0 = number, 1 = symbol
78-
def addEntity(isSymbol: Boolean, x: Int, value: String) =
79-
if isSymbol then symbols += Symbol(x = x, length = value.size, charValue = value.head)
80-
else numbers += Number(x = x, length = value.size, intValue = value.toInt)
81-
for (curr, colIdx) <- row.zipWithIndex do
82-
val isSeparator = curr == '.'
83-
val inEntity = begin >= 0
84-
val kindChanged =
85-
!inEntity && !isSeparator
86-
|| isSeparator && inEntity
87-
|| knownSymbol == 1 && curr.isDigit
88-
|| knownSymbol == 0 && !curr.isDigit
89-
if kindChanged then
90-
if inEntity then // end of entity
91-
addEntity(isSymbol = knownSymbol == 1, x = begin, value = buf.toString)
92-
buf.clear()
93-
if isSeparator then // reset all state
94-
begin = -1
95-
knownSymbol = -1
96-
else // begin new entity
97-
begin = colIdx
98-
knownSymbol = if curr.isDigit then 0 else 1
99-
buf += curr
100-
else
101-
if !isSeparator then buf += curr
102-
end if
103-
end for
104-
if begin >= 0 then // end of line
105-
addEntity(isSymbol = knownSymbol == 1, x = begin, value = buf.toString)
106-
(numbers.result(), symbols.result())
52+
def part2(input: String) =
53+
val all = findPartsAndSymbols(input)
54+
val symbols = all.collect { case s: Symbol => s }
55+
all
56+
.flatMap:
57+
case n: PartNumber =>
58+
symbols
59+
.find(_.neighborOf(n))
60+
.filter(_.sym == "*")
61+
.map(Gear(n, _))
62+
case _ => None
63+
.groupMap(_.symbol)(_.part.value)
64+
.filter(_._2.length == 2)
65+
.foldLeft(0) { _ + _._2.product }

0 commit comments

Comments
 (0)