/
table.go
129 lines (109 loc) · 2.51 KB
/
table.go
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package day14
import (
"fmt"
"math"
"strings"
)
// Table is a list of conversion rules between types of materials.
type Table struct {
rules []*Rule
byOutput map[string]*Rule
sortedRules []*Rule
}
// TableFromString creates a conversion table from lines of rules.
func TableFromString(s string) (*Table, error) {
lines := strings.Split(s, "\n")
rules := make([]*Rule, 0, len(lines))
byOutput := make(map[string]*Rule)
for _, line := range lines {
r, err := RuleFromString(line)
if err != nil {
return nil, err
}
rules = append(rules, &r)
byOutput[r.Output.Material] = &r
}
t := &Table{
rules: rules,
byOutput: byOutput,
}
t.sortRules()
return t, nil
}
func (t *Table) sortRules() {
marks := make(map[string]int)
sorted := make([]*Rule, 0, len(t.rules))
var visit func(string)
visit = func(material string) {
mark := marks[material]
if mark == 2 {
return
}
if mark == 1 {
panic("rule table is not a DAG")
}
marks[material] = 1
r := t.byOutput[material]
if r == nil {
// This must be ORE
marks[material] = 2
return
}
for _, input := range r.Inputs {
visit(input.Material)
}
marks[material] = 2
sorted = append(sorted, r)
}
for {
var visited bool
for _, r := range t.rules {
mark := marks[r.Output.Material]
if mark == 0 {
visit(r.Output.Material)
visited = true
break
}
}
if !visited {
break
}
}
// reverse the list
for i, j := 0, len(sorted)-1; i < j; i, j = i+1, j-1 {
sorted[i], sorted[j] = sorted[j], sorted[i]
}
t.sortedRules = sorted
}
// RequiredOre returns the minimum amount of ore needed to produce the desired
// quantity of material with the rules in this table.
func (t *Table) RequiredOre(desired Quantity) int {
if desired.Material == "ORE" {
return desired.Amount
}
reqs := make(map[string]int)
reqs[desired.Material] = desired.Amount
// apply rules in topo sort order, figuring out how much of each input we need
// for the output of that rule.
for _, r := range t.sortedRules {
amount := reqs[r.Output.Material]
multiplier := int(math.Ceil(float64(amount) / float64(r.Output.Amount)))
for _, q := range r.Inputs {
reqs[q.Material] += multiplier * q.Amount
}
// clear out the requirement now that it's been satisfied
reqs[r.Output.Material] = 0
}
return reqs["ORE"]
}
var _ fmt.Stringer = (*Table)(nil)
func (t *Table) String() string {
var b strings.Builder
for i, r := range t.rules {
if i > 0 {
b.WriteRune('\n')
}
b.WriteString(r.String())
}
return b.String()
}