DEV Community

Discussion on: Advent of Code 2020 Solution Megathread - Day 4: Passport Processing

Collapse
 
mgasparel profile image
Mike Gasparelli

This is a fun little thread. Since there's no C# representation, I figured I would share my solution. I have a little framework code that handles some stuff behind the scenes such as locating and reading the file, as well as validating the solving on the sample data returns the correct solution, so you won't see any of that in the code here:

Part 1

    public class Part1 : Puzzle<IEnumerable<Passport>, int>
    {
        public override int SampleAnswer => 2;

        public override IEnumerable<Passport> ParseInput(string rawInput)
        {
            return rawInput
                .Split(Environment.NewLine + Environment.NewLine)
                .Where(x => x.Length > 0)
                .Select(ParsePassport);
        }

        protected Passport ParsePassport(string chunk)
        {
            var keyValuePairs = chunk
                .Replace(Environment.NewLine, " ")
                .TrimEnd()
                .Split(' ')
                .Select(pair => new KeyValuePair<string, string>(pair[..3], pair[4..]));

            return new Passport(new Dictionary<string, string>(keyValuePairs));
        }

        public override int Solve(IEnumerable<Passport> input)
            => input.Count(p => p.IsValid());
    }
Enter fullscreen mode Exit fullscreen mode

Part 2

    public class Part2 : Part1
    {
        public override int SampleAnswer => 2;

        public override IEnumerable<Passport> ParseInput(string rawInput)
        {
            return rawInput
                .Split(Environment.NewLine + Environment.NewLine)
                .Where(x => x.Length > 0)
                .Select(ParsePassport);
        }

        public override int Solve(IEnumerable<Passport> input)
            => input.Count(p => p.IsStrictValid());
    }
Enter fullscreen mode Exit fullscreen mode

Passport

public class Passport
    {
        HashSet<string> mandatory = new() { "byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid" };
        string[] eyeColors = { "amb", "blu", "brn", "gry", "grn", "hzl", "oth" };
        Dictionary<string, string> fields = new();

        public Passport(Dictionary<string, string> fields)
        {
            this.fields = fields;
        }

        public bool IsValid()
            => mandatory.All(x => fields.ContainsKey(x));

        public bool IsStrictValid()
            => IsValid() && fields.All(f => IsFieldValid(f.Key, f.Value));

        bool IsFieldValid(string key, string value)
            => key switch
            {
                "byr" => IsYearValid(value, 1920, 2002),
                "iyr" => IsYearValid(value, 2010, 2020),
                "eyr" => IsYearValid(value, 2020, 2030),
                "hgt" => IsHeightValid(value),
                "hcl" => IsHairColorValid(value),
                "ecl" => IsEyeColorValid(value),
                "pid" => IsPidValid(value),
                _ => true
            };

        private bool IsPidValid(string value)
            => value.Length == 9
                && long.TryParse(value, out _);

        private bool IsEyeColorValid(string value)
            => eyeColors.Contains(value);

        private bool IsHairColorValid(string value)
            => value[0] == '#'
                && value.Length == 7
                && value
                    .ToLower()
                    .Skip(1)
                    .All(c => c >= '0' && c <= '9' || c>= 'a' && c <= 'z');

        private bool IsHeightValid(string value)
            => int.TryParse(value[..^2], out int iValue)
                && value[^2..] == "cm"
                    ? iValue >= 150 && iValue <= 193
                    : iValue >= 59 && iValue <= 76;

        bool IsYearValid(string value, int min, int max)
            => int.TryParse(value, out int iValue)
                && iValue >= min && iValue <= max;
    }
Enter fullscreen mode Exit fullscreen mode