Thank you so much for the clear and concise python, I'm new to python and thought this would be an excellent way to learn it. I figured this one out eventually and thought I would come here to see how someone else did it. I don't understand how yours doesn't throw a key error when trying to access dictionary keys that aren't there. I need to learn more about classes but I don't know why this works. If you could point me in the right direction I'd greatly appreciate it. Thank you again for the wonderful example
You're not alone in the ugly feeling. I had a particularly nasty bug that gave me one result too many in part two (realized I had a missing ^ and $ on the regex for pid).
I see a fair amount of similarities in approach, so I'm glad to see I'm in good company. :D
useaoc_runner_derive::{aoc,aoc_generator};useregex::Regex;#[derive(Debug,PartialEq)]structHeight{measure:usize,unit:String,}implHeight{fnparse(hgt_str:&str)->Option<Height>{letre=Regex::new("(\\d+)(in|cm)").expect("Unable to create Regex");matchre.captures(hgt_str){None=>None,Some(captures)=>{leth=Height{measure:str::parse(captures.get(1).unwrap().as_str()).expect("Unable to parse number"),unit:String::from(captures.get(2).unwrap().as_str()),};Some(h)}}}fnis_valid(&self)->bool{matchself.unit.as_str(){"cm"=>self.measure>=150&&self.measure<=193,"in"=>self.measure>=59&&self.measure<=76,_=>panic!("Not a valid unit"),}}}#[derive(Debug,PartialEq)]structPassport{byr:Option<usize>,iyr:Option<usize>,eyr:Option<usize>,hgt:Option<Height>,hgt_str:Option<String>,hcl:Option<String>,ecl:Option<String>,pid:Option<String>,cid:Option<String>,}implPassport{fnnew()->Passport{Passport{byr:None,iyr:None,eyr:None,hgt:None,hgt_str:None,hcl:None,ecl:None,pid:None,cid:None,}}fnhas_fields(&self)->bool{self.byr.is_some()&&self.iyr.is_some()&&self.eyr.is_some()&&self.hgt_str.is_some()&&self.hcl.is_some()&&self.ecl.is_some()&&self.pid.is_some()}fnis_valid(&self)->bool{self.valid_byr()&&self.valid_iyr()&&self.valid_eyr()&&self.valid_hgt()&&self.valid_hcl()&&self.valid_ecl()&&self.valid_pid()}fnvalid_byr(&self)->bool{matchself.byr{None=>false,Some(n)=>n>=1920&&n<=2002,}}fnvalid_iyr(&self)->bool{matchself.iyr{None=>false,Some(n)=>n>=2010&&n<=2020,}}fnvalid_eyr(&self)->bool{matchself.eyr{None=>false,Some(n)=>n>=2020&&n<=2030,}}fnvalid_hgt(&self)->bool{match&self.hgt{None=>false,Some(h)=>h.is_valid(),}}fnvalid_hcl(&self)->bool{letre=Regex::new("^#[0-9a-f]{6}$").expect("Failed to make regex");match&self.hcl{None=>false,Some(hair)=>re.is_match(hair.as_str()),}}fnvalid_ecl(&self)->bool{letvalid_colors=vec!["amb","blu","brn","gry","grn","hzl","oth"];match&self.ecl{None=>false,Some(c)=>valid_colors.contains(&c.as_str()),}}fnvalid_pid(&self)->bool{letre=Regex::new("^[0-9]{9}$").expect("Failed to build Regex");match&self.pid{None=>false,Some(pid)=>re.is_match(pid.as_str()),}}}#[aoc_generator(day4)]fnparse_input_day4(input:&str)->Vec<Passport>{input.split("\n\n").map(|passport_str|parse_passport(passport_str)).collect()}fnparse_passport(passport_str:&str)->Passport{letkv:Vec<&str>=passport_str.lines().flat_map(|line|line.split(" ")).collect();letmutpass=Passport::new();forkey_valinkv{letpair:Vec<&str>=key_val.split(":").collect();match*(pair.get(0).unwrap()){"cid"=>pass.cid=Some(String::from(*pair.get(1).unwrap())),"byr"=>pass.byr=Some(str::parse(*pair.get(1).unwrap()).unwrap()),"iyr"=>pass.iyr=Some(str::parse(*pair.get(1).unwrap()).unwrap()),"eyr"=>pass.eyr=Some(str::parse(*pair.get(1).unwrap()).unwrap()),"hgt"=>{pass.hgt_str=Some(str::parse(*pair.get(1).unwrap()).unwrap());pass.hgt=Height::parse(*pair.get(1).unwrap());}"hcl"=>pass.hcl=Some(String::from(*pair.get(1).unwrap())),"ecl"=>pass.ecl=Some(String::from(*pair.get(1).unwrap())),"pid"=>pass.pid=Some(String::from(*pair.get(1).unwrap())),_=>panic!("Found passport code that doesn't match"),}}pass}#[aoc(day4,part1)]fncount_valid_passports(input:&Vec<Passport>)->usize{input.iter().filter(|pass|pass.has_fields()).count()}#[aoc(day4,part2)]fncount_valid_data_passports(input:&Vec<Passport>)->usize{input.iter().filter(|pass|pass.is_valid()).count()}
I went ahead and wrote a solution using PEG in Python (parsimonious library). Aside from the slightly compact PEG grammar, the rest of it is very readable
Not my finest work, I took advantage of the fact that some problems didn't arise in the test data. Then again, this isn't real life, it's a game and I got the stars. 😉
IDENTIFICATION DIVISION.
PROGRAM-ID. AOC-2020-04-1.
AUTHOR. ANNA KOSIERADZKA.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT INPUTFILE ASSIGN TO "d4.input"
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD INPUTFILE
RECORD IS VARYING IN SIZE FROM 1 to 99
DEPENDING ON REC-LEN.
01 INPUTRECORD PIC X(99).
WORKING-STORAGE SECTION.
01 FILE-STATUS PIC 9 VALUE 0.
01 REC-LEN PIC 9(2) COMP.
01 WS-ROW PIC X(16) OCCURS 8 TIMES.
01 WS-CHAR PIC X.
LOCAL-STORAGE SECTION.
01 CORRECT-PASSPORTS UNSIGNED-INT VALUE 0.
01 FOUND-FIELDS UNSIGNED-INT VALUE 0.
01 STRING-PTR UNSIGNED-INT VALUE 1.
01 I UNSIGNED-INT VALUE 1.
PROCEDURE DIVISION.
001-MAIN.
OPEN INPUT INPUTFILE.
PERFORM 002-READ UNTIL FILE-STATUS = 1.
CLOSE INPUTFILE.
PERFORM 004-NEXT-PASSPORT.
DISPLAY CORRECT-PASSPORTS.
STOP RUN.
002-READ.
READ INPUTFILE
AT END MOVE 1 TO FILE-STATUS
NOT AT END PERFORM 003-PROCESS-RECORD
END-READ.
003-PROCESS-RECORD.
IF REC-LEN = 0 THEN
PERFORM 004-NEXT-PASSPORT
ELSE
PERFORM 005-PROCESS-ROW
END-IF.
004-NEXT-PASSPORT.
IF FOUND-FIELDS = 7 THEN
ADD 1 TO CORRECT-PASSPORTS
END-IF.
MOVE 0 TO FOUND-FIELDS.
005-PROCESS-ROW.
MOVE 1 TO STRING-PTR.
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 8
UNSTRING INPUTRECORD DELIMITED BY SPACE INTO WS-ROW(I)
WITH POINTER STRING-PTR
END-PERFORM.
PERFORM VARYING I FROM 1 BY 1 UNTIL I > 8
MOVE WS-ROW(I)(1:1) TO WS-CHAR
IF NOT WS-CHAR ='c' AND NOT WS-CHAR = ' ' THEN
ADD 1 TO FOUND-FIELDS
END-IF
END-PERFORM.
Reading all of these solutions, I'm glad I opted to avoid regexes in my answers! There are some messy hacks here ("1234567890abcdef".indexOf(c) stands out) and the types should probably all have been string? instead of string, which would have made this a little more concise, but I'm still pretty happy with how it turned out.
I am annoyed they decided to put multiple K:V pairs on the same line because it made the parsing a little messier but oh well.
packagemainimport("bufio""fmt""io""os""regexp""strconv""strings")varrequiredAttributes=[]string{"byr","iyr","eyr","hgt","hcl","ecl","pid"}funcisValid(passportmap[string]string)bool{for_,attribute:=rangerequiredAttributes{if_,exists:=passport[attribute];!exists{returnfalse}}returntrue}funcexists(slice[]string,valstring)bool{for_,item:=rangeslice{ifitem==val{returntrue}}returnfalse}funcvalidateRange(valstring,lower,upperint)bool{num,err:=strconv.Atoi(val)returnerr==nil&&num>=lower&&num<=upper}funcisValidV2(passportmap[string]string)bool{forattr,val:=rangepassport{switchattr{case"byr":if!validateRange(val,1920,2002){returnfalse}case"iyr":if!validateRange(val,2010,2020){returnfalse}case"eyr":if!validateRange(val,2020,2030){returnfalse}case"hgt":ifstrings.HasSuffix(val,"cm"){if!validateRange(strings.TrimSuffix(val,"cm"),150,193){returnfalse}}elseifstrings.HasSuffix(val,"in"){if!validateRange(strings.TrimSuffix(val,"in"),59,76){returnfalse}}else{returnfalse}case"hcl":ifmatch,_:=regexp.MatchString("^#[0-9a-f]{6}$",val);!match{returnfalse}case"ecl":eyeColors:=[]string{"amb","blu","brn","gry","grn","hzl","oth"}if!exists(eyeColors,val){returnfalse}case"pid":ifmatch,_:=regexp.MatchString("^[0-9]{9}$",val);!match{returnfalse}}}returntrue}funcmain(){reader:=bufio.NewReader(os.Stdin)passport:=make(map[string]string,8)numValid,numValidV2:=0,0for{varlinestringline,err:=reader.ReadString('\n')iferr==io.EOF{break}line=strings.TrimSpace(line)iflen(line)==0{// passport completeifisValid(passport){numValid++ifisValidV2(passport){numValidV2++}}// reset passportpassport=make(map[string]string,8)}else{parts:=strings.Split(line," ")for_,part:=rangeparts{attribute:=strings.Split(part,":")passport[attribute[0]]=attribute[1]}}}// last one is special snowflake, as file does not end with 2 newlinesifisValid(passport){numValid++ifisValidV2(passport){numValidV2++}}fmt.Println(numValid,numValidV2)}
The input s to is_valid should be a full passport string (what you get after splitting by double new lines). It does parsing + validation at once and eagerly exits if any field validation fails.
Yes, I know - very cursed. 'twas just for fun, and to prove a point ;)
Coding since 11yo, that makes it over 30 years now ~~~
Have a PhD in Comp Sci ~~~
Love to go on bike tours ~~~
I try to stay as generalist as I can in this crazy wide place coding is at now.
Still not doing these the day the come out but here's my Haskell solution.
Problems like these seem like work to me :P
{-# LANGUAGE LambdaCase #-}moduleDay4whereimportText.ParsecimportText.Parsec.StringimportControl.Monad(guard)importData.Maybe(mapMaybe,isJust)importData.List.Splithiding(sepBy,endBy,oneOf)importqualifiedData.MapasMimportData.Map(Map)importData.List((\\))importText.Read(readMaybe)importData.CharimportData.Semigroup(All(All),getAll)hush::Eitherea->Maybeahush(Left_)=Nothinghush(Righta)=Justaparse'::Parsera->String->Maybeaparse'p=hush.parsep""-- Part 1parsePassport::String->EitherParseError(MapStringString)parsePassport=fmapM.fromList.parse'(kvp`sepEndBy`space)wherekvp=(,)<$>manyTillanyChar(char':')<*>many(noneOf"\n ")hasRequiredFields::MapStringString->BoolhasRequiredFieldsm=null$["byr","ecl","eyr","hcl","hgt","iyr","pid"]\\M.keysmvalidatePassports::(MapStringString->Bool)->String->[MapStringString]validatePassportsv=filterv.mapMaybe(hush.parsePassport).splitOn"\n\n"solveP1::String->IntsolveP1=length.validatePassportshasRequiredFields-- Part 2typeValidationa=(a->Maybea)within::(Int,Int)->Int->Boolwithin(min,max)i=i>=min&&i<=maxyear::String->MaybeIntyears=parse'(4`times`digit)s>>=readMaybeyearBetween::(Int,Int)->String->MaybeStringyearBetweenrs=years>>=\n->guard(withinrn)>>puresbyr::ValidationStringbyr=yearBetween(1929,2020)iyr::ValidationStringiyr=yearBetween(2010,2020)eyr::ValidationStringeyr=yearBetween(2020,2030)hgt::ValidationStringhgt=parse'(mappend<$>many1digit<*>(try(string"cm")<|>try(string"in")))hcl::ValidationStringhcl=parse'(mappend<$>string"#"<*>6`times`anyChar)ecl::ValidationStringecls|s=="amb"=Justs|s=="blu"=Justs|s=="brn"=Justs|s=="gry"=Justs|s=="grn"=Justs|s=="hzl"=Justs|s=="oth"=Justs|otherwise=Nothingpid::ValidationStringpids=guard(lengths==9&&isNumbers)>>pureswhereisNumber=getAll.foldMap(All.isDigit)validateFields::MapStringString->Maybe(MapStringString)validateFields=M.traverseWithKeyvalidateAtKeywherevalidateAtKey=\case"byr"->byr"iyr"->iyr"eyr"->eyr"hgt"->hgt"hcl"->hcl"ecl"->ecl_->JustsolveP2::String->IntsolveP2=length.validatePassports(\x->hasRequiredFieldsx&&isJust(validateFieldsx))
Feels like it could have been cleaner, my first round was worse though! I refactored to use Result and the ? error propagation operator, which was fun.
useregex::Regex;useanyhow::{anyhow,Result};#[derive(Clone)]structPassport{byr:Option<String>,iyr:Option<String>,eyr:Option<String>,hgt:Option<String>,hcl:Option<String>,ecl:Option<String>,pid:Option<String>,cid:Option<String>,}implDefaultforPassport{fndefault()->Self{Passport{byr:None,iyr:None,eyr:None,hgt:None,hcl:None,ecl:None,pid:None,cid:None,}}}fnmain(){letinput=std::fs::read_to_string("input.txt").unwrap();letpassports:Vec<Passport>=input.trim().split("\n\n").map(parse_passport).collect();letnum_valid=passports.into_iter().filter(|p|{// validate_passport(p).is_ok()matchvalidate_passport(p){Ok(_)=>true,Err(e)=>{println!("{}",e);false},}}).count();println!("Num Valid: {}",num_valid);}fnparse_passport(input:&str)->Passport{letmutp:Passport=Passport::default();forentryininput.replace("\n"," ").split(" "){letparts:Vec<&str>=entry.split(":").collect();let(label,value)=(parts[0],parts[1]);matchlabel{"byr"=>{p.byr=Some(value.to_owned())},"iyr"=>{p.iyr=Some(value.to_owned())},"eyr"=>{p.eyr=Some(value.to_owned())},"hgt"=>{p.hgt=Some(value.to_owned())},"hcl"=>{p.hcl=Some(value.to_owned())},"ecl"=>{p.ecl=Some(value.to_owned())},"pid"=>{p.pid=Some(value.to_owned())},"cid"=>{p.cid=Some(value.to_owned())},_=>{},}}p}fnvalidate_passport(p:&Passport)->Result<()>{check_year_range(p.clone().byr,1920,2002)?;check_year_range(p.clone().iyr,2010,2020)?;check_year_range(p.clone().eyr,2020,2030)?;check_height(p.clone().hgt)?;check_regex(p.clone().hcl,"#[0-9A-Fa-f]{6}")?;check_regex(p.clone().ecl,"^(amb|blu|brn|gry|grn|hzl|oth)$")?;check_regex(p.clone().pid,"^[0-9]{9}$")?;Ok(())}fncheck_year_range(year_str:Option<String>,min:usize,max:usize)->Result<()>{letyear=year_str.ok_or(anyhow!("Missing value"))?.parse::<usize>().map_err(|_e|anyhow!("Failed to parse year_str"))?;ifyear<min||year>max{returnErr(anyhow!("Year out of range"));}Ok(())}fncheck_height(height:Option<String>)->Result<()>{lethgt=height.ok_or(anyhow!("Missing hgt"))?;letre=Regex::new(r"^([0-9]+)(cm|in)$").unwrap();letcaps=re.captures(hgt.as_str()).ok_or(anyhow!("Invalid height format"))?;letnum_str=caps.get(1).unwrap().as_str();letunit=caps.get(2).unwrap();letnum=num_str.parse::<usize>().map_err(|_e|anyhow!("Failed to parse height value"))?;matchunit.as_str(){"in"ifnum<59||num>76=>{returnErr(anyhow!("Height out of range"));},"cm"ifnum<150||num>193=>{returnErr(anyhow!("Height out of range"));},_=>{},}Ok(())}fncheck_regex(field:Option<String>,re_str:&str)->Result<()>{letre=Regex::new(re_str).unwrap();letval=field.ok_or(anyhow!("Field missing"))?;matchre.is_match(val.as_str()){true=>Ok(()),false=>Err(anyhow!(format!("Regex mismatch: {}, {}",re_str,val))),}}
God damn, it was not that easy for me! To be honest, I'm not the best with both algorithms and Elixir, but... anyway AoC is a great chance to improve these skills. I've wasted time making stupid things with Regex. Facepalm.
I'm a Staff Software Engineer at Cisco. I specialize in Python and Go, building functional, practical, and maintainable web systems leveraging Kubernetes and the cloud. Blog opinions are my own.
This was a long one, but I learned a ton about the <string.h> library. I also incremented a macro by 1 and spent 3 hours chasing a segfault. So, all in all, I think it sounds like we all had similar days. There are also several edge cases that I'm pretty sure my code doesn't cover, BUT that doesn't matter because this is AoC and it's not wrong if you get the stars. Right? Right?
Day4.h:
#ifndef AOC2020_DAY4_H
#define AOC2020_DAY4_H
/// Day 4: Passport Processing////// Pick through passports made up of key/value pairs to figure out/// which ones are valid.#include <stdlib.h>
#include <stdbool.h>
#define NUM_PASS_FIELDS 8 ///< Number of fields in a SimplePassport
#define MAX_ENTRY_SIZE 20 ///< Max size of possible value in key/value
#define KEY_SIZE 4 ///< Size of a key in key/value
#define HAIR_COLOR_SIZE 8 ///< Size of a well-formed hair color
#define PID_SIZE 10 ///< Size of a well-formed PID
/// A simple passport just has certain fields or not. Valid ones have/// them all, with 'cid' being optional.typedefstruct{boolbyr;booliyr;booleyr;boolhgt;boolhcl;boolecl;boolpid;boolcid;}SimplePassport;/// Parses the input file, which is a series of passports. Each key/val/// is separated by a space or newline. Passports are separated by /// two newlines. Returns a list of passport structs.SimplePassport*parse_simple_passports(constchar*filename,int*count);/// Counts the number of valid passports. Passports are valid if/// the have all keys, except CID is optional.intpart1(SimplePassport*passes,intcount);// ============== Part 2 ================== ///// Possible values for height units: none, inches, or centimeters.typedefenum{HT_BAD,HT_IN,HT_CM,}HeightUnit;/// A height is a measurement associated with a set of units.typedefstruct{intvalue;HeightUnitunits;}Height;/// Possible options for eye colortypedefenum{EYE_BAD,EYE_AMBER,EYE_BLUE,EYE_BROWN,EYE_GREY,EYE_GREEN,EYE_HAZEL,EYE_OTHER,}EyeColor;/// A fancy passport has strict value validation for all fields./// Note: all fields here must be present to win.typedefstruct{/// Birth Year: 1920 <= x <= 2002intbyr;/// Issue Year: 2010 <= x <= 2020intiyr;/// Expiration Year: 2020 <= x <= 2030inteyr;/// Height: 150 <= cm <= 193 || 59 <= in <= 76Heighthgt;/// Hair Color: # followed by 6 chars 0-9 or a-fcharhcl[8];/// Eye Color: amb | blu | brn | gry | grn | hzl | othEyeColorecl;/// Passport ID : Exactly 9 digits. (Storing 10 to find invalid ones)charpid[10];}FancyPassport;/// Parse fancy passports from an input file. The number of them/// is stored in 'count'.FancyPassport*parse_fancy_passports(constchar*filename,int*count);/// Counts the number of valid fancy passports based on the rules above.intpart2(FancyPassport*passes,intcount);/// Runs both parts.intday4(void);#endif
Day4.c:
#include "Day4.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/// Loops through a file and counts all instances of double-newlines./// Adds one at the end for the last entry which doesn't have a trailing/// double-newline.staticintcount_passports(FILE*fp){intcount=0;while(!feof(fp)){if(getc(fp)=='\n'&&getc(fp)=='\n')count++;}count++;// No double newline after last one.rewind(fp);returncount;}/// Parses a single passport from a file. Leaves the file pointer /// after the trailing double-newline, ready to parse the next one.staticSimplePassportparse_simple_passport(FILE*fp){charkey[4];charvalue[20];SimplePassportpassport={0};while(!feof(fp)&&getc(fp)!='\n'){memset(key,0,4);memset(value,0,20);fseek(fp,-1,SEEK_CUR);// Un-eat the previous char because we need it.fgets(key,KEY_SIZE,fp);fscanf(fp,"%s",value);// Eat the colon, valuegetc(fp);// Eat white space?if(strcmp(key,"byr")==0)passport.byr=true;elseif(strcmp(key,"iyr")==0)passport.iyr=true;elseif(strcmp(key,"eyr")==0)passport.eyr=true;elseif(strcmp(key,"hgt")==0)passport.hgt=true;elseif(strcmp(key,"hcl")==0)passport.hcl=true;elseif(strcmp(key,"ecl")==0)passport.ecl=true;elseif(strcmp(key,"pid")==0)passport.pid=true;elseif(strcmp(key,"cid")==0)passport.cid=true;else{printf("Unrecognized key: %s\n",key);}}returnpassport;}SimplePassport*parse_simple_passports(constchar*filename,int*count){FILE*fp;fp=fopen(filename,"r");if(fp==NULL){printf("Couldn't open file.\n");exit(EXIT_FAILURE);}*count=count_passports(fp);SimplePassport*passes=(SimplePassport*)malloc(sizeof(SimplePassport)**count);for(inti=0;i<*count;i++){passes[i]=parse_simple_passport(fp);}fclose(fp);returnpasses;}intpart1(SimplePassport*passes,intcount){intinvalid=0;for(inti=0;i<count;i++){bool*p=(bool*)&passes[i];// Iterates over each field of the passport by using the bytes of// the struct as an array of bools. Which should be OK?for(intj=0;j<NUM_PASS_FIELDS-1;j++){if(p[j]==false){invalid++;break;};}}returncount-invalid;}// ================= Part 2 ===================== ///// Parses a height string to a Height struct. Well-formed strings/// are [0-9]+(in|cm). If it's invalid, leaves the value as 0 and/// the units as HT_BAD.staticHeightparse_height(char*value){Heighth;intpossible_value;charunits[4];sscanf(value,"%d%s",&possible_value,units);if(strcmp(units,"in")==0){h.value=possible_value;h.units=HT_IN;}elseif(strcmp(units,"cm")==0){h.value=possible_value;h.units=HT_CM;}else{h.value=0;h.units=HT_BAD;}returnh;}/// Parses an EyeColor from a string.staticEyeColorparse_eye_color(char*value){if(strcmp(value,"amb")==0)returnEYE_AMBER;if(strcmp(value,"blu")==0)returnEYE_BLUE;if(strcmp(value,"brn")==0)returnEYE_BROWN;if(strcmp(value,"gry")==0)returnEYE_GREY;if(strcmp(value,"grn")==0)returnEYE_GREEN;if(strcmp(value,"hzl")==0)returnEYE_HAZEL;if(strcmp(value,"oth")==0)returnEYE_OTHER;returnEYE_BAD;}/// Prints out a FancyPassport for debugging.staticvoidprint_fancy_passport(FancyPassport*p){printf("Passport:\n");printf("BYR: %d\nIYR: %d\nEYR: %d\n",p->byr,p->iyr,p->eyr);printf("HGT: %d-%d\nHCL: %s\nECL: %d\n",p->hgt.value,p->hgt.units,p->hcl,p->ecl);printf("PID: %s\n",p->pid);}/// Parses a single fancy passport from a file.staticFancyPassportparse_fancy_passport(FILE*fp){charkey[KEY_SIZE];charvalue[20];FancyPassportpassport={0};while(!feof(fp)&&getc(fp)!='\n'){memset(key,0,4);memset(value,0,20);fseek(fp,-1,SEEK_CUR);// Un-eat the previous char because we need it.fgets(key,KEY_SIZE,fp);fscanf(fp,":%s",value);// Eat the colon, valuegetc(fp);// Eat white spaceif(strcmp(key,"byr")==0)passport.byr=atoi(value);elseif(strcmp(key,"iyr")==0)passport.iyr=atoi(value);elseif(strcmp(key,"eyr")==0)passport.eyr=atoi(value);elseif(strcmp(key,"hgt")==0)passport.hgt=parse_height(value);elseif(strcmp(key,"hcl")==0){if(strlen(value)!=HAIR_COLOR_SIZE-1)continue;// Leave it emptystrncpy(passport.hcl,value,HAIR_COLOR_SIZE-1);}elseif(strcmp(key,"ecl")==0)passport.ecl=parse_eye_color(value);elseif(strcmp(key,"pid")==0){if(strlen(value)!=PID_SIZE-1)continue;// Leave it emptystrncpy(passport.pid,value,PID_SIZE-1);}elseif(strcmp(key,"cid")==0)continue;else{printf("Unrecognized key: %s\n",key);}}returnpassport;}FancyPassport*parse_fancy_passports(constchar*filename,int*count){FILE*fp;fp=fopen(filename,"r");if(fp==NULL){printf("Couldn't open file.\n");exit(EXIT_FAILURE);}*count=count_passports(fp);FancyPassport*passes=(FancyPassport*)malloc(sizeof(FancyPassport)**count);for(inti=0;i<*count;i++){passes[i]=parse_fancy_passport(fp);}fclose(fp);returnpasses;}/// Checks whether a hair color is valid.staticboolvalid_hair_color(char*color){if(color[0]!='#')returnfalse;for(inti=1;color[i];i++){if((color[i]<'0'||color[i]>'9')&&(color[i]<'a'||color[i]>'f')){returnfalse;}}returntrue;}/// Checks whether a PID is valid.staticboolvalid_pid(char*value){if(strlen(value)!=PID_SIZE-1)returnfalse;for(inti=0;value[i];i++){if(value[i]<'0'||value[i]>'9')returnfalse;}returntrue;}/// Finger-saving macro for part2: increment the invalids and continue#define INC_BAD {invalid++; continue;}
intpart2(FancyPassport*passes,intcount){intinvalid=0;for(inti=0;i<count;i++){FancyPassportp=passes[i];if(p.byr<1920||p.byr>2002)INC_BADif(p.iyr<2010||p.iyr>2020)INC_BADif(p.eyr<2020||p.eyr>2030)INC_BADif(p.hgt.units==HT_BAD)INC_BADif(p.hgt.units==HT_CM&&(p.hgt.value<150||p.hgt.value>193))INC_BADif(p.hgt.units==HT_IN&&(p.hgt.value<59||p.hgt.value>76))INC_BADif(!valid_hair_color(p.hcl))INC_BADif(p.ecl==EYE_BAD)INC_BADif(!valid_pid(p.pid))INC_BAD}returncount-invalid;}intday4(){intcount;SimplePassport*passes=parse_simple_passports("data/day4.txt",&count);printf("====== Day 4 ======\n");printf("Part 1: %d\n",part1(passes,count));free(passes);count=0;FancyPassport*passes2=parse_fancy_passports("data/day4.txt",&count);printf("Part 2: %d\n",part2(passes2,count));free(passes2);returnEXIT_SUCCESS;}
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:
I hate problems like this:
Thank you so much for the clear and concise python, I'm new to python and thought this would be an excellent way to learn it. I figured this one out eventually and thought I would come here to see how someone else did it. I don't understand how yours doesn't throw a key error when trying to access dictionary keys that aren't there. I need to learn more about classes but I don't know why this works. If you could point me in the right direction I'd greatly appreciate it. Thank you again for the wonderful example
My super gross rust impl. Messy regex. I am sure there are way better ways to parse according to white space.
Also, I sort of went half-way with a struct solution. Probably would have been better to either go all the way or not have custom types at all.
You're not alone in the ugly feeling. I had a particularly nasty bug that gave me one result too many in part two (realized I had a missing ^ and $ on the regex for
pid).I see a fair amount of similarities in approach, so I'm glad to see I'm in good company. :D
As always, on Github.
I went ahead and wrote a solution using PEG in Python (parsimonious library). Aside from the slightly compact PEG grammar, the rest of it is very readable
Not my finest work, I took advantage of the fact that some problems didn't arise in the test data. Then again, this isn't real life, it's a game and I got the stars. 😉
Part 2 here.
Reading all of these solutions, I'm glad I opted to avoid regexes in my answers! There are some messy hacks here (
"1234567890abcdef".indexOf(c)stands out) and the types should probably all have beenstring?instead ofstring, which would have made this a little more concise, but I'm still pretty happy with how it turned out.I am annoyed they decided to put multiple K:V pairs on the same line because it made the parsing a little messier but oh well.
Go again, a bit hacky with the very last element
would anyone be interested in a solution written entirely in C, without regex or hashtables
The input
stois_validshould be a full passport string (what you get after splitting by double new lines). It does parsing + validation at once and eagerly exits if any field validation fails.Yes, I know - very cursed. 'twas just for fun, and to prove a point ;)
Warning this might be offensive to some :D
My Saturday was too long, so I created a code-golfed PHP version (for both parts)
Plain forgot to break out of the comfort zone and use something other than javascript :|
I'll try C tomorrow
Here's today:
(added
checkas a way to see which field validator was broken when I failed part 2 the first time. It was a stupid typo in 'ecl')I feel this reads as quite elegant in Ruby -- or at least my implementation of it.
Hoping on something more interesting to do for Day 5!
Still not doing these the day the come out but here's my Haskell solution.
Problems like these seem like work to me :P
Feels like it could have been cleaner, my first round was worse though! I refactored to use
Resultand the?error propagation operator, which was fun.God damn, it was not that easy for me! To be honest, I'm not the best with both algorithms and Elixir, but... anyway AoC is a great chance to improve these skills. I've wasted time making stupid things with Regex. Facepalm.
There is my solution for the first step:
And the second step:
This was a long one, but I learned a ton about the
<string.h>library. I also incremented a macro by 1 and spent 3 hours chasing a segfault. So, all in all, I think it sounds like we all had similar days. There are also several edge cases that I'm pretty sure my code doesn't cover, BUT that doesn't matter because this is AoC and it's not wrong if you get the stars. Right? Right?Day4.h:
Day4.c:
Hi,
A lot of class for this one in order to limit each class responsability.
Full size here : Advent of Code - Day 4
Again, more javascript. It's in a gist if you like that sort of thing.
Part 1 required far less code than part 2 😋
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
Part 2
Passport
Ruby, part 2:
Here is a Haskell soloution for Day 4:
My JavaScript walkthrough: