From ecafea7adf07882e0bbe184622e178ba7249bcab Mon Sep 17 00:00:00 2001 From: James Griffin Date: Mon, 21 Dec 2020 21:23:33 -0400 Subject: [PATCH] Day 21: Part 2 Signed-off-by: James Griffin --- main.go | 1 + twentyone/day_twentyone.go | 112 ++++++++++++++++++++++++++++++-- twentyone/day_twentyone_test.go | 34 +++++++++- 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/main.go b/main.go index c9e5865..8bd5d6e 100644 --- a/main.go +++ b/main.go @@ -89,4 +89,5 @@ func main() { // Day 21 fmt.Println(twentyone.PartOne()) + fmt.Println(twentyone.PartTwo()) } diff --git a/twentyone/day_twentyone.go b/twentyone/day_twentyone.go index 099d4b7..2f60000 100644 --- a/twentyone/day_twentyone.go +++ b/twentyone/day_twentyone.go @@ -4,6 +4,7 @@ import ( "bufio" "fmt" "os" + "sort" "strings" ) @@ -13,9 +14,9 @@ type food struct { } type list struct { - foods []food - allergens map[string]bool - possibleIngredientAllergens map[string][]string + foods []food + allergens map[string]bool + ingredientAllergens map[string][]string } func (l *list) load(filename string) error { @@ -26,7 +27,7 @@ func (l *list) load(filename string) error { defer file.Close() l.allergens = map[string]bool{} - l.possibleIngredientAllergens = map[string][]string{} + l.ingredientAllergens = map[string][]string{} scanner := bufio.NewScanner(file) for scanner.Scan() { @@ -41,7 +42,7 @@ func (l *list) load(filename string) error { ingredients := strings.Split(foodParts[0], " ") for _, i := range ingredients { f.ingredients[i] = true - l.possibleIngredientAllergens[i] = []string{} + l.ingredientAllergens[i] = []string{} } allergens := strings.Split(foodParts[1], ")") @@ -85,10 +86,72 @@ func (l *list) isolateIngredientAllergens() { for i, cause := range possibleIngredients { if cause { - l.possibleIngredientAllergens[i] = append(l.possibleIngredientAllergens[i], allergen) + l.ingredientAllergens[i] = append(l.ingredientAllergens[i], allergen) } } } + + tmpIngredients := map[string][]string{} + // Remove duplicate allergens + for ingredient, allergens := range l.ingredientAllergens { + if len(allergens) < 1 { + continue + } + + tmpIngredients[ingredient] = allergens + } + + stable := false + for !stable { + causes := l.getIngredientsWithAllergens() + // Check to see if every ingredient has 1 allergen + stable = true + for _, allergens := range causes { + if len(allergens) != 1 { + stable = false + } + } + + if stable { + break + } + + // Find all allergens with with only 1 option. Remove that option from others + for i, a := range causes { + if len(a) == 1 { + l.claimAllergen(i, a[0]) + } + } + } +} + +func (l *list) getIngredientsWithAllergens() map[string][]string { + tmpIngredients := map[string][]string{} + // Remove duplicate allergens + for ingredient, allergens := range l.ingredientAllergens { + if len(allergens) < 1 { + continue + } + + tmpIngredients[ingredient] = allergens + } + return tmpIngredients +} + +func (l *list) claimAllergen(ingredient, allergen string) { + for i, alls := range l.ingredientAllergens { + if i == ingredient { + continue + } + + filtered := []string{} + for _, a := range alls { + if a != allergen { + filtered = append(filtered, a) + } + } + l.ingredientAllergens[i] = filtered + } } func (l *list) countAllergenFreeAppearances() (int, []string) { @@ -96,7 +159,7 @@ func (l *list) countAllergenFreeAppearances() (int, []string) { // Find allergen free ingredients ingredients := []string{} - for ingredient, allergens := range l.possibleIngredientAllergens { + for ingredient, allergens := range l.ingredientAllergens { if len(allergens) == 0 { ingredients = append(ingredients, ingredient) } @@ -116,6 +179,31 @@ func (l *list) countAllergenFreeAppearances() (int, []string) { return count, ingredients } +func (l *list) canonicalDangerList() string { + dangerous := []string{} + + for _, allergens := range l.ingredientAllergens { + if len(allergens) == 1 { + dangerous = append(dangerous, allergens[0]) + } + } + + sort.Strings(dangerous) + ingredients := []string{} + for _, allergen := range dangerous { + for ingredient, allergens := range l.ingredientAllergens { + if len(allergens) == 1 { + if allergens[0] == allergen { + ingredients = append(ingredients, ingredient) + break + } + } + } + } + + return strings.Join(ingredients, ",") +} + // PartOne How many times do allergen free ingredients appear func PartOne() string { l := list{} @@ -125,3 +213,13 @@ func PartOne() string { count, ingredients := l.countAllergenFreeAppearances() return fmt.Sprintf("There are %d appearances of %d allergen free ingredients", count, len(ingredients)) } + +// PartTwo What is the canonical dangerous list of ingredients +func PartTwo() string { + l := list{} + l.load("twentyone/input.txt") + + l.isolateIngredientAllergens() + + return fmt.Sprintf("The canonical list of dangerous ingredients is %q", l.canonicalDangerList()) +} diff --git a/twentyone/day_twentyone_test.go b/twentyone/day_twentyone_test.go index 187e409..401ba9e 100644 --- a/twentyone/day_twentyone_test.go +++ b/twentyone/day_twentyone_test.go @@ -16,7 +16,7 @@ func Test_loading_list(t *testing.T) { t.FailNow() } - ingredients := len(l.possibleIngredientAllergens) + ingredients := len(l.ingredientAllergens) if ingredients != 7 { t.Logf("Expected 7 ingredients, found %d", ingredients) t.FailNow() @@ -38,3 +38,35 @@ func Test_find_allergenfree(t *testing.T) { t.FailNow() } } + +func Test_canonical_list(t *testing.T) { + l := list{} + err := l.load("sample.txt") + if err != nil { + t.Logf(err.Error()) + t.FailNow() + } + + l.isolateIngredientAllergens() + list := l.canonicalDangerList() + if list != "mxmxvkd,sqjhc,fvjkl" { + t.Logf("Expected list of mxmxvkd,sqjhc,fvjkl, got %s", list) + t.Fail() + } +} + +func Test_fullcanonical_list(t *testing.T) { + l := list{} + err := l.load("input.txt") + if err != nil { + t.Logf(err.Error()) + t.FailNow() + } + + l.isolateIngredientAllergens() + list := l.canonicalDangerList() + if list != "vmhqr,qxfzc,khpdjv,gnrpml,xrmxxvn,rfmvh,rdfr,jxh" { + t.Logf("Expected list \"vmhqr,qxfzc,khpdjv,gnrpml,xrmxxvn,rfmvh,rdfr,jxh\", got %s", list) + t.Fail() + } +}