/
aoc2020_21.go
508 lines (422 loc) · 14.8 KB
/
aoc2020_21.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
package aoc2020
/*
--- Day 21: Allergen Assessment ---
You reach the train's last stop and the closest you can get to your vacation island without getting wet. There aren't even any boats here, but nothing can stop you now: you build a raft. You just need a few days' worth of food for your journey.
You don't speak the local language, so you can't read any ingredients lists. However, sometimes, allergens are listed in a language you do understand. You should be able to use this information to determine which ingredient contains which allergen and work out which foods are safe to take with you on your trip.
You start by compiling a list of foods (your puzzle input), one food per line. Each line includes that food's ingredients list followed by some or all of the allergens the food contains.
Each allergen is found in exactly one ingredient. Each ingredient contains zero or one allergen. Allergens aren't always marked; when they're listed (as in (contains nuts, shellfish) after an ingredients list), the ingredient that contains each listed allergen will be somewhere in the corresponding ingredients list. However, even if an allergen isn't listed, the ingredient that contains that allergen could still be present: maybe they forgot to label it, or maybe it was labeled in a language you don't know.
For example, consider the following list of foods:
mxmxvkd kfcds sqjhc nhms (contains dairy, fish)
trh fvjkl sbzzf mxmxvkd (contains dairy)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd sbzzf (contains fish)
The first food in the list has four ingredients (written in a language you don't understand): mxmxvkd, kfcds, sqjhc, and nhms. While the food might contain other allergens, a few allergens the food definitely contains are listed afterward: dairy and fish.
The first step is to determine which ingredients can't possibly contain any of the allergens in any food in your list. In the above example, none of the ingredients kfcds, nhms, sbzzf, or trh can contain an allergen. Counting the number of times any of these ingredients appear in any ingredients list produces 5: they all appear once each except sbzzf, which appears twice.
Determine which ingredients cannot possibly contain any of the allergens in your list. How many times do any of those ingredients appear?
*/
import (
"fmt"
"strings"
"github.com/simonski/aoc/utils"
)
func (app *Application) Y2020D21_Summary() *utils.Summary {
s := utils.NewSummary(2020, 21)
s.Name = "Allergen Assessment"
s.ProgressP1 = utils.Completed
s.ProgressP2 = utils.Completed
return s
}
func (app *Application) Y2020D21() {
app.Y2020D21P1()
app.Y2020D21P2()
}
func (app *Application) Y2020D21P1() {
}
func (app *Application) Y2020D21P2() {
}
func NewFoodDB(data string) *FoodDB {
splits := strings.Split(data, "\n")
db := FoodDB{}
db.Food = make(map[string]*Food)
db.Ingredients = make(map[string]*Ingredient)
db.Allergens = make(map[string]*Allergen)
for index, line := range splits {
db.AddFood(index, line)
}
return &db
}
type FoodDB struct {
Food map[string]*Food
Ingredients map[string]*Ingredient
Allergens map[string]*Allergen
NoAllergens map[string]bool
IdentifiedAllergens map[string]string
}
func (db *FoodDB) Debug() string {
line := fmt.Sprintf("DB: %v food, %v ingredients, %v allergens.\n", len(db.Food), len(db.Ingredients), len(db.Allergens))
line += fmt.Sprintf("No allergens: %v, IdentifiedAllergens: %v\n", db.NoAllergens, db.IdentifiedAllergens)
return line
}
func (db *FoodDB) GetIngredientsWithoutAllergens() []*Ingredient {
ingredients := make([]*Ingredient, 0)
return ingredients
}
func (db *FoodDB) BuildIngredientAllergyMap() map[string]string {
shared := make(map[string][]string)
for _, allergen := range db.Allergens {
sharedIngredients := allergen.CreateSharedIngredientsAcrossFoods()
// remove these ingredients from everything else
shared[allergen.Value] = sharedIngredients
}
// for key, value := range shared {
// fmt.Printf("%v = %v\n", key, value)
// }
for {
quit := true
for allergen, ingredients := range shared {
if len(ingredients) == 1 {
ingredient := ingredients[0]
for allergen2, ingredients2 := range shared {
if allergen2 == allergen {
continue
}
arr := make([]string, 0)
for _, i := range ingredients2 {
if i != ingredient {
arr = append(arr, i)
} else {
quit = false
}
shared[allergen2] = arr
}
}
}
}
// fmt.Printf("\n")
// for key, value := range shared {
// fmt.Printf("%v = %v\n", key, value)
// }
if quit {
break
}
}
s := make(map[string]string)
for key, value := range shared {
s[value[0]] = key
}
return s
}
func (db *FoodDB) Analyse() {
/*
trh fvjkl sbzzf mxmxvkd (contains dairy)
mxmxvkd kfcds sqjhc nhms (contains dairy, fish)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd sbzzf (contains fish)
for each ingredient
if total_occurrances is 1
put in does not occur anywhere
remove from food
if food.ingredients == 1 and food.allergens == 1:
# this allergen and ingredient are identified
create allergen/ingredient
# remove them both, remove the food
Step 1 Remove all 1-occurances that we can
kfcds nhms sbzzf, trh
trh fvjkl sbzzf mxmxvkd (contains dairy)
mxmxvkd kfcds sqjhc nhms (contains dairy, fish)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd sbzzf (contains fish)
Step 2
---- fvjkl ----- mxmxvkd (contains dairy)
mxmxvkd ---- sqjhc ---- (contains dairy, fish)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd ----- (contains fish)
// all ingrediens are in > 1 food now
// can we attach an ingredient to an allergen
// take ingredient 1 'fvjkl'
for an allergen 'dairy'
list foods, strip other allergens for this purpose
---- fvjkl ----- mxmxvkd (contains dairy)
mxmxvkd ---- sqjhc ---- (contains dairy, ----)
remove ingredients NOT in both
---- ------ ----- mxmxvkd (contains dairy)
mxmxvkd ---- ------ ---- (contains dairy, ---)
mxmxvkd is dairy
remove allergen dairy from everywhere
remove ingredient mxmxvkd from everywhere
IDENTIFY identify mxmxvkd as owner of allergen dairy
*/
noAllergens := make(map[string]bool)
identifiedAllergens := make(map[string]string)
iteration := 0
for {
quit := true
// Step 1
fmt.Printf("\n[Iteration %v Step 1]\n", iteration)
noAllergensThisIteration := 0
singleFoodIngredientsThisIteration := 0
for _, ingredient := range db.Ingredients {
if len(ingredient.Foods) == 1 {
// it is present in only 1 food
singleFoodIngredientsThisIteration++
for _, food := range ingredient.Foods {
// check the allergen list, if the allergens exist elsewhere, this ingredient has no allergens
for _, allergen := range food.Allergens {
if len(allergen.Foods) > 1 {
// then this ingredient has no allergens
fmt.Printf("[Iteration %v Step 1: Ingredient '%v' can be removed as a 'no-allergen' ingredient.\n", iteration, ingredient.Value)
noAllergens[ingredient.Value] = true
db.RemoveIngredient(ingredient.Value)
noAllergensThisIteration++
// we made a change, so we need to walk our database once more
quit = false
}
}
}
}
}
if singleFoodIngredientsThisIteration == 0 {
fmt.Printf("[Iteration %v Step 1] No ingredients are in a single food, all are in multiple.\n", iteration)
} else {
fmt.Printf("[Iteration %v Step 1] %v ingredients were in a single food, %v were no-allergens and removed..\n", iteration, singleFoodIngredientsThisIteration, noAllergensThisIteration)
}
/*
// Step 2
---- fvjkl ----- mxmxvkd (contains dairy)
mxmxvkd ---- sqjhc ---- (contains dairy, fish)
sqjhc fvjkl (contains soy)
sqjhc mxmxvkd ----- (contains fish)
all ingredients are in > 1 food now
now go over allergens themselves
first allergen 'dairy', list foods
---- fvjkl ----- mxmxvkd (contains dairy)
mxmxvkd ---- sqjhc ---- (contains dairy, fish)
for dairy, the common ingredient is mxmxvkd
so mxmxvkd is dairy
add this to the ActualAllergens, then remove mxmxvkd ingredient AND 'dairy' allergen
quit = false
for each food
if number of allergens == 0
each ingredeint in this food is a zero allergen ingredient
ADD to NO ALLERGENS
REMOVE INGREDIENT
quit = false
*/
fmt.Printf("[Iteration %v Step 2]\n", iteration)
for _, allergen := range db.Allergens {
fmt.Printf("[Iteration %v Step 2] allergen %v has %v foods.\n", iteration, allergen.Value, len(allergen.Foods))
// if len(allergen.Foods) > 1 {
sharedIngredients := allergen.CreateSharedIngredientsAcrossFoods()
fmt.Printf("[Iteration %v Step 2] Shared Ingredients for allergen %v : %v\n", iteration, allergen.Value, sharedIngredients)
if len(sharedIngredients) == 1 {
// this is the only common ingredient, this must be the ingredient to use
ingredient := sharedIngredients[0]
identifiedAllergens[ingredient] = allergen.Value
db.RemoveIngredient(ingredient)
db.RemoveAllergen(allergen.Value)
quit = false
}
// this ingredient appears in > 1 allergen
// remove any ingredients that are NOT in both for this allergen as they cannot be it
// }
}
for _, food := range db.Food {
if len(food.Allergens) == 0 {
// each ingredeint in this food is a zero allergen ingredient
for _, ingredient := range food.Ingredients {
noAllergens[ingredient.Value] = true
db.RemoveIngredient(ingredient.Value)
quit = false
// ADD to NO ALLERGENS
// REMOVE INGREDIENT
// quit = false
}
}
}
// for an allergen 'dairy'
// list foods, strip other allergens for this purpose
// ---- fvjkl ----- mxmxvkd (contains dairy)
// mxmxvkd ---- sqjhc ---- (contains dairy, ----)
// remove ingredients NOT in both
// ---- ------ ----- mxmxvkd (contains dairy)
// mxmxvkd ---- ------ ---- (contains dairy, ---)
// mxmxvkd is dairy
// remove allergen dairy from everywhere
// remove ingredient mxmxvkd from everywhere
// IDENTIFY identify mxmxvkd as owner of allergen dairy
if quit {
fmt.Printf("Quitting.\n")
break
}
iteration++
}
// Now we have removed the ingredients
db.NoAllergens = noAllergens
db.IdentifiedAllergens = identifiedAllergens
}
func (db *FoodDB) AddFood(index int, line string) *Food {
line = strings.ReplaceAll(line, "(", "")
line = strings.ReplaceAll(line, ")", "")
line = strings.ReplaceAll(line, ",", "")
splits := strings.Split(line, " ")
isIngredients := true
key := fmt.Sprintf("%v", index)
food := &Food{Key: key, Value: line}
food.Ingredients = make(map[string]*Ingredient)
food.Allergens = make(map[string]*Allergen)
db.Food[key] = food
for _, value := range splits {
if value == "contains" {
isIngredients = false
continue
}
if isIngredients {
ingredient := db.GetIngredient(value)
food.AddIngredient(ingredient)
ingredient.AddFood(food)
} else {
allergen := db.GetAllergen(value)
food.AddAllergen(allergen)
allergen.AddFood(food)
}
}
return food
}
func (db *FoodDB) RemoveIngredient(value string) {
delete(db.Ingredients, value)
for _, a := range db.Allergens {
a.RemoveIngredient(value)
}
for _, f := range db.Food {
f.RemoveIngredient(value)
}
}
func (db *FoodDB) RemoveAllergen(value string) {
delete(db.Allergens, value)
for _, i := range db.Ingredients {
i.RemoveAllergen(value)
}
for _, f := range db.Food {
f.RemoveAllergen(value)
}
}
func (db *FoodDB) GetIngredient(value string) *Ingredient {
ingredient, exists := db.Ingredients[value]
if exists {
return ingredient
}
ingredient = &Ingredient{Value: value}
ingredient.Allergens = make(map[string]*Allergen)
ingredient.Foods = make(map[string]*Food)
db.Ingredients[value] = ingredient
return ingredient
}
func (db *FoodDB) GetAllergen(value string) *Allergen {
allergen, exists := db.Allergens[value]
if exists {
return allergen
}
allergen = &Allergen{Value: value}
allergen.Ingredients = make(map[string]*Ingredient)
allergen.Foods = make(map[string]*Food)
db.Allergens[value] = allergen
return allergen
}
type Food struct {
Key string
Value string
Ingredients map[string]*Ingredient
Allergens map[string]*Allergen
}
func (food *Food) ContainsIngredient(value string) bool {
for _, v := range food.Ingredients {
if v.Value == value {
return true
}
}
return false
// _, exists := food.Ingredients[value]
// return exists
}
func (food *Food) ContainsAllergen(value string) bool {
for _, v := range food.Allergens {
if v.Value == value {
return true
}
}
return false
// _, exists := food.Allergens[value]
// return exists
}
func (food *Food) NumberOfIngredients() int {
return len(food.Ingredients)
}
func (food *Food) NumberOfAllergens() int {
return len(food.Allergens)
}
func (food *Food) AddIngredient(ingredient *Ingredient) {
food.Ingredients[ingredient.Value] = ingredient
}
func (food *Food) RemoveIngredient(value string) {
delete(food.Ingredients, value)
}
func (food *Food) AddAllergen(allergen *Allergen) {
food.Allergens[allergen.Value] = allergen
}
func (food *Food) RemoveAllergen(value string) {
delete(food.Allergens, value)
}
type Ingredient struct {
Value string
Foods map[string]*Food
Allergens map[string]*Allergen
}
func (ingredient *Ingredient) AddFood(food *Food) {
ingredient.Foods[food.Value] = food
}
func (ingredient *Ingredient) AddAllergen(allergen *Allergen) {
ingredient.Allergens[allergen.Value] = allergen
}
func (ingredient *Ingredient) RemoveAllergen(value string) {
delete(ingredient.Allergens, value)
}
type Allergen struct {
Value string
Foods map[string]*Food
Ingredients map[string]*Ingredient
}
func (allergen *Allergen) AddFood(food *Food) {
allergen.Foods[food.Value] = food
}
func (allergen *Allergen) AddIngredient(ingredient *Ingredient) {
allergen.Ingredients[ingredient.Value] = ingredient
}
func (allergen *Allergen) RemoveIngredient(value string) {
delete(allergen.Ingredients, value)
}
func (allergen *Allergen) CreateSharedIngredientsAcrossFoods() []string {
numOfFoods := len(allergen.Foods)
sharedIngredients := make([]string, 0)
countOfIngredients := make(map[string]int)
for _, food := range allergen.Foods {
for _, ingredient := range food.Ingredients {
// fmt.Printf("Allergen '%v' has food '%v' which has ingredient '%v'\n", allergen.Value, food.Value, ingredient.Value)
value, exists := countOfIngredients[ingredient.Value]
if exists {
value++
countOfIngredients[ingredient.Value] = value
// fmt.Printf("ingredient '%v' count is now '%v'\n", ingredient.Value, value)
} else {
value = 1
countOfIngredients[ingredient.Value] = value
// fmt.Printf("ingredeient '%v' count is now %v\n", ingredient.Value, value)
}
}
}
for k, v := range countOfIngredients {
if v == numOfFoods {
sharedIngredients = append(sharedIngredients, k)
}
}
return sharedIngredients
}