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.

193 lines
5.2 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. public class Day04 {
  10. static string[] requiredFields = {
  11. // "cid" not required because we are hackers
  12. "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"
  13. };
  14. static string[] validEyeColors = {
  15. "amb", "blu", "brn", "gry", "grn", "hzl", "oth"
  16. };
  17. static bool IsValidPassport1(string input) {
  18. string[] fields = input.Split(' ');
  19. var fieldsPresent = new HashSet<string>();
  20. foreach (string field in fields) {
  21. string[] tokens = field.Split(':');
  22. string key = tokens[0];
  23. fieldsPresent.Add(key);
  24. }
  25. return fieldsPresent.IsSupersetOf(requiredFields);
  26. }
  27. static int ParseInt(string value) {
  28. int result;
  29. if (!int.TryParse(value, out result)) {
  30. return -1;
  31. }
  32. return result;
  33. }
  34. static bool IsValidPassport2(string input) {
  35. string[] fields = input.Split(' ');
  36. var fieldsPresent = new HashSet<string>();
  37. foreach (string field in fields) {
  38. if (field.Length == 0) {
  39. continue;
  40. }
  41. string[] tokens = field.Split(':');
  42. string key = tokens[0];
  43. string value = tokens[1];
  44. fieldsPresent.Add(key);
  45. if (key == "byr") {
  46. int year = ParseInt(value);
  47. if (year < 1920 || year > 2002) {
  48. return false;
  49. }
  50. } else if (key == "iyr") {
  51. int year = ParseInt(value);
  52. if (year < 2010 || year > 2020) {
  53. return false;
  54. }
  55. } else if (key == "eyr") {
  56. int year = ParseInt(value);
  57. if (year < 2020 || year > 2030) {
  58. return false;
  59. }
  60. } else if (key == "hgt") {
  61. string unit = value.Substring(value.Length - 2);
  62. int amount = ParseInt(value.Substring(0, value.Length - 2));
  63. if (unit == "cm") {
  64. if (amount < 150 || amount > 193) {
  65. return false;
  66. }
  67. } else if (unit == "in") {
  68. if (amount < 59 || amount > 76) {
  69. return false;
  70. }
  71. } else { // not cm or in
  72. return false;
  73. }
  74. } else if (key == "hcl") {
  75. Regex color = new Regex(@"^#[0-9a-f]{6}$");
  76. if (!color.IsMatch(value)) {
  77. return false;
  78. }
  79. } else if (key == "ecl") {
  80. if (!validEyeColors.Contains(value)) {
  81. return false;
  82. }
  83. } else if (key == "pid") {
  84. Regex pid = new Regex(@"^[0-9]{9}$");
  85. if (!pid.IsMatch(value)) {
  86. return false;
  87. }
  88. }
  89. }
  90. return fieldsPresent.IsSupersetOf(requiredFields);
  91. }
  92. static List<string> ParsePassports(string[] input) {
  93. var result = new List<string>();
  94. string passport = "";
  95. foreach (string line in input) {
  96. if (line == "") {
  97. result.Add(passport);
  98. passport = "";
  99. } else {
  100. passport += line + " ";
  101. }
  102. }
  103. result.Add(passport);
  104. return result;
  105. }
  106. static int CountValidPassports1(string[] input) {
  107. return ParsePassports(input).Count(IsValidPassport1);
  108. }
  109. static int CountValidPassports2(string[] input) {
  110. return ParsePassports(input).Count(IsValidPassport2);
  111. }
  112. static int Part1() {
  113. string[] input = File.ReadAllLines(Util.RootDir + "day04.txt");
  114. return CountValidPassports1(input);
  115. }
  116. static int Part2() {
  117. string[] input = File.ReadAllLines(Util.RootDir + "day04.txt");
  118. return CountValidPassports2(input);
  119. }
  120. [Fact]
  121. public static void Test() {
  122. string[] example =
  123. @"ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
  124. byr:1937 iyr:2017 cid:147 hgt:183cm
  125. iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
  126. hcl:#cfa07d byr:1929
  127. hcl:#ae17e1 iyr:2013
  128. eyr:2024
  129. ecl:brn pid:760753108 byr:1931
  130. hgt:179cm
  131. hcl:#cfa07d eyr:2025 pid:166559648
  132. iyr:2011 ecl:brn hgt:59in".Split('\n');
  133. Assert.Equal(2, CountValidPassports1(example));
  134. Assert.Equal(256, Part1());
  135. string[] invalidPassports =
  136. @"eyr:1972 cid:100
  137. hcl:#18171d ecl:amb hgt:170 pid:186cm iyr:2018 byr:1926
  138. iyr:2019
  139. hcl:#602927 eyr:1967 hgt:170cm
  140. ecl:grn pid:012533040 byr:1946
  141. hcl:dab227 iyr:2012
  142. ecl:brn hgt:182cm pid:021572410 eyr:2020 byr:1992 cid:277
  143. hgt:59cm ecl:zzz
  144. eyr:2038 hcl:74454a iyr:2023
  145. pid:3556412378 byr:2007".Split('\n');
  146. string[] validPassports =
  147. @"pid:087499704 hgt:74in ecl:grn iyr:2012 eyr:2030 byr:1980
  148. hcl:#623a2f
  149. eyr:2029 ecl:blu cid:129 byr:1989
  150. iyr:2014 pid:896056539 hcl:#a97842 hgt:165cm
  151. hcl:#888785
  152. hgt:164cm byr:2001 iyr:2015 cid:88
  153. pid:545766238 ecl:hzl
  154. eyr:2022
  155. iyr:2010 hgt:158cm hcl:#b6652a ecl:blu byr:1944 eyr:2021 pid:093154719".Split('\n');
  156. List<string> invalid = ParsePassports(invalidPassports);
  157. Assert.Equal(4, invalid.Count);
  158. Assert.All(invalid, item => Assert.False(IsValidPassport2(item)));
  159. List<string> valid = ParsePassports(validPassports);
  160. Assert.Equal(4, valid.Count);
  161. Assert.All(valid, item => Assert.True(IsValidPassport2(item)));
  162. Assert.Equal(198, Part2());
  163. }
  164. }
  165. }