Closest-pair problem/Fortran: Difference between revisions

From Rosetta Code
Content added Content deleted
m (Fixed syntax highlighting.)
 
Line 5: Line 5:
This module defines the point type and implements only two operations on "vectors" ("difference" and "length")
This module defines the point type and implements only two operations on "vectors" ("difference" and "length")


<lang fortran>module Points_Module
<syntaxhighlight lang="fortran">module Points_Module
implicit none
implicit none


Line 39: Line 39:
end function pt_len
end function pt_len


end module Points_Module</lang>
end module Points_Module</syntaxhighlight>


Then we need a modified version of the [[Quicksort#Fortran|fortran quicksort]] able to handle "points" and that can use a custom comparator.
Then we need a modified version of the [[Quicksort#Fortran|fortran quicksort]] able to handle "points" and that can use a custom comparator.


<lang fortran>module qsort_module
<syntaxhighlight lang="fortran">module qsort_module
use Points_Module
use Points_Module
implicit none
implicit none
Line 109: Line 109:
end subroutine partition
end subroutine partition


end module qsort_module</lang>
end module qsort_module</syntaxhighlight>


The module containing the custom comparators.
The module containing the custom comparators.


<lang fortran>module Order_By_XY
<syntaxhighlight lang="fortran">module Order_By_XY
use Points_Module
use Points_Module
implicit none
implicit none
Line 140: Line 140:
end if
end if
end function order_by_y
end function order_by_y
end module Order_By_XY</lang>
end module Order_By_XY</syntaxhighlight>


The '''closest pair''' functions' module.
The '''closest pair''' functions' module.


<lang fortran>module ClosestPair
<syntaxhighlight lang="fortran">module ClosestPair
use Points_Module
use Points_Module
use Order_By_XY
use Order_By_XY
Line 278: Line 278:
end function closest_pair_real
end function closest_pair_real


end module ClosestPair</lang>
end module ClosestPair</syntaxhighlight>


Testing:
Testing:


<lang fortran>program TestClosestPair
<syntaxhighlight lang="fortran">program TestClosestPair
use ClosestPair
use ClosestPair
implicit none
implicit none
Line 307: Line 307:
dr = closest_pair(points, p)
dr = closest_pair(points, p)
print *, "rec ", dr
print *, "rec ", dr
end program TestClosestPair</lang>
end program TestClosestPair</syntaxhighlight>


<tt>Time</tt> gave 2.92user 0.00system 0:02.94elapsed for brute force, and 0.02user 0.00system 0:00.03elapsed for the other one.
<tt>Time</tt> gave 2.92user 0.00system 0:02.94elapsed for brute force, and 0.02user 0.00system 0:00.03elapsed for the other one.

Latest revision as of 13:55, 1 September 2022

Closest-pair problem/Fortran is part of Closest pair problem. You may find other members of Closest pair problem at Category:Closest pair problem.
Works with: Fortran version 95 and later

This module defines the point type and implements only two operations on "vectors" ("difference" and "length")

module Points_Module
  implicit none

  type point
     real :: x, y
  end type point

  interface operator (-)
     module procedure pt_sub
  end interface

  interface len
     module procedure pt_len
  end interface

  public :: point
  private :: pt_sub, pt_len

contains

  function pt_sub(a, b) result(c)
    type(point), intent(in) :: a, b
    type(point) :: c
    
    c = point(a%x - b%x, a%y - b%y)
  end function pt_sub

  function pt_len(a) result(l)
    type(point), intent(in) :: a
    real :: l

    l = sqrt((a%x)**2 + (a%y)**2)
  end function pt_len

end module Points_Module

Then we need a modified version of the fortran quicksort able to handle "points" and that can use a custom comparator.

module qsort_module
  use Points_Module
  implicit none

contains

  recursive subroutine qsort(a, comparator)
    type(point), intent(inout) :: a(:)
    interface
       integer function comparator(aa, bb)
         use Points_Module
         type(point), intent(in) :: aa, bb
       end function comparator
    end interface

    integer :: split

    if(size(a) > 1) then
       call partition(a, split, comparator)
       call qsort(a(:split-1), comparator)
       call qsort(a(split:), comparator)
    end if

  end subroutine qsort

  subroutine partition(a, marker, comparator)
    type(point), intent(inout) :: a(:)
    integer, intent(out) :: marker

    interface
       integer function comparator(aa, bb)
         use Points_Module
         type(point), intent(in) :: aa, bb
       end function comparator
    end interface

    type(point) :: pivot, temp
    integer :: left, right

    left = 0                       
    right = size(a) + 1
    pivot = a(size(a)/2)

    do while (left < right)
       right = right - 1
       do while (comparator(a(right), pivot) > 0)
          right = right - 1
       end do
       left = left + 1
       do while (comparator(a(left), pivot) < 0)
          left = left + 1
       end do
       if ( left < right ) then
          temp = a(left)
          a(left) = a(right)
          a(right) = temp
       end if
    end do

    if (left == right) then
       marker = left + 1
    else
       marker = left
    end if
  end subroutine partition

end module qsort_module

The module containing the custom comparators.

module Order_By_XY
  use Points_Module
  implicit none
contains
  integer function order_by_x(aa, bb)
    type(point), intent(in) :: aa, bb

    if ( aa%x < bb%x ) then
       order_by_x = -1
    elseif (aa%x > bb%x) then
       order_by_x = 1
    else
       order_by_x = 0
    end if
  end function order_by_x

  integer function order_by_y(aa, bb)
    type(point), intent(in) :: aa, bb

    if ( aa%y < bb%y ) then
       order_by_y = -1
    elseif (aa%y > bb%y) then
       order_by_y = 1
    else
       order_by_y = 0
    end if    
  end function order_by_y
end module Order_By_XY

The closest pair functions' module.

module ClosestPair
  use Points_Module
  use Order_By_XY
  use qsort_module
  implicit none

  private :: closest_pair_real

contains

  function closest_pair_simple(p, pair) result(distance)
    type(point), dimension(:), intent(in) :: p
    type(point), dimension(:), intent(out), optional :: pair
    real :: distance

    type(point), dimension(2) :: cp
    integer :: i, j
    real :: d

    if ( size(P) < 2 ) then
       distance = huge(0.0)
    else
       distance = len(p(1) - p(2))
       cp = (/ p(1), p(2) /)
       do i = 1, size(p) - 1
          do j = i+1, size(p)
             d = len(p(i) - p(j))
             if ( d < distance ) then
                distance = d
                cp = (/ p(i), p(j) /)
             end if
          end do
       end do
       if ( present(pair) ) pair = cp
    end if
  end function closest_pair_simple


  function closest_pair(p, pair) result(distance)
    type(point), dimension(:), intent(in) :: p
    type(point), dimension(:), intent(out), optional :: pair
    real :: distance

    type(point), dimension(2) :: dp
    type(point), dimension(size(p)) :: xp, yp
    integer :: i

    xp = p
    yp = p

    call qsort(xp, order_by_x)
    call qsort(yp, order_by_y)

    distance = closest_pair_real(xp, yp, dp)
    if ( present(pair) ) pair = dp
  end function closest_pair


  recursive function closest_pair_real(xp, yp, pair) result(distance)
    type(point), dimension(:), intent(in) :: xp, yp
    type(point), dimension(:), intent(out) :: pair
    real :: distance

    type(point), dimension(2) :: pairl, pairr
    type(point), dimension(:), allocatable :: ys
    type(point), dimension(:), allocatable :: pl, yl
    type(point), dimension(:), allocatable :: pr, yr
    real :: dl, dr, dmin, xm, d
    integer :: ns, i, k, j, midx

    if ( size(xp) <= 3 ) then
       distance = closest_pair_simple(xp, pair)
    else
       midx = ceiling(size(xp)/2.0)

       allocate(ys(size(xp)))
       allocate(pl(midx), yl(midx))
       allocate(pr(size(xp)-midx), yr(size(xp)-midx))

       pl = xp(1:midx)
       pr = xp((midx+1):size(xp))

       xm = xp(midx)%x

       k = 1; j = 1
       do i = 1, size(yp)
          if ( yp(i)%x > xm ) then
             ! guard ring; this is an "idiosyncrasy" that should be fixed in a
             ! smarter way
             if ( k <= size(yr) ) yr(k) = yp(i)
             k = k + 1
          else
             ! guard ring (see above)
             if ( j <= size(yl) ) yl(j) = yp(i)
             j = j + 1
          end if
       end do

       dl = closest_pair_real(pl, yl, pairl)
       dr = closest_pair_real(pr, yr, pairr)

       pair = pairr
       dmin = dr
       if ( dl < dr ) then
          pair = pairl
          dmin = dl
       end if

       ns = 0
       do i = 1, size(yp)
          if ( abs(yp(i)%x - xm) < dmin ) then
             ns = ns + 1
             ys(ns) = yp(i)
          end if
       end do

       distance = dmin
       do i = 1, ns-1
          k = i + 1
          do while ( k <= ns .and. abs(ys(k)%y - ys(i)%y) < dmin )
             d = len(ys(k) - ys(i))
             if ( d < distance ) then
                distance = d
                pair = (/ ys(k), ys(i) /)
             end if
             k = k + 1
          end do
       end do
       deallocate(ys)
       deallocate(pl, yl)
       deallocate(pr, yr)
    end if
  end function closest_pair_real

end module ClosestPair

Testing:

program TestClosestPair
  use ClosestPair
  implicit none

  integer, parameter :: n = 10000

  integer :: i
  real :: x, y
  type(point), dimension(n) :: points

  type(point), dimension(2) :: p
  real :: dp, dr

  ! init the random generator here if needed

  do i = 1, n
     call random_number(x)
     call random_number(y)
     points(i) = point(x*20.0-10.0, y*20.0-10.0)
  end do

  dp = closest_pair_simple(points, p)
  print *, "sim ", dp
  dr = closest_pair(points, p)
  print *, "rec ", dr
end program TestClosestPair

Time gave 2.92user 0.00system 0:02.94elapsed for brute force, and 0.02user 0.00system 0:00.03elapsed for the other one.