package main import ( "bufio" "fmt" "io" "os" "regexp" "sort" "strconv" "strings" ) type BagRegistry struct { knownBags map[string]*Bag } func (registry *BagRegistry) Add(bag *Bag) { if _, ok := registry.knownBags[bag.Color]; ok { panic("Bag of that color was already registered") } registry.knownBags[bag.Color] = bag } func (registry *BagRegistry) GetBagByColor(color string) (bag *Bag, ok bool) { bag, ok = registry.knownBags[color] return } func (registry *BagRegistry) GetAllColors() []string { colors := []string{} for color := range registry.knownBags { colors = append(colors, color) } sort.Strings(colors) return colors } func (registry *BagRegistry) GetColorsThatContain(innerColor string) (result []string) { result = []string{} for _, color := range registry.GetAllColors() { bag := registry.knownBags[color] if bag.GetMinimumAmountOf(innerColor, registry) > 0 { result = append(result, color) } } return } type Bag struct { Color string Rules []*BagRule } func (bag *Bag) GetMinimumAmountOf(innerBagColor string, registry *BagRegistry) int { minAmount := 0 for _, rule := range bag.Rules { if rule.Bag == innerBagColor { if minAmount == 0 || minAmount > rule.Amount { minAmount = rule.Amount } } innerBag, ok := registry.GetBagByColor(rule.Bag) if !ok { panic("Invalid rule, bag of this color not found") } innerAmount := innerBag.GetMinimumAmountOf(innerBagColor, registry) if innerAmount > 0 && (minAmount == 0 || minAmount > innerAmount) { minAmount = innerAmount } } return minAmount } func (bag *Bag) GetRequiredAmountOfBags(registry *BagRegistry) int { amount := 0 for _, rule := range bag.Rules { bag, ok := registry.GetBagByColor(rule.Bag) if !ok { panic("Can't find inner bag") } amount += rule.Amount + (rule.Amount * bag.GetRequiredAmountOfBags(registry)) } return amount } type BagRule struct { Bag string Amount int } var rxBagDescription = regexp.MustCompile(`^(.+) bags contain (.+)\.?$`) var rxBagRule = regexp.MustCompile(`^(\d+) (.+) bags?$`) func ParseRule(line string) *Bag { substrings := rxBagDescription.FindStringSubmatch(line) if substrings == nil { panic("Bag rule can not be parsed") } substrings[2] = strings.TrimRight(substrings[2], "., ") containedBags := strings.Split(substrings[2], ", ") bag := &Bag{ Color: substrings[1], Rules: []*BagRule{}, } // Does rule only say no other bags inside? // Example: "light red bags contain no other bags" if len(containedBags) == 1 && containedBags[0] == "no other bags" { return bag } // Can contain other bags. // Example: "light red bags contain 1 bright white bag, 2 muted yellow bags." for _, containedBag := range containedBags { substrings = rxBagRule.FindStringSubmatch(containedBag) if substrings == nil { panic("Could not parse description: " + containedBag) } containedBagAmount, err := strconv.Atoi(substrings[1]) if err != nil { panic("Could not parse number from contained bag rule") } containedBagColor := substrings[2] bag.Rules = append(bag.Rules, &BagRule{ Bag: containedBagColor, Amount: containedBagAmount, }) } return bag } func main() { f, err := os.Open("input") if err != nil { panic(err) } r := bufio.NewReader(f) registry := &BagRegistry{ knownBags: map[string]*Bag{}, } for { line, err := r.ReadString('\n') if err == io.EOF { break } if err != nil { panic(err) } line = strings.TrimSpace(line) bag := ParseRule(line) registry.Add(bag) } // Find out how many bags are necessary for one shiny gold bag shinyGoldBag, ok := registry.GetBagByColor("shiny gold") if !ok { panic("Where my shiny gold bag be?!") } fmt.Printf("%d bags needed for one shiny gold bag\n", shinyGoldBag.GetRequiredAmountOfBags(registry)) }