Skip to content

Instantly share code, notes, and snippets.

@ivan-pi
Created December 4, 2020 23:20
Show Gist options
  • Save ivan-pi/96efd13565d87e056bc1dbffb5fc691d to your computer and use it in GitHub Desktop.
Save ivan-pi/96efd13565d87e056bc1dbffb5fc691d to your computer and use it in GitHub Desktop.
Advent of Code - Day 4
module day4
use fortran202x_split, only: split
!! The `fortran202x_split` module can be downloaded from:
!! https://github.com/milancurcic/fortran202x_split
implicit none
private
public :: count_passports
integer, parameter :: valid_mask = int(b'01111111')
character(len=*), parameter :: digits = "1234567890"
character(len=*), parameter :: lowercase = "abcdefghijklmnopqrstuvwxyz"
type :: passport
integer :: key_mask = 0, value_mask = 0
integer :: byr, iyr, eyr
integer :: hgt
character(len=2) :: hgt_unit
character(len=7) :: hcl
character(len=3) :: ecl
character(len=9) :: pid
integer :: cid
contains
procedure :: add_key
procedure :: add_key_value
procedure :: clear
procedure :: keys_present
procedure :: keys_present_and_valid
end type
contains
subroutine add_key(self,key)
class(passport), intent(inout) :: self
character(len=3), intent(in) :: key
select case(key)
case('byr') ! Birth year
self%key_mask = ibset(self%key_mask,0)
case('iyr') ! Issue year
self%key_mask = ibset(self%key_mask,1)
case('eyr') ! Expiration year
self%key_mask = ibset(self%key_mask,2)
case('hgt') ! Height
self%key_mask = ibset(self%key_mask,3)
case('hcl') ! Hair color
self%key_mask = ibset(self%key_mask,4)
case('ecl') ! Eye color
self%key_mask = ibset(self%key_mask,5)
case('pid') ! Passport ID
self%key_mask = ibset(self%key_mask,6)
case('cid') ! Country ID
self%key_mask = ibset(self%key_mask,7)
case default
write(*,*) "[add_key] Invalid key."
error stop 1
end select
end subroutine
subroutine add_key_value(self,key,value,stat)
class(passport), intent(inout) :: self
!! Passport object.
character(len=3), intent(in) :: key
!! Key string (3-letters).
character(len=*), intent(in) :: value
!! Trimmed value string.
integer, intent(out), optional :: stat
!! Error flag.
integer :: stat_, lt
stat_ = 0
associate(mask => self%value_mask)
select case(key)
case('byr')
! Birth year
read(value,*,iostat=stat_) self%byr
if (self%byr >= 1920 .and. self%byr <= 2002) mask = ibset(mask,0)
case('iyr')
! Issue year
read(value,*,iostat=stat_) self%iyr
if (self%iyr >= 2010 .and. self%iyr <= 2020) mask = ibset(mask,1)
case('eyr')
! Expiration year
read(value,*,iostat=stat_) self%eyr
if (self%eyr >= 2020 .and. self%eyr <= 2030) mask = ibset(mask,2)
case('hgt')
! Height
lt = len(value)
if (lt >= 4) then
self%hgt_unit = value(lt-1:lt)
read(value(1:lt-2),*,iostat=stat_) self%hgt
select case(self%hgt_unit)
case('cm')
if (self%hgt >= 150 .and. self%hgt <= 193) mask = ibset(mask,3)
case('in')
if (self%hgt >= 59 .and. self%hgt <= 76) mask = ibset(mask,3)
end select
end if
case('hcl')
! Hair color
if (len(value) == 7) then
self%hcl = value(1:7)
if (self%hcl(1:1) == '#') then
if (verify(self%hcl(2:),digits//lowercase) == 0) &
mask = ibset(mask,4)
end if
end if
case('ecl')
! Eye color
if (len(value) == 3) then
self%ecl = value(1:3)
select case(self%ecl)
case('amb', 'blu', 'brn', 'gry', 'grn', 'hzl', 'oth')
mask = ibset(mask,5)
end select
end if
case('pid')
! Passport ID
if (len(value) == 9) then
self%pid = value(1:9)
if (len_trim(self%pid) == 9 .and. &
verify(self%pid,digits) == 0) mask = ibset(mask,6)
end if
case('cid')
! Country ID
read(value,*,iostat=stat_) self%cid
case default
write(*,*) "[add_key_value] Invalid key."
error stop 1
end select
end associate
if (present(stat)) stat = stat_
end subroutine
subroutine clear(self)
class(passport), intent(out) :: self
! reset self%key_mask by specifying intent(out)
end subroutine
logical function keys_present(self)
class(passport), intent(in) :: self
keys_present = iand(self%key_mask,valid_mask) >= valid_mask
end function
logical function keys_present_and_valid(self) result(valid)
class(passport), intent(in) :: self
if (keys_present(self)) then
valid = iand(self%value_mask,valid_mask) >= valid_mask
else
valid = .false.
end if
end function
integer function count_passports(file,validate) result(nvalid)
character(len=*), intent(in) :: file
logical, intent(in), optional :: validate
integer :: unit, stat, i, npass
character(len=1000) :: buffer
character(len=3) :: key
character(len=30) :: value
character(:), allocatable :: tokens(:)
logical :: validate_, eof
type(passport) :: pp
validate_ = .false.
if (present(validate)) validate_ = validate
open(newunit=unit,file=file,action="read",status="old")
npass = 0
nvalid = 0
eof = .false.
do
! Load buffer
read(unit,'(A)',iostat=stat) buffer
if (stat < 0) eof = .true.
if (stat > 0) then
! Error while reading
write(*,'(A,I0,A)') "Error encountered in unit ", unit, &
" associated with file "//trim(file)
write(*,'(A,I0)') "Error code: ", stat
error stop 1
end if
if (len_trim(buffer) > 0) then
call split(trim(buffer),' ',tokens)
do i = 1, size(tokens)
key = tokens(i)(1:3)
value = trim(tokens(i)(5:))
call pp%add_key(key)
if (validate_) then
call pp%add_key_value(key,trim(value),stat)
if (stat /= 0) then
error stop "[count_passports] Error inserting value."
end if
end if
end do
end if
if (len_trim(buffer) == 0 .or. eof) then
npass = npass + 1
if (validate_) then
if (pp%keys_present_and_valid()) nvalid = nvalid + 1
else
if (pp%keys_present()) nvalid = nvalid + 1
end if
call pp%clear()
end if
if (eof) exit
end do
close(unit)
end function
end module
program main
use day4
implicit none
integer :: n
! Part 1
n = count_passports("input",validate=.false.)
write(*,'(A,I0)') "# of valid passports: ", n
! Part 2
n = count_passports("input",validate=.true.)
write(*,'(A,I0)') "# of valid passports: ", n
end program
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment