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.

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