You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
3.6 KiB

  1. using System;
  2. using static System.Console;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text.RegularExpressions;
  7. using Xunit;
  8. namespace AdventOfCode {
  9. struct Contains {
  10. public Contains(string outer, string inner, int count) {
  11. Outer = outer;
  12. Inner = inner;
  13. Count = count;
  14. }
  15. public string Outer;
  16. public string Inner;
  17. public int Count;
  18. public override string ToString() {
  19. return $"{Outer} contains {Count} {Inner}";
  20. }
  21. }
  22. public class Day07 {
  23. static void AddRules(string rule, List<Contains> contains) {
  24. string[] tokens = rule.Split(" bags contain ");
  25. string color = tokens[0];
  26. string[] otherBags = tokens[1].Split(", ");
  27. foreach (var s in otherBags) {
  28. Match m = Regex.Match(s, @"^(\d+) (\w+ \w+) bag");
  29. if (!m.Success) {
  30. continue;
  31. }
  32. int count = int.Parse(m.Groups[1].Value);
  33. Contains c = new Contains(color, m.Groups[2].Value, count);
  34. contains.Add(c);
  35. }
  36. }
  37. static List<Contains> ParseRules(string[] rules) {
  38. var contains = new List<Contains>();
  39. rules.ToList().ForEach(rule => AddRules(rule, contains));
  40. return contains;
  41. }
  42. static int WhatCanContain(List<Contains> contains, string target) {
  43. var result = new HashSet<string>();
  44. int setSize = 0;
  45. result.Add(target);
  46. // transitive closure: keep adding things, until a round of doing so doesn't add anything new
  47. while (true) {
  48. foreach (Contains c in contains) {
  49. if (result.Contains(c.Inner)) {
  50. result.Add(c.Outer);
  51. }
  52. }
  53. if (result.Count() == setSize) { // nothing new to add, we're done
  54. return result.Count() - 1; // our target doesn't actually contain itself
  55. }
  56. setSize = result.Count();
  57. }
  58. }
  59. static int CountBags(List<Contains> rules, string target) {
  60. int total = 1;
  61. foreach (Contains rule in rules) {
  62. if (rule.Outer == target) {
  63. total += rule.Count * CountBags(rules, rule.Inner);
  64. }
  65. }
  66. return total;
  67. }
  68. static int Part1() {
  69. List<Contains> contains = ParseRules(File.ReadAllLines(Util.RootDir + "day07.txt"));
  70. return WhatCanContain(contains, "shiny gold");
  71. }
  72. static int Part2() {
  73. List<Contains> contains = ParseRules(File.ReadAllLines(Util.RootDir + "day07.txt"));
  74. return CountBags(contains, "shiny gold") - 1;
  75. }
  76. [Fact]
  77. public static void Test() {
  78. string[] example =
  79. @"light red bags contain 1 bright white bag, 2 muted yellow bags.
  80. dark orange bags contain 3 bright white bags, 4 muted yellow bags.
  81. bright white bags contain 1 shiny gold bag.
  82. muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.
  83. shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.
  84. dark olive bags contain 3 faded blue bags, 4 dotted black bags.
  85. vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
  86. faded blue bags contain no other bags.
  87. dotted black bags contain no other bags.".Split('\n');
  88. Assert.Equal(4, WhatCanContain(ParseRules(example), "shiny gold"));
  89. Assert.Equal(213, Part1());
  90. string[] example2 =
  91. @"shiny gold bags contain 2 dark red bags.
  92. dark red bags contain 2 dark orange bags.
  93. dark orange bags contain 2 dark yellow bags.
  94. dark yellow bags contain 2 dark green bags.
  95. dark green bags contain 2 dark blue bags.
  96. dark blue bags contain 2 dark violet bags.
  97. dark violet bags contain no other bags.".Split('\n');
  98. Assert.Equal(127, CountBags(ParseRules(example2), "shiny gold"));
  99. Assert.Equal(38426, Part2());
  100. }
  101. }
  102. }