RosettaCodeData/Task/N-queens-problem/Fortran/n-queens-problem-5.f
2023-07-01 13:44:08 -04:00

280 lines
8.6 KiB
Fortran

program example__n_queens
use, intrinsic :: iso_fortran_env, only: output_unit
use, non_intrinsic :: garbage_collector
use, non_intrinsic :: cons_pairs
implicit none
! .true. is good for testing that necessary values are rooted.
! .false. to collect garbage only when the heap reaches a limit.
logical :: aggressive_garbage_collection = .true.
integer :: arg_count
integer :: stat
character(80) :: arg
type(gcroot_t) :: board_sizes
arg_count = command_argument_count ()
if (arg_count < 1) then
call print_usage (output_unit)
else
board_sizes = nil
block
integer :: i
integer :: board_size
do i = 1, arg_count
call get_command_argument (i, arg)
read (arg, *, iostat = stat) board_size
if (stat /= 0 .or. board_size < 1) then
board_size = -1
end if
board_sizes = cons (board_size, board_sizes)
end do
board_sizes = reversex (board_sizes)
end block
if (is_member (int_eq, -1, board_sizes)) then
call print_usage (output_unit)
else
! Use pair_for_each as a way to distinguish the last
! BOARD_SIZE from the others. The last entry will be the final
! pair, and so its CDR will *not* be a pair.
call pair_for_each (find_and_print_all_solutions, &
& circular_list (output_unit), &
& board_sizes)
end if
end if
contains
subroutine print_usage (outp)
integer, intent(in) :: outp
write (outp, '("Usage: example__n_queens BOARD_SIZE [BOARD_SIZE...]")')
write (outp, '("Each BOARD_SIZE must be at least 1.")')
write (outp, '("For each BOARD_SIZE, all solutions are computed before any is printed.")')
end subroutine print_usage
subroutine find_and_print_all_solutions (outp_pair, board_sizes)
class(*), intent(in) :: outp_pair
class(*), intent(in) :: board_sizes
integer :: n_outp
type(gcroot_t) :: all_solutions
n_outp = int_cast (car (outp_pair))
all_solutions = find_all_solutions (car (board_sizes))
call check_garbage
call print_all_solutions (n_outp, car (board_sizes), all_solutions)
call check_garbage
if (is_pair (cdr (board_sizes))) then
! Space between one BOARD_SIZE and another.
write (n_outp, '()')
end if
end subroutine find_and_print_all_solutions
function find_all_solutions (board_size) result (all_solutions)
class(*), intent(in) :: board_size
type(cons_t) :: all_solutions
class(*), allocatable :: solutions
call find_solutions_from_ranks_so_far (board_size, nil, solutions)
all_solutions = solutions
end function find_all_solutions
recursive subroutine find_solutions_from_ranks_so_far (board_size, ranks_so_far, solutions)
class(*), intent(in) :: board_size
class(*), intent(in) :: ranks_so_far
class(*), allocatable, intent(out) :: solutions
type(cons_t) :: ranks
if (length (ranks_so_far) == int_cast (board_size)) then
solutions = list (ranks_so_far)
else
ranks = find_legal_ranks_for_file (int_cast (board_size), ranks_so_far)
solutions = concatenatex (map (find_solutions_from_ranks_so_far, &
& circular_list (board_size), &
& map (kons, ranks, circular_list (ranks_so_far))))
end if
end subroutine find_solutions_from_ranks_so_far
function find_legal_ranks_for_file (board_size, ranks_so_far) result (ranks)
!
! Return a list of all the ranks in the next file, under the
! constraint that a queen placed in the position not be under
! attack.
!
integer, intent(in) :: board_size
class(*), intent(in) :: ranks_so_far
type(cons_t) :: ranks
ranks = iota (board_size, 1) ! All the possible ranks.
ranks = remove_illegal_ranks (ranks, ranks_so_far)
end function find_legal_ranks_for_file
function remove_illegal_ranks (new_ranks, ranks_so_far) result (legal_ranks)
class(*), intent(in) :: new_ranks
class(*), intent(in) :: ranks_so_far
type(cons_t) :: legal_ranks
legal_ranks = filter_map (keep_legal_rank, new_ranks, &
& circular_list (ranks_so_far))
end function remove_illegal_ranks
subroutine keep_legal_rank (rank, ranks_so_far, retval)
class(*), intent(in) :: rank
class(*), intent(in) :: ranks_so_far
class(*), allocatable, intent(out) :: retval
if (rank_is_legal (rank, ranks_so_far)) then
retval = rank
else
retval = .false.
end if
end subroutine keep_legal_rank
function rank_is_legal (new_rank, ranks_so_far) result (bool)
class(*), intent(in) :: new_rank
class(*), intent(in) :: ranks_so_far
logical :: bool
integer :: new_file
type(cons_t) :: files_so_far
new_file = int (length (ranks_so_far)) + 1
files_so_far = iota (new_file - 1, new_file - 1, -1)
bool = every (these_two_queens_are_nonattacking, &
& circular_list (new_file), &
& circular_list (new_rank), &
& files_so_far, &
& ranks_so_far)
end function rank_is_legal
function these_two_queens_are_nonattacking (file1, rank1, file2, rank2) result (bool)
class(*), intent(in) :: file1, rank1
class(*), intent(in) :: file2, rank2
logical :: bool
integer :: f1, r1
integer :: f2, r2
! The rank and the two diagonals must not be the same. (The files
! are known to be different.)
f1 = int_cast (file1)
r1 = int_cast (rank1)
f2 = int_cast (file2)
r2 = int_cast (rank2)
bool = (r1 /= r2 .and. r1 + f1 /= r2 + f2 .and. r1 - f1 /= r2 - f2)
end function these_two_queens_are_nonattacking
subroutine print_all_solutions (outp, board_size, all_solutions)
class(*), intent(in) :: outp
class(*), intent(in) :: board_size
class(*), intent(in) :: all_solutions
integer(size_kind) :: n
n = length (all_solutions)
write (int_cast (outp), '("For a board ", I0, " by ", I0, ", ")', advance = 'no') &
& int_cast (board_size), int_cast (board_size)
if (n == 1) then
write (int_cast (outp), '("there is ", I0, " solution.")') n
else
write (int_cast (outp), '("there are ", I0, " solutions.")') n
end if
call for_each (print_spaced_solution, circular_list (outp), &
& circular_list (board_size), all_solutions)
end subroutine print_all_solutions
subroutine print_spaced_solution (outp, board_size, solution)
class(*), intent(in) :: outp
class(*), intent(in) :: board_size
class(*), intent(in) :: solution
write (int_cast (outp), '()', advance = 'yes')
call print_solution (outp, board_size, solution)
end subroutine print_spaced_solution
subroutine print_solution (outp, board_size, solution)
class(*), intent(in) :: outp
class(*), intent(in) :: board_size
class(*), intent(in) :: solution
integer :: n_outp
integer :: n_board_size
integer :: rank
integer :: file
integer :: file_of_queen
n_outp = int_cast (outp)
n_board_size = int_cast (board_size)
do rank = n_board_size, 1, -1
do file = 1, n_board_size
write (n_outp, '("----")', advance = 'no')
end do
write (n_outp, '("-")', advance = 'yes')
file_of_queen = n_board_size - int (list_index0 (int_eq, circular_list (rank), solution))
do file = 1, n_board_size
if (file == file_of_queen) then
write (n_outp, '("| Q ")', advance = 'no')
else
write (n_outp, '("| ")', advance = 'no')
end if
end do
write (n_outp, '("|")', advance = 'yes')
end do
do file = 1, n_board_size
write (n_outp, '("----")', advance = 'no')
end do
write (n_outp, '("-")', advance = 'yes')
end subroutine print_solution
subroutine kons (x, y, xy)
class(*), intent(in) :: x
class(*), intent(in) :: y
class(*), allocatable, intent(out) :: xy
xy = cons (x, y)
end subroutine kons
pure function int_cast (x) result (val)
class(*), intent(in) :: x
integer :: val
select type (x)
type is (integer)
val = x
class default
error stop
end select
end function int_cast
pure function int_eq (x, y) result (bool)
class(*), intent(in) :: x
class(*), intent(in) :: y
logical :: bool
bool = (int_cast (x) == int_cast (y))
end function int_eq
subroutine check_garbage
if (aggressive_garbage_collection) then
call collect_garbage_now
else
call check_heap_size
end if
end subroutine check_garbage
end program example__n_queens