-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathDay07.scala
102 lines (85 loc) · 3.56 KB
/
Day07.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.lmat.adventofcode.year2020
import com.lmat.adventofcode.SimpleCommonPuzzle
import com.lmat.adventofcode.year2020.Day07Definitions._
import com.lmat.util.Files.readResource
import scala.annotation.tailrec
import scala.util.Try
object Day07Definitions {
type Bag = String
type BagRules = Map[Bag, Map[Bag, Int]]
}
object Day07 extends SimpleCommonPuzzle[BagRules, Int, Int]{
override def parse(resource: String): BagRules =
parseBagRules(readResource(resource).mkString("\n"))
def parseBagRules(source: String): BagRules =
source.split("\n").flatMap(parseBagRule).toMap
def parseBagRule(row: String): Option[(String, Map[String, Int])] = {
def parseBag(raw: String): Option[Bag] = {
val pattern1 = s"(.*) bag".r
val pattern2 = s"(.*) bags".r
raw match {
case pattern1(bag) => Some(bag)
case pattern2(bag) => Some(bag)
case _ => None
}
}
def parseComponent(raw: String): Option[(Bag, Int)] = {
val pattern = s"(\\d+) (.*)".r
raw match {
case pattern(countRaw, bagRaw) => for {
count <- Try(countRaw.toInt).toOption
bag <- parseBag(bagRaw)
} yield (bag, count)
case _ => None
}
}
def parseRight(right: String): Map[Bag, Int] =
right.split(", ").flatMap(parseComponent).toMap
val components: List[String] = row.dropRight(1).split(" contain ").toList
components match {
case List(leftRaw, rightRaw) => parseBag(leftRaw).map(left => (left, parseRight(rightRaw)))
case _ => None
}
}
override def part1(bagRules: BagRules): Int =
findPossibleContainers(bagRules)("shiny gold").size
override def part2(bagRules: BagRules): Int =
buildBagCountMap(bagRules)("shiny gold")
/**
* Algorithm:
* Given a Set of target bags find the Set of bags that contain any
* Recursively do this until the Set stops expanding
* Remove the original target bag
*/
def findPossibleContainers(bagRules: BagRules)(target: Bag): Set[Bag] = {
@tailrec
def loop(current: Set[Bag]): Set[Bag] = {
// println(s"Current: $current")
val newBags = bagRules.filter{case (_,containedBags) => containedBags.exists{case (bag, _) => current.contains(bag)}}.keySet
if ((newBags -- current).isEmpty) current
else loop(newBags ++ current)
}
loop(Set(target)) - target
}
/**
* Algorithm:
* Given the bag rules find all the trivially solvable cases
* - A case is trivially solvable if all bags it contains have already been solved
* Add their solution to the solutions map and remove them from the unsolved cases
* Recursively do this until there is no more solvable cases or no more unsolved cases remain
*/
def buildBagCountMap(bagRules: BagRules): Map[Bag, Int] = {
@tailrec
def loop(remaining: BagRules, current: Map[Bag, Int]): Map[Bag, Int] = {
val triviallySolvable = remaining.filter{case (_,containedBags) => containedBags.forall{case (bag, _ ) => current.contains(bag)}}
// println(s"\nCurrent: $current")
// println(s"Remaining: ${remaining.keySet}")
// println(s"TriviallySolvable: ${triviallySolvable.keySet}")
val nextState = triviallySolvable.foldLeft(current){case (state, (bag, containedBags)) => state.updated(bag, containedBags.map {case (containedBag, count) => count + current(containedBag) * count}.sum)}
val nextRemaining = remaining.removedAll(triviallySolvable.keySet)
if(nextRemaining.isEmpty || triviallySolvable.isEmpty) nextState
else loop(nextRemaining, nextState)
}
loop(bagRules, Map())
}
}