problem_22.f90 Source File


This file depends on

sourcefile~~problem_22.f90~~EfferentGraph sourcefile~problem_22.f90 problem_22.f90 sourcefile~aoc_utilities.f90 aoc_utilities.F90 sourcefile~problem_22.f90->sourcefile~aoc_utilities.f90 sourcefile~cache_module.f90~2 cache_module.f90 sourcefile~problem_22.f90->sourcefile~cache_module.f90~2 sourcefile~cache_module.f90~2->sourcefile~aoc_utilities.f90

Source Code

program problem_22

use iso_fortran_env
use aoc_utilities
use aoc_cache_module

implicit none

integer :: iunit, n_lines
integer(ip) :: i, j, k
character(len=:),allocatable :: line
type(string),dimension(:),allocatable :: start_end, starts, ends
integer(ip),dimension(:,:),allocatable :: istart_array
integer(ip),dimension(:,:),allocatable :: iend_array
integer(ip),dimension(:,:,:),allocatable :: array
integer(ip),dimension(:),allocatable :: ipieces_above, ipieces_below
integer(ip) :: ok_to_disintegrate, isum, isum_total
logical :: ok_tmp
type(function_cache) :: above_cache, below_cache

call clk%tic()

!find the ones we can remove without any other falling:
!  to do that, look to see if any above it are not otherwise held up by another piece
call initialize()
ok_to_disintegrate = 0
do i = 1, n_lines
    ipieces_above = get_pieces_above(i)
    if (size(ipieces_above)==0) then
        ! nothing above, it so ok to remove
        ok_to_disintegrate = ok_to_disintegrate + 1
    else
        ok_tmp = .true.
        do j = 1, size(ipieces_above) ! check all the pieces above. if ALL have other supporters then OK to remove
            ipieces_below = get_pieces_below(ipieces_above(j))
            if (all(ipieces_below==i)) then ! only supported by i , so can't remove i
                ok_tmp = .false.
                exit
            end if
        end do
        if (ok_tmp) ok_to_disintegrate = ok_to_disintegrate + 1
    end if
end do
write(*,*) '22a: ', ok_to_disintegrate

! for part 2, we need to find all the nodes in the tree
! that are only supportd by ones below that in the tree
!
!  777  44444
! 66111222  9
!88 333333333   <--- deleting 3 will cause 1, 2, 4, 9 to fall, but not 6,7,8

! just use a cache to save the ones above/below a given piece
! since there is a lot of duplication in that calculation
call above_cache%initialize(isize=1000,chunk_size=1000)
call below_cache%initialize(isize=1000,chunk_size=1000)

call initialize()
isum_total = 0
do i = 1, n_lines ! try remove piece i
    ipieces_above = get_all_pieces_above(i)
    isum = size(ipieces_above) ! start out assuming they will all fall
    if (isum>0) then
        main : do j = 1, size(ipieces_above)
            ! for all the ones, see if there are any below that are not in the above set.
            ! is so, then it will not fall.
            ipieces_below = get_all_pieces_below(ipieces_above(j), iskip=i)
            ipieces_below = pack(ipieces_below, ipieces_below/=i) ! remove i if present (the one being removed)
            do k = 1, size(ipieces_below)
                if (.not. any(ipieces_below(k)==[ipieces_above])) then
                    isum = isum - 1 ! this one will not fall, so remove it from total
                    cycle main
                end if
            end do
        end do main
    end if
    isum_total = isum_total + isum
end do
write(*,*) '22b: ', isum_total

call clk%toc('22')

