13 votes

Day 4: Passport Processing

Today's problem description: https://adventofcode.com/2020/day/4


Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c.

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

19 comments

  1. [2]
    Comment deleted by author
    Link
    1. fazit
      Link Parent
      Aaaah that's so nice with the Lambda Function Pointers. I was writing my fourth if-elif-branch and thought "Well, this is annoying?!" and I totally forgot about that trick. And using regex looks...

      Aaaah that's so nice with the Lambda Function Pointers. I was writing my fourth if-elif-branch and thought "Well, this is annoying?!" and I totally forgot about that trick.
      And using regex looks also way nicer than my "let's just convert the string to an int and see if that works" approach.

      4 votes
  2. PapaNachos
    (edited )
    Link
    Wow Part B was annoying! This one has a lot of small bits that need to be exactly right and it's hard to track down exactly what's wrong. Make sure to use the examples! Day 4 Part A - Python The...

    Wow Part B was annoying! This one has a lot of small bits that need to be exactly right and it's hard to track down exactly what's wrong. Make sure to use the examples!

    Day 4 Part A - Python The tricky part here is that you need to track stuff across multiple lines. There are a number of ways to do this, I chose to build a password database, but you could easily just check each passport as it goes through. I wasn't sure what part B would be like, so I possibly overdesigned it.

    I was tempted to use regex in part A to do shove each passport into a dictionary, but I realized splitting on spaces and then again on ':' would accomplish what I wanted more easily. I ended up having to use some regex in part B anyway.

    data = input_data.split('\n')
    
    passport_list = []
    
    current_passport = {}
    for line in data:
        if line == '':
            passport_list.append(current_passport)
            current_passport = {}
        else:
            for entry in line.split(' '):
                key, val, = entry.split(':')
                current_passport[key] = val
    passport_list.append(current_passport)
    
    valid_count = 0
    for passport in passport_list:
        length = len(passport.keys())
        if length >= 8 or (length >= 7 and 'cid' not in passport.keys()):
            valid_count = valid_count + 1
            #print('Valid')
        else:
            #print('Invalid')
            pass
    print(f'{valid_count} valid passports found!')
    
    Day 4 Part B - Python FUCKING HELL THIS WAS ANNOYING. So many fiddly bits. Basically I started by assuming any given passport is valid and checking if there was any reason to invalidate it. I designed an simple check for each criteria and then ran every passport through them. If a given passport passes all the checks, it's valid. I used a mixture of standard string functions, some type casting, and some regex for good measure. Basically whatever seemed best for each individual question.

    I'm not really happy with my if-else chain, but it did get the job done

    data = input_data.split('\n')
    
    passport_list = []
    
    current_passport = {}
    for line in data:
        if line == '':
            passport_list.append(current_passport)
            current_passport = {}
        else:
            for entry in line.split(' '):
                key, val, = entry.split(':')
                current_passport[key] = val
    passport_list.append(current_passport)
    
    valid_count = 0
    for passport in passport_list:
        length = len(passport.keys())
        
        is_valid = True
        
        #print('Testing passport:')
        #print(passport)
        
        if length >= 8 or (length >= 7 and 'cid' not in passport.keys()):
            #check bry
            byr = passport['byr']
            if len(byr) == 4 and int(byr) >= 1920 and int(byr) <= 2002:
                pass
            else:
                #print('Failed due to byr')
                is_valid = False
            #check iyr
            iyr = passport['iyr']
            if len(iyr) == 4 and int(iyr) >= 2010 and int(iyr) <= 2020:
                pass
            else:
                #print('Failed due to iyr')
                is_valid = False
            #check iyr
            eyr = passport['eyr']
            if len(eyr) == 4 and int(eyr) >= 2020 and int(eyr) <= 2030:
                pass
            else:
                #print('Failed due to eyr')
                is_valid = False
            #check hgt
            hgt = passport['hgt']
            if hgt.endswith('in') and int(hgt[:-2]) >= 59 and int(hgt[:-2]) <= 76:
                pass
            elif hgt.endswith('cm') and int(hgt[:-2]) >= 150 and int(hgt[:-2]) <= 193:
                pass
            else:
                #print('Failed due to hgt')
                is_valid = False
            #check hcl
            hcl = passport['hcl']
            pattern = re.compile('#[0-9a-f]{6}')
            if pattern.match(hcl):
                pass
            else:
                #print('Failed due to hcl')
                is_valid = False
            #check ecl
            ecl = passport['ecl']
            if ecl in ['amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth']:
                pass
            else:
                #print('Failed due to ecl')
                is_valid = False
            pid = passport['pid']
            pattern = re.compile('^\d{9}$')
            if pattern.match(pid):
                pass
            else:
                #print('Failed due to pid')
                is_valid = False
            pass
            #print('Valid')
        else:
            #print('Failed due to len')
            is_valid = False
            #print('Invalid')
        if is_valid:
            #print('Valid!')
            valid_count = valid_count + 1
        else:
            #print('Invalid')
            pass
    
        
    print(f'{valid_count} valid passports found!')
    
    Tips for Beginners
    • This problem in particular makes it very easy to make a mistake and not understand why your answer isn't coming out correct

    • When you're looking at a given passport, you need to be able to manage taking in your data from several different lines. There are several different ways to do this. Personally, I loaded my data into a dictionary (a specific Python data structure). Whenever I saw a blank line, I knew I was done gathering data for the from that passport, so I moved on and created a new dictionary for the next passport. You could either check each passport as it comes in, or you could wait until you have them all stored. There are pros and cons to both approaches, but that discussion is beyond the scope of this problem

    • Part B is MUCH harder than Part A. At least it was for me. Don't get discouraged if it gives you trouble. Just breath. You might need to spend some time deep diving

    • You have several different characteristics you need to check. If even 1 is wrong, the whole thing comes back wrong. So you need to structure your code in a way that a single error will invalidate the whole thing. I started by assuming every passport was valid. Then I checked it against each of the criteria I was looking for, If any of them was wrong, I flipped a flag that said the whole passport was invalid. Then at the end, I check if the flag was still valid. If nothing had flipped it, that means it made it through all the checks. It's sort of like checking it in reverse.

    • There are other ways you could do it too. Another way you could try to do it is to find out how many checks you need to pass. Start a count at 0 for each passport. When the passport passes a check (for example, you see that the eye color is one of the choices) you add +1 to the count. At the end if the count is above a certain number, you know you passed all the checks. Some people might find this more intuitive than running it in reverse.

    • If something goes wrong it may be difficult to track down where your error is coming in. You may need to check each specific function 1 by 1 to make sure they're behaving properly, because even 1 of them being slightly off will throw off your whole result. What I did was add output that said 'This passport failed due to byr' whenever a passport had an invalid birth year for example. And each other check I used had a similar output so I could always see why a given passport was getting rejected. Though I made sure to turn them off when it was time to run the real data set.

    • You should also test your individual checks in isolation. I was having trouble figuring out which one of my checks wasn't working correctly, so I took each function on it's own and looked at the examples of valid and invalid results. I put my equation and the value in a separate file and just walked through them until I found the one that wasn't giving me the right result.

    • You may want to use different methods for different checks. There are a number of different ways you can do each of them, but some are much easier using certain methods. For example, I used regex for hcl and pid, but just a simple comparison for byr. In the same way that you wouldn't eat a steak with a spoon, you can use different tools to make your life easier.

    • If you use regex, pay close attention to your patterns. They might not be doing exactly what you think they're doing. My biggest time sink ended up being that on of my regex patterns didn't have '^' and '$', but really needed them

    5 votes
  3. archevel
    (edited )
    Link
    I decided to skip python/numpy since this problem seems to be very cleanly solvable in awk Part 1 in awk /ecl/&&/pid/&&/eyr/&&/hcl/&&/byr/&&/iyr/&&/hgt/ { cnt++ } END { print cnt } To run the code...

    I decided to skip python/numpy since this problem seems to be very cleanly solvable in awk

    Part 1 in awk
    /ecl/&&/pid/&&/eyr/&&/hcl/&&/byr/&&/iyr/&&/hgt/ {
            cnt++
    }
    
    END {
            print cnt
    }
    

    To run the code awk -v RS="" -f seven.awk input7.txt (asuming the awk file is named seven.awk)

    I've run into some problem though with validation in step 2, so looking into that now.
    Managed to sort it out. While not as short as the solution to step 1 I think it is fairly ok

    Part 2 in awk I kind of like this solution since each rule is tightly encoded next to it's identifier. I had problems getting the end part of each pattern correct until I realized it needed to include both space, newlines and end of lines, i.e the "( |\n|$)".
    /eyr:20(2[0-9]|30)( |\n|$)/\
    && /pid:[0-9]{9}( |\n|$)/\
    && /ecl:(amb|blu|brn|gry|grn|hzl|oth)( |\n|$)/\
    && /byr:(19[2-9][0-9]|200[012])( |\n|$)/\
    && /hcl:#[0-9a-f]{6}( |\n|$)/\
    && /iyr:20(1[0-9]|20)( |\n|$)/\
    && /hgt:(1([5-8][0-9]|9[0-3])cm|(59|6[0-9]|7[0-6])in)( |\n|$)/ {
            cnt++
    }
    
    END {
            print cnt
    }
    

    To run the code awk -v RS="" -f eight.awk input7.txt (asuming the awk file is named eightawk)

    5 votes
  4. [2]
    Comment deleted by author
    Link
    1. wycy
      Link Parent
      I like this, I don't know why I didn't think about doing Option<String>. I used just String and checked whether it was empty. I'm going to have to steal this later and update my solution. So much...

      I like this, I don't know why I didn't think about doing Option<String>. I used just String and checked whether it was empty. I'm going to have to steal this later and update my solution. So much cleaner.

      3 votes
  5. Crespyl
    Link
    Ruby Part 1 #!/usr/bin/env ruby require "set" input = File.read(ARGV[0] || "test.txt") passports = input.split("\n\n") .map(&:strip) .map{|p| p.split(' ') .map{|f| f.split(':')} .inject({}){|p,...

    Ruby

    Part 1
    #!/usr/bin/env ruby
    require "set"
    
    input = File.read(ARGV[0] || "test.txt")
    
    passports = input.split("\n\n")
                  .map(&:strip)
                  .map{|p| p.split(' ')
                         .map{|f| f.split(':')}
                         .inject({}){|p, kv| p[kv[0].to_sym] = kv[1]; p}}
    
    REQUIRED = [:byr, :iyr, :eyr, :hgt, :hcl, :ecl, :pid].to_set
    OPTIONAL = [:cid].to_set
    
    puts "Part 1"
    valid_count = passports.reduce(0) do |count, passport|
      passport.keys.to_set.superset?(REQUIRED) ? count+1 : count
    end
    puts "Valid Passports: #{valid_count}"
    
    Part 2
    def is_valid?(passport)
      return false unless
        passport.keys.to_set.superset?(REQUIRED) &&
        passport[:byr].to_i.between?(1920, 2002) &&
        passport[:iyr].to_i.between?(2010, 2020) &&
        passport[:eyr].to_i.between?(2020, 2030) &&
        passport[:hcl].match?(/^#[0-9a-f]{6}$/) &&
        passport[:ecl].match?(/^amb|blu|brn|gry|grn|hzl|oth$/) &&
        passport[:pid].match?(/^\d{9}$/) &&
        hgt = passport[:hgt].match(/^(\d+)(cm|in)$/)
    
      n, u = hgt.captures
      u == "cm" ? n.to_i.between?(150, 193) : n.to_i.between?(50, 76)
    end
    
    puts "Part 2"
    valid_count = passports.reduce(0) do |count, passport|
      is_valid?(passport) ? count+1 : count
    end
    puts "Valid Passports: #{valid_count}"
    

    Lost a bunch of time before realizing that I was missing the parens around the first capture group in my height regex, oh well.

    I haven't been starting these right on time, and I've got enough actual work I'm behind on that I don't know how long I'll be able to keep up, but AoC is always so much fun. Here's hoping we get more virtual machine puzzles this year!

    5 votes
  6. blitz
    Link
    I ended up having to resort to unit tests to find my validation bugs. Thanks to @Bauke for introducing me to the anyhow crate, which was useful in in the validators. rust main.rs use std::io; use...

    I ended up having to resort to unit tests to find my validation bugs. Thanks to @Bauke for introducing me to the anyhow crate, which was useful in in the validators.

    rust

    main.rs
    use std::io;
    use std::io::prelude::*;
    
    use day4::{group_lines, Passport};
    
    fn main() {
        let stdin = io::stdin();
        let lines: Vec<String> = stdin.lock().lines().collect::<Result<_, _>>().unwrap();
    
        let grouped_lines = group_lines(&lines);
        let mut passports = Vec::new();
    
        for group in grouped_lines.iter() {
            passports.push(Passport::new(group));
        }
    
        let mut num_valid = 0;
    
        for p in passports.iter() {
            if p.is_valid() {
                num_valid += 1;
            }
        }
    
        println!("Part 2: {}", num_valid);
    }
    
    lib.rs
    #![feature(str_split_once)]
    use std::collections::HashMap;
    
    #[macro_use]
    extern crate lazy_static;
    
    use anyhow::{Result, bail};
    use regex::Regex;
    
    fn validate_int(input: &str, min_bound: usize, max_bound: usize) -> Result<()> {
        let x = input.parse::<usize>()?;
    
        if min_bound <= x && x <= max_bound {
            return Ok(());
        }
    
        bail!("Invalid x");
    }
    
    fn validate_byr(input: &str) -> Result<()> {
        validate_int(input, 1920, 2002)
    }
    
    fn validate_iyr(input: &str) -> Result<()> {
        validate_int(input, 2010, 2020)
    }
    
    fn validate_eyr(input: &str) -> Result<()> {
        validate_int(input, 2020, 2030)
    }
    
    fn validate_hgt(input: &str) -> Result<()> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"(\d+)(in|cm)").unwrap();
        }
    
        if let Some(caps) = RE.captures(input) {
            let height = &caps[1];
            let units = &caps[2];
    
            if units == "in" {
                return validate_int(&height, 59, 76);
            } else if units == "cm" {
                return validate_int(&height, 150, 193);
            } else {
                bail!("Invalid height unit");
            }
        }
        bail!("Invalid height");
    }
    
    fn validate_hcl(input: &str) -> Result<()> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"#[0-9a-f]{6}").unwrap();
        }
    
        if RE.is_match(input) {
            return Ok(());
        } else {
            bail!("Invalid hcl");
        }
    }
    
    fn validate_ecl(input: &str) -> Result<()> {
        let valid_colors = vec!["amb", "blu", "brn", "gry", "grn", "hzl", "oth"];
    
        for c in valid_colors.iter() {
            if c == &input {
                return Ok(());
            }
        }
    
        bail!("Invalid ecl");
    }
    
    fn validate_pid(input: &str) -> Result<()> {
        lazy_static! {
            static ref RE: Regex = Regex::new(r"^\d{9}$").unwrap();
        }
    
        if RE.is_match(input) {
            return Ok(());
        } else {
            bail!("Invalid pid");
        }
    }
    
    fn validate_cid(_input: &str) -> Result<()> {
        Ok(())
    }
    
    #[derive (Debug)]
    pub struct Passport(HashMap<String, String>);
    
    impl Passport {
        pub fn new(grouped_lines: &Vec<String>) -> Passport {
            let mut entries = HashMap::new();
    
            for line in grouped_lines.iter() {
                for word_group in line.split_whitespace() {
                    if let Some((key, value)) = word_group.split_once(':') {
                        let validation_function = match key {
                            "byr" => validate_byr,
                            "iyr" => validate_iyr,
                            "eyr" => validate_eyr,
                            "hgt" => validate_hgt,
                            "hcl" => validate_hcl,
                            "ecl" => validate_ecl,
                            "pid" => validate_pid,
                            "cid" => validate_cid,
                            _ => {
                                panic!("Invalid passport format")
                            }
                        };
    
                        if let Ok(_) = validation_function(value) {
                            entries.insert(key.to_string(), value.to_string());
                        }
                    } else {
                        panic!["Unable to split on passport entry."];
                    }
                }
            }
    
            Passport( entries )
        }
    
        pub fn is_valid(&self) -> bool {
            let Passport( hm ) = self;
    
            let required_fields = vec![
                "byr", "iyr", "eyr", "hgt", "hcl",
                "ecl", "pid"
            ];
    
            for field in required_fields.iter() {
                if !hm.contains_key(*field) {
                    return false;
                }
            }
    
            true
        }
    }
    
    pub fn group_lines(raw_lines: &Vec<String>) -> Vec<Vec<String>> {
        let mut all_groups = Vec::new();
    
        let mut group = Vec::new();
    
        for line in raw_lines.iter() {
            if line.len() > 0 {
                group.push(line.clone());
            } else {
                all_groups.push(group);
                group = Vec::new();
            }
        }
    
        if group.len() > 0 {
            all_groups.push(group);
        }
    
        all_groups
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_validate_hgt_valids() {
            let valids = vec!["74in", "164cm", "158cm", "150cm", "193cm"];
    
            for v in valids.iter() {
                assert!(validate_hgt(v).is_ok());
            }
        }
    
        #[test]
        fn test_validate_hgt_invalids() {
            let invalids = vec!["170", "200cm", "20in"];
    
            for i in invalids.iter() {
                assert!(validate_hgt(i).is_err());
            }
        }
    
        #[test]
        fn test_validate_hcl_valids() {
            assert!(validate_hcl("#123abc").is_ok());
        }
    
        #[test]
        fn test_validate_hcl_invalids() {
            let invalids = vec!["#123abz", "123abc"];
    
            for i in invalids.iter() {
                assert!(validate_hcl(i).is_err());
            }
        }
    
        #[test]
        fn test_validate_pid_valids() {
            assert!(validate_pid("000000001").is_ok())
        }
    
        #[test]
        fn test_validate_pid_invalids() {
            assert!(validate_pid("0123456789").is_err())
        }
    }
    
    5 votes
  7. tomf
    (edited )
    Link
    Sheets again! Now, I understand that these look unruly and super ugly... Basically, I'm splitting everything off and pairing the source row number with it. From there I am pulling a list of all of...

    Sheets again! Now, I understand that these look unruly and super ugly...

    Basically, I'm splitting everything off and pairing the source row number with it. From there I am pulling a list of all of the blank rows within the dataset. Using a VLOOKUP with TRUE I can group everything properly. Blank rows are delimiters, essentially.

    From there I just clean it up and run some bland REGEX. Not pretty, but it works... much to my surprise. :)

    link to sheet

    Part 1
    =ARRAYFORMULA(
      QUERY(
       QUERY(
        QUERY(
         SPLIT(
          FLATTEN(
           TRANSPOSE(
            VLOOKUP(
             ROW(A1:A),
             QUERY(
              IF(ISBLANK(
               INDIRECT(
                "A1:A"&
                MAX(
                 FILTER(ROW(A1:A),A1:A<>"")))),
               ROW(A1:A),),
              "select * 
               where Col1 is not null"),
             1,TRUE)&
            "|"&
            IFERROR(
             SPLIT(A1:A," ")))),
          "|"),
         "select * 
          where 
           Col2 is not null and 
           Col2 matches '(byr|iyr|eyr|hgt|hcl|ecl|pid).*'"),
        "select Col1, Count(Col1) group by Col1"),
       "select Count(Col1) where Col2 = 7 label Count(Col1) 'Part 1'"))
    
    
    Part 2
    =ARRAYFORMULA(
      QUERY(
       QUERY(
        SPLIT(
         FLATTEN(
          TRANSPOSE(
           VLOOKUP(
            ROW(A1:A),
            QUERY(
             IF(ISBLANK(
              INDIRECT("A1:A"&
               MAX(FILTER(ROW(A1:A),A1:A<>"")))),
             ROW(A1:A),),
             "select * where Col1 is not null"),1,TRUE)&"|"&
           IFERROR(
            SPLIT(A1:A," ")))),
         "|"),
        "select Col1, Count(Col1) 
         where 
          Col2 matches 'byr:(19[2-9][0-9]|200[0-2])|iyr:(20(1[0-9]|20))|eyr:(20(2[0-9]|30))|hgt:(1([5-8][0-9]|9[0-3])cm|(59|6[0-9]|7[0-6])in)|hcl:#[0-9a-fA-F]{6}|ecl:(amb|blu|brn|gry|grn|hzl|oth)|pid:[0-9]{9}'
          group by Col1"),
        "select Count(Col1) where Col2 = 7
         label Count(Col1) 'Part 2'"))
    
    

    quick edit: the average height in mine is 7'2"

    4 votes
  8. [3]
    3d12
    (edited )
    Link
    Well, I'm finished with part 1, and ready to ask for some help on part 2. I've done a deep dive, and still can't come up with the part that's mismatched. I know it's not my input set, because my...

    Well, I'm finished with part 1, and ready to ask for some help on part 2. I've done a deep dive, and still can't come up with the part that's mismatched. I know it's not my input set, because my answer for part 1 was correct, and I currently come up with 128, which it says is "too high." So I filtered my output array to only show the "valid" matches and checked them visually, all of them look correct! So unless I'm missing something glaring, I think maybe my answer is bugged? Please, I don't want an answer outright, just some validation that I'm not insane. Repo (including code for part 2 with debug still included) is here

    Part 1
    const fs = require('fs');
    const readline = require('readline');
    
    let passportArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		passportArray.push(line);
    	}
    }
    
    function parsePassport(text) {
    	let byr = "",iyr = "",eyr = "",hgt = "",hcl = "",ecl = "",pid = "",cid = "";
    	let singleLineText = text.replace(/\n/g,' ');
    	// check for all instances of k/v pairs in the single-line version of the text
    	const regex = /(\b\w+):([\w|\d|#]+\b)/g;
    	const found = singleLineText.match(regex);
    	// for each k/v pair, save to the correct variable by matching the key
    	for (const kvpair of found) {
    		const regex2 = /(\b\w+):([\w|\d|#]+\b)/;
    		const found2 = kvpair.match(regex2);
    		switch (found2[1]) {
    			case 'byr': byr = found2[2];
    					break;
    			case 'iyr': iyr = found2[2];
    					break;
    			case 'eyr': eyr = found2[2];
    					break;
    			case 'hgt': hgt = found2[2];
    					break;
    			case 'hcl': hcl = found2[2];
    					break;
    			case 'ecl': ecl = found2[2];
    					break;
    			case 'pid': pid = found2[2];
    					break;
    			case 'cid': cid = found2[2];
    					break;
    		}
    	}
    	return {
    		byr: byr,
    		iyr: iyr,
    		eyr: eyr,
    		hgt: hgt,
    		hcl: hcl,
    		ecl: ecl,
    		pid: pid,
    		cid: cid
    	}
    }
    
    function splitPassports() {
    	let splitArray = [];
    	let currentPassport = "";
    	for (const line of passportArray) {
    		// if we find a blank line, reset our accumulated line and send for parsing
    		if (line.trim().length == 0) {
    			splitArray.push(parsePassport(currentPassport));
    			currentPassport = "";
    		} else {
    			currentPassport += ' ' + line;
    		}
    	}
    	// flushing the buffer, in case the file is not newline-terminated
    	if (currentPassport != "") {
    		splitArray.push(parsePassport(currentPassport));
    	}
    	return splitArray;
    }
    
    function validatePassport(passport) {
    	// check that required fields are non-blank
    	return (passport.byr != "" &&
    		passport.iyr != "" &&
    		passport.eyr != "" &&
    		passport.hgt != "" &&
    		passport.hcl != "" &&
    		passport.ecl != "" &&
    		passport.pid != ""); //&&
    		//passport.cid != "");
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	let parsedArray = splitPassports();
    	let validPassports = 0;
    	for (const port of parsedArray) {
    		if (validatePassport(port)) validPassports++;
    	}
    	console.log("Valid passports: " + validPassports);
    })();
    
    Part 2 (thanks to @spit-evil-olive-tips for the hint!)
    const fs = require('fs');
    const readline = require('readline');
    
    let passportArray = []
    
    async function openFileForReading(file) {
    	const fileStream = fs.createReadStream(file);
    
    	const rl = readline.createInterface({
    		input: fileStream,
    		crlfDelay: Infinity
    	});
    
    	for await (const line of rl) {
    		passportArray.push(line);
    	}
    }
    
    function parsePassport(text) {
    	let byr = "",iyr = "",eyr = "",hgt = "",hcl = "",ecl = "",pid = "",cid = "";
    	let singleLineText = text.replace(/\n/g,' ');
    	// check for all instances of k/v pairs in the single-line version of the text
    	const regex = /(\b\w+):([\w|\d|#]+\b)/g;
    	const found = singleLineText.match(regex);
    	// for each k/v pair, save to the correct variable by matching the key
    	for (const kvpair of found) {
    		const regex2 = /(\b\w+):([\w|\d|#]+\b)/;
    		const found2 = kvpair.match(regex2);
    		switch (found2[1]) {
    			case 'byr': byr = found2[2];
    					break;
    			case 'iyr': iyr = found2[2];
    					break;
    			case 'eyr': eyr = found2[2];
    					break;
    			case 'hgt': hgt = found2[2];
    					break;
    			case 'hcl': hcl = found2[2];
    					break;
    			case 'ecl': ecl = found2[2];
    					break;
    			case 'pid': pid = found2[2];
    					break;
    			case 'cid': cid = found2[2];
    					break;
    		}
    	}
    	return {
    		byr: byr,
    		iyr: iyr,
    		eyr: eyr,
    		hgt: hgt,
    		hcl: hcl,
    		ecl: ecl,
    		pid: pid,
    		cid: cid
    	}
    }
    
    function splitPassports() {
    	let splitArray = [];
    	let currentPassport = "";
    	for (const line of passportArray) {
    		// if we find a blank line, reset our accumulated line and send for parsing
    		if (line.trim().length == 0) {
    			splitArray.push(parsePassport(currentPassport));
    			currentPassport = "";
    		} else {
    			currentPassport += ' ' + line;
    		}
    	}
    	// flushing the buffer, in case the file is not newline-terminated
    	if (currentPassport != "") {
    		splitArray.push(parsePassport(currentPassport));
    	}
    	return splitArray;
    }
    
    function validatePassport(passport) {
    	// check that required fields are non-blank
    	if (passport.byr == "" ||
    		passport.iyr == "" ||
    		passport.eyr == "" ||
    		passport.hgt == "" ||
    		passport.hcl == "" ||
    		passport.ecl == "" ||
    		passport.pid == "") {
    		return false;
    	}
    	// byr, iyr, and eyr should be numbers
    	let byrnum = 0,iyrnum = 0,eyrnum = 0;
    	try {
    		byrnum = parseInt(passport.byr);
    		iyrnum = parseInt(passport.iyr);
    		eyrnum = parseInt(passport.eyr);
    	} catch(e) {
    		return false;
    	}
    	// byr four digits, between 1920 and 2002
    	if (passport.byr.length != 4 || byrnum < 1920 || byrnum > 2002) {
    		return false;
    	}
    	// iyr four digits, between 2010 and 2020
    	if (passport.iyr.length != 4 || iyrnum < 2010 || iyrnum > 2020) {
    		return false;
    	}
    	// eyr four digits, between 2020 and 2030
    	if (passport.eyr.length != 4 || eyrnum < 2020 || eyrnum > 2030) {
    		return false;
    	}
    	// hgt, number followed by cm or in
    	let hgtnum = 0,hgtunits = "";
    	try {
    		const regex = /(\d+)(cm|in)/;
    		const found = passport.hgt.match(regex);
    		hgtnum = parseInt(found[1]);
    		hgtunits = found[2];
    	} catch(e) {
    		return false;
    	}
    	if (hgtunits != 'cm' && hgtunits != 'in' ) {
    		return false;
    	}
    	// -- if cm, must be between 150 and 193
    	if (hgtunits == 'cm' && (hgtnum < 150 || hgtnum > 193)) {
    		return false;
    	}
    	// -- if in, must be between 59 and 76
    	if (hgtunits == 'in' && (hgtnum < 59 || hgtnum > 76)) {
    		return false;
    	}
    	// hcl a # followed by exactly six chars 0-9 or a-f
    	let hclnum = 0;
    	try {
    		const regex = /(#)([0-9a-f]{6})/;
    		const found = passport.hcl.match(regex);
    		hclnum = found[2];
    	} catch(e) {
    		return false;
    	}
    	// ecl exactly one of: amb blu brn gry grn hzl oth
    	switch (passport.ecl) {
    		case 'amb':
    		case 'blu':
    		case 'brn':
    		case 'gry':
    		case 'grn':
    		case 'hzl':
    		case 'oth':
    				break;
    		default:
    			return false;
    	}
    	// pid nine-digit number, incl leading zeroes
    	let pidnum = 0;
    	try {
    		const regex = /^(\d{9})$/;
    		const found = passport.pid.match(regex);
    		pidnum = found[1];
    	} catch(e) {
    		return false;
    	}
    	// cid, ignored
    	return true;
    }
    
    (async function mainExecution() {
    	await openFileForReading('input.txt');
    	let parsedArray = splitPassports();
    	let validPassports = 0;
    	for (const port of parsedArray) {
    		if (validatePassport(port)) validPassports++;
    	}
    	console.log("Valid passports: " + validPassports);
    })();
    
    4 votes
    1. [3]
      Comment deleted by author
      Link Parent
      1. [2]
        3d12
        Link Parent
        It sounds so obvious when you say it like that... :) Thanks very much! Especially frustrating that this slight change (adding the ^ and % markers to that regex) only removed 1 invalid entry... So...

        It sounds so obvious when you say it like that... :) Thanks very much!

        Especially frustrating that this slight change (adding the ^ and % markers to that regex) only removed 1 invalid entry... So my real answer was 127, lol

        3 votes
        1. Crespyl
          Link Parent
          I had a similar issue, my height regex was missing a capture group and as a result I wasn't actually rejecting two invalid entries. Took a lot of staring to find those two missing parens.

          I had a similar issue, my height regex was missing a capture group and as a result I wasn't actually rejecting two invalid entries. Took a lot of staring to find those two missing parens.

          4 votes
  9. clone1
    (edited )
    Link
    I "solved" part one with python in about 10 minutes... Then spent 30 minutes debugging to find that I was dropping the last passport during parsing... Writing my scheme solution was cathartic. I...

    I "solved" part one with python in about 10 minutes... Then spent 30 minutes debugging to find that I was dropping the last passport during parsing... Writing my scheme solution was cathartic. I really need to learn scheme regex because some of my validations (hgt) are pretty messy.

    Part 1 & 2
    (define (make-field key val)
      (cons key val))
    
    (define (make-field-from-str field-str)
      (let ((split-str ((string-splitter 'delimiter #\:) field-str)))
        (make-field (string->symbol (car split-str))
                    (cadr split-str))))
    
    (define get-key car)
    (define get-val cdr)
    
    (define (get-passports)
      (define (add-passport passports lines)
        (if (= (length lines) 0) passports
            (cons (map make-field-from-str
                       (append-map (string-splitter) lines))
                  passports)))
      (with-input-from-file "input"
        (lambda ()
          (let loop ((line (read-line))
                     (passport-lines '())
                     (passports '()))
            (cond ((eof-object? line)
                   (reverse (add-passport passports passport-lines)))
                  ((= (string-length line) 0)
                   (loop (read-line) '() (add-passport passports passport-lines)))
                  (else (loop (read-line)
                              (cons line passport-lines)
                              passports)))))))
    
    (define (has-field? passport key)
      (member key (map get-key passport)))
    
    (define (has-all-fields? passport)
      (let ((required '(byr ecl eyr hcl hgt iyr pid)))
        (equal? (sort (remove (lambda (key) (eq? key 'cid))
                              (map get-key passport))
                      symbol<?)
                required)))
    
    (define (field-valid? field)
      (define (string-between? min max str)
        (let ((num (string->number str)))
          (and num (<= min num max))))
      (let ((key (get-key field))
            (val (get-val field)))
        (cond ((eq? key 'byr) (string-between? 1920 2002 val))
              ((eq? key 'iyr) (string-between? 2010 2020 val))
              ((eq? key 'eyr) (string-between? 2020 2030 val))
              ;; Probably should learn how regex works in scheme
              ((eq? key 'hgt) (let ((split-i (string-find-first-index
                                              char-alphabetic? val)))
                                (if (not split-i) #f
                                    (let ((num (substring val 0 split-i))
                                          (unit (substring val split-i)))
                                      (cond ((string=? unit "cm")
                                             (string-between? 150 193 num))
                                            ((string=? unit "in")
                                             (string-between? 59 76 num))
                                            (else #f))))))
              ((eq? key 'hcl) (and (= (string-length val) 7)
                                   (eq? (string-ref val 0) #\#)
                                   (string-every (lambda (c) (or (char-numeric? c)
                                                            (char-set-member?
                                                             (string->char-set "abcdef")
                                                             c)))
                                                 (substring val 1))))
              ((eq? key 'ecl) (member val '("amb" "blu" "brn" "gry" "grn" "hzl" "oth")))
              ((eq? key 'pid) (and (string->number val)
                                   (= (string-length val) 9)))
              ((eq? key 'cid) #t)
              (else (display "Unknown field: ") (display key) (newline) #t))))
    
    (define (all-fields-valid? passport)
      (every field-valid? passport))
    
    (define (one lines)
      (count has-all-fields? lines))
    
    (define (two lines)
      (count (lambda (passport)
               (and (has-all-fields? passport)
                    (all-fields-valid? passport)))
             lines))
    
    (define (run lines)
      (display "One: ")
      (display (one lines))
      (newline)
      (display "Two: ")
      (display (two lines))
      (newline))
    
    (run (get-passports))
    

    Update:
    Scheme regex are weird. I think it's worse

    Regex changes
    (define (try-match-exact pattern string)
      (regsexp-match-string (compile-regsexp (append '(seq (line-start))
                                                     pattern
                                                     '((line-end))))
                            string))
    
    (define (get-group matches group)
      (cdr (find (lambda (x) (and (pair? x) (eq? (car x) group)))
                 matches)))
    
    (define (field-valid? field)
      (define (string-between? min max str)
        (let ((num (string->number str)))
          (and num (<= min num max))))
      (let ((key (get-key field))
            (val (get-val field)))
        (cond ((eq? key 'hgt) (let ((matches
                                     (try-match-exact
                                      '((group num (* (char-in numeric)))
                                        (group unit (* (char-in alphabetic))))
                                      val)))
                                (if (not matches) #f
                                    (let ((num (get-group matches 'num))
                                          (unit (get-group matches 'unit)))
                                      (cond ((string=? unit "cm")
                                             (string-between? 150 193 num))
                                            ((string=? unit "in")
                                             (string-between? 59 76 num))
                                            (else #f))))))
              ((eq? key 'hcl) (try-match-exact '(#\# (** 6 (char-in numeric "abcdef")))
                                               val))
              ((eq? key 'pid) (try-match-exact '((** 9 (char-in numeric)))
                                               val))
              (else (display "Unknown field: ") (display key) (newline) #t))))
    
    4 votes
  10. teaearlgraycold
    Link
    I double pasted my input (why does middle click also paste?!?) and couldn't figure out what was wrong for quite a long time.

    I double pasted my input (why does middle click also paste?!?) and couldn't figure out what was wrong for quite a long time.

    3 votes
  11. andre
    Link
    I didn't start this one at release time, but it wouldn't have mattered - had a really annoying mistake that took a while to track down. Reasonably happy with the outcome though. JavaScript Parts 1...

    I didn't start this one at release time, but it wouldn't have mattered - had a really annoying mistake that took a while to track down. Reasonably happy with the outcome though.

    JavaScript

    Parts 1 and 2
    function isValid(p) {
      if (!p) return false
    
      if (p.byr < 1920 || p.byr > 2002) return false
      if (p.iyr < 2010 || p.iyr > 2020) return false
      if (p.eyr < 2020 || p.eyr > 2030) return false
    
      let [match, hgt, unit] = /(\d+)(cm|in)?/.exec(p.hgt)
      if (
        !match ||
        !unit ||
        (unit == 'cm' && (hgt < 150 || hgt > 193)) ||
        (unit == 'in' && (hgt < 59 || hgt > 76))
      ) {
        return false
      }
    
      if (!/^#[0-9a-f]{6}$/.test(p.hcl)) return false
      if (!/(amb|blu|brn|gry|grn|hzl|oth)/.test(p.ecl)) return false
      if (!/^\d{9}$/.test(p.pid)) return false
    
      return true
    }
    
    function Passport(line) {
      const requiredFields = ['byr', 'iyr', 'eyr', 'hgt', 'hcl', 'ecl', 'pid']
    
      const passport = line.split(' ').reduce((acc, field) => {
        const [key, value] = field.split(':')
    
        const idx = requiredFields.indexOf(key)
        if (idx > -1) requiredFields.splice(idx, 1)
    
        acc[key] = value
        return acc
      }, {})
    
      if (requiredFields.length) return null
      return passport
    }
    
    export function solvePart1(input) {
      input = input.split('\n\n').map(l => l.replace(/\n/g, ' '))
      return input.filter(Passport).length
    }
    
    export function solvePart2(input) {
      input = input.split('\n\n').map(l => l.replace(/\n/g, ' '))
      return input.map(Passport).filter(isValid).length
    }
    
    3 votes
  12. nothis
    Link
    Fucking regex hell. I'm sure there's some elegant way of doing part 2 but it didn't seem reasonable to generalize this into something nicer. I assume this thread's current top post is the correct...

    Fucking regex hell. I'm sure there's some elegant way of doing part 2 but it didn't seem reasonable to generalize this into something nicer. I assume this thread's current top post is the correct answer and it still gives me headaches.

    I guess the challenge here was keeping calm and careful, otherwise I don't quite get why we had to validate like 7 fields, just different enough to be annoying. I didn't really enjoy today's.

    Part 1+2
    import re
    
    with open("04/input.txt") as inputFile:
        passportList = inputFile.read().replace(" ", "\n").split("\n\n")
    
    
    def validBYR(passport):
        byr = re.findall(r'byr\:(\d+)', passport)
        if byr:
            byr = int(byr[0])
            if byr >= 1920 and byr <= 2020:
                return True
        return False
    
    
    def validIYR(passport):
        iyr = re.findall(r'iyr\:(\d+)', passport)
        if iyr:
            iyr = int(iyr[0])
            if iyr >= 2010 and iyr <= 2020:
                return True
        return False
    
    
    def validEYR(passport):
        eyr = re.findall(r'eyr\:(\d+)', passport)
        if eyr:
            eyr = int(eyr[0])
            if eyr >= 2020 and eyr <= 2030:
                return True
        return False
    
    
    def validHGT(passport):
        hgt = re.findall(r'hgt\:(\d+)(cm|in)', passport)
        if hgt:
            height = int(hgt[0][0])
            unit = hgt[0][1]
            if unit == "cm" and height >= 150 and height <= 193:
                return True
            elif unit == "in" and height >= 59 and height <= 76:
                return True
        return False
    
    
    def validHCL(passport):
        return bool(re.search(r'hcl\:\#[a-f|\d]{6}(\n|$)', passport))
    
    
    def validECL(passport):
        return bool(re.search(r'ecl\:(amb|blu|brn|gry|grn|hzl|oth)', passport))
    
    
    def validPID(passport):
        return bool(re.search(r'pid\:\d{9}(\n|$)', passport))
    
    
    def validData(passport):
        return validBYR(passport) and validIYR(passport) and validEYR(passport) and \
            validHGT(passport) and validHCL(passport) and validECL(
                passport) and validPID(passport)
    
    
    def validFields(passport):
        for string in ["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"]:
            if not string in passport:
                return False
        return True
    
    
    validFieldsCount = 0
    validDataCount = 0
    for passport in passportList:
    
        if validFields(passport):
            validFieldsCount += 1
    
        if(validData(passport)):
            validDataCount += 1
    
    print("Passports with valid field names: ", validFieldsCount)
    print("Passports with valid field data: ", validDataCount)
    
    3 votes
  13. markh
    Link
    Elixir, Day 4 Parts 1 and 2 defmodule Day4 do def one do File.read!("inputs/four.txt") |> String.split("\n\n") |> Enum.map(&parse/1) |> Enum.count(&valid_keys?/1) end def two do...

    Elixir, Day 4

    Parts 1 and 2
    defmodule Day4 do
      def one do
        File.read!("inputs/four.txt")
        |> String.split("\n\n")
        |> Enum.map(&parse/1)
        |> Enum.count(&valid_keys?/1)
      end
    
      def two do
        File.read!("inputs/four.txt")
        |> String.split("\n\n")
        |> Enum.map(&parse/1)
        |> Enum.reject(fn x -> !(valid_keys?(x)) end)
        |> Enum.count(&valid_values?/1)
      end
    
      def parse(input) do
        input
        |> String.split(~r/\s+/, trim: true)
        |> Enum.map(&(String.split(&1, ":")))
        |> Enum.map(fn [k, v] -> {String.to_atom(k), v} end)
        |> Map.new()
      end
    
      def valid_keys?(data) do
        Enum.all?([:byr, :iyr, :eyr, :hgt, :hcl, :ecl, :pid], fn key -> Map.has_key?(data, key) end)
      end
    
      def valid_values?(data) do
        Enum.all?(data, fn {key, value} -> valid_value?(key, value) end)
      end
    
    
      def valid_value?(:hgt, value) do
        case Regex.scan(~r/(\d+)(\w+)/, value, capture: :all_but_first) do
          [[val, "cm"]] ->
            String.to_integer(val) in 150..193
          [[val, "in"]] ->
            String.to_integer(val) in 59..76
          _ ->
            false
        end
      end
    
      def valid_value?(:byr, value), do: String.to_integer(value) in 1920..2002
      def valid_value?(:iyr, value), do: String.to_integer(value) in 2010..2020
      def valid_value?(:eyr, value), do: String.to_integer(value) in 2020..2030
      def valid_value?(:hcl, value), do: Regex.match?(~r/#[0-9a-f]{6}/, value)
      def valid_value?(:ecl, value), do: value in ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]
      def valid_value?(:pid, value), do: Regex.match?(~r/^(\d){9}$/, value)
      def valid_value?(_, _), do: true
    end
    
    3 votes
  14. wycy
    (edited )
    Link
    Rust Updated solution to incorporate cleaner input reading plus some things I liked from Bauke's (Optionals) and spit-evil-olive-tips' solutions (split on \n\n). Rust (updated solution) use...

    Rust

    Updated solution to incorporate cleaner input reading plus some things I liked from Bauke's (Optionals) and spit-evil-olive-tips' solutions (split on \n\n).

    Rust (updated solution)
    use std::env;
    use std::io;
    
    extern crate regex;
    use regex::Regex;
    
    #[derive(Default)]
    struct Passport {
        byr: Option<String>,
        iyr: Option<String>,
        eyr: Option<String>,
        hgt: Option<String>,
        hcl: Option<String>,
        ecl: Option<String>,
        pid: Option<String>,
        cid: Option<String>,
    }
    impl Passport {
        pub fn is_valid_part1(&self) -> bool {
            self.byr.is_some() &&
            self.iyr.is_some() &&
            self.eyr.is_some() &&
            self.hgt.is_some() &&
            self.hcl.is_some() &&
            self.ecl.is_some() &&
            self.pid.is_some()
        }
        pub fn is_valid_part2(&self) -> bool {
            self.is_valid_part1() &&
            self.byr_valid() &&
            self.iyr_valid() &&
            self.eyr_valid() &&
            self.hgt_valid() &&
            self.hcl_valid() &&
            self.ecl_valid() &&
            self.pid_valid()
        }
        pub fn byr_valid(&self) -> bool {
            let byr: i64 = self.byr.as_ref().unwrap().parse().unwrap_or(-1);
            byr >= 1920 && byr <= 2002
        }
        pub fn iyr_valid(&self) -> bool {
            let iyr: i64 = self.iyr.as_ref().unwrap().parse().unwrap_or(-1);
            iyr >= 2010 && iyr <= 2020
        }
        pub fn eyr_valid(&self) -> bool {
            let eyr: i64 = self.eyr.as_ref().unwrap().parse().unwrap_or(-1);
            eyr >= 2020 && eyr <= 2030
        }
        pub fn hgt_valid(&self) -> bool {
            let re = Regex::new(r"^(\d+)(\w+)$").unwrap();
            let matches = re.captures(&self.hgt.as_ref().unwrap()).unwrap();
            let hgt_num: i64     = matches[1].parse().unwrap();
            let hgt_unit: String = matches[2].parse().unwrap();
            match hgt_unit.as_str() {
                "cm" => hgt_num >= 150 && hgt_num <= 193,
                "in" => hgt_num >= 59 && hgt_num <= 76,
                _ =>    false,
            }
        }
        pub fn hcl_valid(&self) -> bool {
            let re = Regex::new(r"^\#[a-f0-9]{6}$").unwrap();
            re.is_match(&self.hcl.as_ref().unwrap())
        }
        pub fn ecl_valid(&self) -> bool {
            let re = Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$").unwrap();
            re.is_match(&self.ecl.as_ref().unwrap())
        }
        pub fn pid_valid(&self) -> bool {
            let re = Regex::new(r"^\d{9}$").unwrap();
            re.is_match(&self.pid.as_ref().unwrap())
        }
    }
    
    fn day04(input: &str) -> io::Result<()> {
        let passports = std::fs::read_to_string(input)
            .unwrap()
            .split("\n\n")
            .map(|vline| {
                let mut passport = Passport::default();
                for part in vline.split_ascii_whitespace() {
                    let mut data = part.split(":");
                    match data.next().unwrap() {
                        "byr" => passport.byr = Some(data.next().unwrap().to_string()),
                        "iyr" => passport.iyr = Some(data.next().unwrap().to_string()),
                        "eyr" => passport.eyr = Some(data.next().unwrap().to_string()),
                        "hgt" => passport.hgt = Some(data.next().unwrap().to_string()),
                        "hcl" => passport.hcl = Some(data.next().unwrap().to_string()),
                        "ecl" => passport.ecl = Some(data.next().unwrap().to_string()),
                        "pid" => passport.pid = Some(data.next().unwrap().to_string()),
                        "cid" => passport.cid = Some(data.next().unwrap().to_string()),
                        other  => panic!("Unknown data type: {}", other),
                    }
                }
                passport
            })
            .collect::<Vec<Passport>>();
    
        // Part 1
        let part1 = passports.iter().filter(|x| x.is_valid_part1()).count();
        println!("Part 1: {}", part1); // 233
    
        // Part 2
        let part2 = passports.iter().filter(|x| x.is_valid_part2()).count();
        println!("Part 2: {}", part2); // 111
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        day04(&filename).unwrap();
    }
    

    By the way, would someone better than me at Rust know a cleaner way to do this? Was there a way to do this without clone()ing line to check it twice? Sometimes I feel like I understand the borrow checker but with strings things get messy.

        for line in reader.lines() {
            let line = line.unwrap().clone();
            if &line.len() == &0_usize { passports.push(Passport::new()); continue; }
            let ix = passports.len()-1;
            for part in line.split_ascii_whitespace() {
    
    Rust (original solution)
    use std::env;
    use std::io::{self, prelude::*, BufReader};
    use std::fs::File;
    
    extern crate regex;
    use regex::Regex;
    
    struct Passport {
        byr: String,
        iyr: String,
        eyr: String,
        hgt: String,
        hcl: String,
        ecl: String,
        pid: String,
        cid: String,
    }
    impl Passport {
        pub fn new() -> Passport {
            Passport {
                byr: "".to_string(),
                iyr: "".to_string(),
                eyr: "".to_string(),
                hgt: "".to_string(),
                hcl: "".to_string(),
                ecl: "".to_string(),
                pid: "".to_string(),
                cid: "".to_string(),
            }
        }
        pub fn is_valid_part1(&self) -> bool {
            self.byr != "" &&
            self.iyr != "" &&
            self.eyr != "" &&
            self.hgt != "" &&
            self.hcl != "" &&
            self.ecl != "" &&
            self.pid != ""
        }
        pub fn is_valid_part2(&self) -> bool {
            self.is_valid_part1() &&
            self.byr_valid() &&
            self.iyr_valid() &&
            self.eyr_valid() &&
            self.hgt_valid() &&
            self.hcl_valid() &&
            self.ecl_valid() &&
            self.pid_valid()
        }
        pub fn byr_valid(&self) -> bool {
            let byr: i64 = self.byr.parse().unwrap_or(-1);
            byr >= 1920 && byr <= 2002
        }
        pub fn iyr_valid(&self) -> bool {
            let iyr: i64 = self.iyr.parse().unwrap_or(-1);
            iyr >= 2010 && iyr <= 2020
        }
        pub fn eyr_valid(&self) -> bool {
            let eyr: i64 = self.eyr.parse().unwrap_or(-1);
            eyr >= 2020 && eyr <= 2030
        }
        pub fn hgt_valid(&self) -> bool {
            let re = Regex::new(r"^(\d+)(\w+)$").unwrap();
            let matches = re.captures(&self.hgt).unwrap();
            let hgt_num: i64     = matches[1].parse().unwrap();
            let hgt_unit: String = matches[2].parse().unwrap();
            match hgt_unit.as_str() {
                "cm" => { return hgt_num >= 150 && hgt_num <= 193; },
                "in" => { return hgt_num >= 59 && hgt_num <= 76; },
                _ =>    { return false; },
            }
        }
        pub fn hcl_valid(&self) -> bool {
            let re = Regex::new(r"^\#[a-f0-9]{6}$").unwrap();
            re.is_match(&self.hcl)
        }
        pub fn ecl_valid(&self) -> bool {
            let re = Regex::new(r"^(amb|blu|brn|gry|grn|hzl|oth)$").unwrap();
            re.is_match(&self.ecl)
        }
        pub fn pid_valid(&self) -> bool {
            let re = Regex::new(r"^(\d){9}$").unwrap();
            re.is_match(&self.pid)
        }
    }
    
    fn day04(input: &str) -> io::Result<()> {
        let file = File::open(input).expect("Input file not found.");
        let reader = BufReader::new(file);
    
        let mut passports: Vec<Passport> = Vec::new();
        passports.push(Passport::new());
        for line in reader.lines() {
            let line = line.unwrap().clone();
            if &line.len() == &0_usize { passports.push(Passport::new()); continue; }
            let ix = passports.len()-1;
            for part in line.split_ascii_whitespace() {
                let mut data = part.split(":");
                match data.next().unwrap() {
                    "byr" => passports[ix].byr = data.next().unwrap().to_string(),
                    "iyr" => passports[ix].iyr = data.next().unwrap().to_string(),
                    "eyr" => passports[ix].eyr = data.next().unwrap().to_string(),
                    "hgt" => passports[ix].hgt = data.next().unwrap().to_string(),
                    "hcl" => passports[ix].hcl = data.next().unwrap().to_string(),
                    "ecl" => passports[ix].ecl = data.next().unwrap().to_string(),
                    "pid" => passports[ix].pid = data.next().unwrap().to_string(),
                    "cid" => passports[ix].cid = data.next().unwrap().to_string(),
                    _  => passports.push(Passport::new()),
                }
            }
        }
    
        // Part 1
        let part1 = passports.iter().filter(|x| x.is_valid_part1()).count();
        println!("Part 1: {}", part1); // 233
    
        // Part 2
        let part2 = passports.iter().filter(|x| x.is_valid_part2()).count();
        println!("Part 2: {}", part2); // 111
    
        Ok(())
    }
    
    fn main() {
        let args: Vec<String> = env::args().collect();
        let filename = &args[1];
        day04(&filename).unwrap();
    }
    
    2 votes
  15. Gyrfalcon
    Link
    Language: Julia Repository Personal Challenge: No loops, recursion, map, and vectorization okay. More on this after the code It's not pretty, but it works, and despite the gymnastics to avoid...
    • Language: Julia
    • Repository
    • Personal Challenge: No loops, recursion, map, and vectorization okay. More on this after the code

    It's not pretty, but it works, and despite the gymnastics to avoid loops, it runs pretty fast, doing both challenges in ~400 ms.

    Part 1
    function main()
    
        # Read in raw input
        lines = []
        open("Day04/input.txt") do fp
            lines = readlines(fp)
        end
    
        # To avoid looping over things to add them to the dictionary
        function dict_add!(target, inputs)
            if length(inputs) < 1
                return
            else
                target[inputs[1][1]] = inputs[1][2]
                dict_add!(target, inputs[2:end])
            end
        end
    
        # Convert the IDs, again without using a loop
        function convert_ids!(output, lines)
            if length(lines) == 0
                return
            elseif lines[1] == ""
                push!(output, Dict())
                convert_ids!(output, lines[2:end])
            else
                curr_line = split.(split(chomp(lines[1]), ' '), ':')
                dict_add!(output[end], curr_line)
                convert_ids!(output, lines[2:end])
            end
        end
    
        # Do the conversion
        output = [Dict()]
        convert_ids!(output, lines)
    
        # Validate using sets!
        needed_keys = Set(["byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"])
        function validate_id(id, fields)
            return length(intersect(Set(keys(id)), fields)) >= length(fields)
        end
    
        result_1 = count(map(x -> validate_id(x, needed_keys), output))
    
        println("Result from part 1 is ", result_1)
    end
    
    main()
    
    Part 2 diff
    @@ -43,6 +43,82 @@ function main()
         result_1 = count(map(x -> validate_id(x, needed_keys), output))
     
         println("Result from part 1 is ", result_1)
    +
    +    # Make something to validate numbers, and make up functions for purely
    +    # numeric fields
    +    function validate_number(num, range)
    +        value = tryparse(Int, num)
    +
    +        if isa(value, Nothing)
    +            return false
    +        else
    +            return (value >= range[1] && value <= range[2])
    +        end
    +    end
    +
    +    function validate_byr(byr)
    +        return validate_number(byr, [1920, 2002])
    +    end
    +
    +    function validate_iyr(iyr)
    +        return validate_number(iyr, [2010, 2020])
    +    end
    +
    +    function validate_eyr(eyr)
    +        return validate_number(eyr, [2020, 2030])
    +    end
    +
    +    # Height needs a lil something extra
    +    function validate_hgt(hgt)
    +        if occursin("cm", hgt)
    +            return validate_number(hgt[1:end-2], [150, 193])
    +        elseif occursin("in", hgt)
    +            return validate_number(hgt[1:end-2], [59, 76])
    +        else
    +            return false
    +        end
    +    end
    +
    +    # Use a combination of simple checks and regex for the remaining fields
    +    function validate_ecl(ecl)
    +        valid_colors = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]
    +        return count(map(x -> occursin(x, ecl), valid_colors)) > 0
    +    end
    +
    +    function validate_hcl(hcl)
    +        if hcl[1] != '#'
    +            return false
    +        elseif length(hcl[2:end]) != 6
    +            return false
    +        else
    +            return length(collect(eachmatch(r"[0-9a-f]", hcl[2:end]))) == 6
    +        end
    +    end
    +
    +    function validate_pid(pid)
    +        if length(pid) != 9
    +            return false
    +        else
    +            return length(collect(eachmatch(r"[0-9]", pid))) == 9
    +        end
    +    end
    +
    +    # Combine into our final function and calculate
    +    function deep_validate_id(id)
    +        return (validate_byr(id["byr"])
    +                && validate_iyr(id["iyr"])
    +                && validate_eyr(id["eyr"])
    +                && validate_hgt(id["hgt"])
    +                && validate_ecl(id["ecl"])
    +                && validate_hcl(id["hcl"])
    +                && validate_pid(id["pid"]))
    +    end
    +
    +    output = output[map(x -> validate_id(x, needed_keys), output)]
    +
    +    result_2 = count(deep_validate_id.(output))
    +
    +    println("Result from part 2 is ", result_2)
     end
     
     main()
    

    This second part was super gross, so I just kind of hope this isn't the challenge we end up building off of for future challenges. If it is, I'll probably do a major refactor of this.

    I think after this I will probably use loops just because it's making everything far uglier than necessary. It was a valuable exercise though, I think I may try to approach more things from the "no loops" perspective in order to decide what needs to be broken out into general functions, and then just write those functions with loops. I ended up with a lot of things falling into place quickly at the end of these first few challenges thanks to having broken things out into function earlier on.

    2 votes
  16. [2]
    heady
    (edited )
    Link
    This is what I had rawinput = open("day4input", 'r') passports = [] passport = {} for line in rawinput: if len(line) == 1: passports.append(passport) passport = {} continue fields = line.split()...

    I'm stuck on part 1 and can't figure out why it's giving the wrong result.

    This is what I had
    rawinput = open("day4input", 'r')
    
    passports = []
    passport = {}
    
    for line in rawinput:
        if len(line) == 1:
            passports.append(passport)
            passport = {}
            continue
    
        fields = line.split()
        for field in fields:
            entry = field.split(':')
            passport[entry[0]] = entry[1]
                
    valid = 0
    for passport in passports:
        if len(passport) == 8:
            valid += 1
        elif len(passport) == 7 and 'cid' not in passport:
            valid += 1
    
    print(valid)
    

    Edit; Working version

    Part 1
    rawinput = open("day4input", 'r')
    
    passports = []
    passport = {}
    
    passport_count = 1
    passport_fresh = True
    
    for line in rawinput:
        if passport_fresh == True:
            passports.append(passport)
    
        if  len(line) == 1:
            passport_count += 1
            passport_fresh = True
            passport = {}
        else:
            passport_fresh = False
            fields = line.split()
            for field in fields:
                entry = field.split(':')
                passport = passports[(passport_count - 1)]
                passports.pop(passport_count - 1)
                passport[entry[0]] = entry[1]
                passports.append(passport)
    
    valid = 0
    for passport in passports:
        if len(passport) == 8:
            valid += 1
        elif len(passport) == 7 and 'cid' not in passport:
            valid += 1
    
    print(valid)
    
    1 vote
    1. heady
      Link Parent
      ok I just realised it's because this design doesn't include the passport until after a blank line meaning the last one is ignored, which ofc happened to be valid.

      ok I just realised it's because this design doesn't include the passport until after a blank line meaning the last one is ignored, which ofc happened to be valid.

      1 vote