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.

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