contains

    subroutine initialize()
        !! read the data
        ! open(newunit=iunit, file='inputs/day22_test.txt', status='OLD')
        open(newunit=iunit, file='inputs/day22.txt', status='OLD')
        n_lines = number_of_lines_in_file(iunit)
        if (allocated(istart_array)) deallocate(istart_array); allocate(istart_array(n_lines,3))
        if (allocated(iend_array)) deallocate(iend_array); allocate(iend_array(n_lines,3))
        do i = 1, n_lines
            line = read_line(iunit); start_end = split(line, '~')
            starts = split(start_end(1)%str, ',')
            ends   = split(start_end(2)%str, ',')
            istart_array(i,:) = [(int(starts(j)%str), j = 1, 3)]
            iend_array(i,:)   = [(int(ends(j)%str),   j = 1, 3)]
        end do
        close(iunit)
        ! create the array to hold all the pieces:
        if (allocated(array)) deallocate(array)
        allocate(array(0:maxval([istart_array(:,1), iend_array(:,1)]),&
                       0:maxval([istart_array(:,2), iend_array(:,2)]),&
                       0:maxval([istart_array(:,3), iend_array(:,3)])))
        call update_array()  ! set array values from the start/end arrays
        call drop() ! continue forward in time until all the bricks settle.
    end subroutine initialize

    subroutine drop()
        !! continue forward in time until all the bricks settle.
        logical :: moved, tmp_moved
        integer(ip) :: i
        do
            moved = .false.
            do i = 1, n_lines
                call move_piece_down(i, tmp_moved)
                if (tmp_moved) moved = .true. ! at least one was moved
            end do
            if (.not. moved) exit ! done moving all the pieces
        end do
    end subroutine drop

    recursive function get_all_pieces_above(i) result(ipieces)
        !! reursively get a list of all pieces above piece i
        integer(ip),intent(in) :: i
        integer(ip),dimension(:),allocatable :: ipieces
        integer :: j !! counter
        logical :: found
        integer(ip) :: idx
        call above_cache%get([i],idx,ipieces,found)
        if (.not. found) then
            allocate(ipieces(0))
            associate(tmp => get_pieces_above(i))
                if (size(tmp)>0) then
                    ipieces = [ipieces, tmp] ! ones directly above
                    do j = 1, size(tmp) ! go up the tree
                        ipieces = [ipieces, get_all_pieces_above(tmp(j))]
                    end do
                    ipieces = unique(ipieces)
                end if
            end associate
           call above_cache%put(idx,[i],ipieces)
        end if
    end function get_all_pieces_above

    recursive function get_all_pieces_below(i, iskip) result(ipieces)
        !! reursively get a list of all pieces below piece i
        integer(ip),intent(in) :: i
        integer(ip),intent(in) :: iskip !! skip this one and it's children
        integer(ip),dimension(:),allocatable :: ipieces
        integer :: j !! counter
        logical :: found
        integer(ip) :: idx
        integer(ip),dimension(:),allocatable :: tmp
        call below_cache%get([i,iskip],idx,ipieces,found)
        if (.not. found) then
            allocate(ipieces(0))
            if (i==iskip) return
            tmp = get_pieces_below(i)
            if (size(tmp)>0) then
                tmp = pack(tmp, tmp/=iskip) ! remove the skipped one
                ipieces = [ipieces, tmp] ! ones directly below
                do j = 1, size(tmp) ! go down the tree
                    ipieces = [ipieces, get_all_pieces_below(tmp(j),iskip=iskip)]
                end do
                ipieces = unique(ipieces)
            end if
           call below_cache%put(idx,[i,iskip],ipieces)
        end if
    end function get_all_pieces_below

    subroutine update_array()
        !! populate the array using the start/end indices
        integer :: i
        array = 0
        do i = 1, n_lines
            array(istart_array(i,1):iend_array(i,1),&
                  istart_array(i,2):iend_array(i,2),&
                  istart_array(i,3):iend_array(i,3)) = i
        end do
    end subroutine update_array

    function get_pieces_above(i) result(ipieces)
        !! get set of pieces directly above piece i
        integer(ip),intent(in) :: i
        integer(ip),dimension(:),allocatable :: ipieces
        integer(ip) :: x,y,z !! counter
        allocate(ipieces(0))
        do x = istart_array(i,1), iend_array(i,1)
            do y = istart_array(i,2), iend_array(i,2)
                do z = istart_array(i,3), iend_array(i,3)
                    if (z+1<=ubound(array,3)) then
                        if (array(x,y,z+1)/=0) ipieces = [ipieces,array(x,y,z+1)] ! above this one
                    end if
                end do
            end do
        end do
        ipieces = unique(pack(ipieces, ipieces/=i)) ! exclude the ones in this piece
    end function get_pieces_above

    function get_pieces_below(i) result(ipieces)
        !! get set of pieces directly below piece i
        integer(ip),intent(in) :: i
        integer(ip),dimension(:),allocatable :: ipieces
        integer(ip) :: x,y,z !! counter
        allocate(ipieces(0))
        do x = istart_array(i,1), iend_array(i,1)
            do y = istart_array(i,2), iend_array(i,2)
                do z = istart_array(i,3), iend_array(i,3)
                    if (z-1>0) then ! 0 is the floor
                        if (array(x,y,z-1)/=0) ipieces = [ipieces,array(x,y,z-1)] ! below this one
                    end if
                end do
            end do
        end do
        ipieces = unique(pack(ipieces, ipieces/=i)) ! exclude the ones in this piece
    end function get_pieces_below

    subroutine move_piece_down(i, moved)
        !! move a piece down (fall one square) if it can be moved
        integer(ip),intent(in) :: i !! piece number
        logical,intent(out) :: moved !! if it was actually movec
        moved = .false.
        if (size(get_pieces_below(i))==0) then ! can only move if nothing below
            if (istart_array(i,3)>1 .and. iend_array(i,3)>1) then
                istart_array(i,3) = istart_array(i,3) - 1
                iend_array(i,3)   = iend_array(i,3)   - 1
                moved = .true.
                call update_array()
            end if
        end if
    end subroutine move_piece_down

end program problem_22