@@ -3,6 +3,7 @@ package day03
33
44import locations .Directory .currentDir
55import 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
1314def 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