! sortmod.f90
! module procedure written by Tim Mitchell on 11.7.00 and thereafter
! QuickSort etc
! BubbleSort, MedianValue, RankVector are in old versions

module SortMod

implicit none

contains

!*******************************************************************************
! quick sort text in ascending order - call this procedure
! call with Text, returns in Position the alphabetical order of the indices of Text

subroutine QuickSortText (Text,Position,Restricted)

real, pointer, dimension (:) 			:: SubSet
real, allocatable, dimension (:,:) 		:: AsciiCode

integer, pointer, dimension (:) 		:: Position
integer, allocatable, dimension (:) 		:: Order

character (len=80), pointer, dimension (:) 	:: Text

integer, intent(in), optional 			:: Restricted	! contains the max char of Text to consider

real, parameter :: MissVal = -999.0

integer :: AllocStat
integer :: XDatum, XChar, XSubSet, XPlace
integer :: DataN,  CharN, SubSetN
integer :: MaxPlace, CurrentAsciiCode,CurrentDatum,CurrentPlace, LastAsciiCode,PlacesToAdd

character (len=80) :: DatumText

DataN = size (Text,1)

if (present(Restricted)) then
  CharN = Restricted
else
  CharN = 80
end if

allocate (AsciiCode  (DataN,CharN), &
	  Order      (DataN),       &
	  Position   (DataN),       stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSortText: Allocation failure: most #####"
Order = 1

do XDatum = 1, DataN  
  DatumText = trim(adjustl(Text(XDatum)))
  do XChar = 1, CharN
    AsciiCode (XDatum,XChar) = real(iachar(DatumText(XChar:XChar))) + (real(XDatum)/10000.0)
  end do
end do

do XChar = 1, CharN
 do XPlace = DataN, 1, -1
  SubSetN = count (Order.EQ.XPlace)
  
  if (SubSetN.GT.1) then  
   allocate (SubSet (SubSetN),stat=AllocStat)
   if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSortText: Allocation failure: SubSet #####"

   XSubSet = 0
   do XDatum = 1, DataN
     if (Order(XDatum).EQ.XPlace) then
    	XSubSet=XSubSet+1 ; SubSet(XSubSet) = AsciiCode(XDatum,XChar)
     end if
   end do
   
   call REFSOR (SubSet)
   
   CurrentPlace = XPlace-1 ; LastAsciiCode = -1 ; PlacesToAdd = 1
   do XSubSet = 1, SubSetN
     CurrentAsciiCode = floor (SubSet(XSubSet))
     CurrentDatum     = nint ((SubSet(XSubSet)-real(CurrentAsciiCode))*10000.0)
     
     if (CurrentAsciiCode.GT.LastAsciiCode) then
     	CurrentPlace  = CurrentPlace + PlacesToAdd
     	PlacesToAdd   = 1
     	LastAsciiCode = CurrentAsciiCode
     else
        PlacesToAdd  = PlacesToAdd + 1
     end if
     
     Order(CurrentDatum) = CurrentPlace
   end do
     
   deallocate (SubSet,stat=AllocStat)
   if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSortText: Deallocation failure: Column #####"
  end if
 end do
end do

do XDatum = 1, DataN  
  Position(Order(XDatum)) = XDatum
end do

deallocate (AsciiCode,Order,stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSortText: Deallocation failure: AsciiCode #####"

end subroutine QuickSortText 

!*******************************************************************************
! quick sort in ascending order - call this procedure
! if Order or OrderValid is set, Reals/Ints are left unchanged
! the difference between Order and OrderValid is that
!	Order=intarr(DataN) and is declared here and passed to calling program
!	OrderValid=intarr(at least ValidN) and is already declared

subroutine QuickSort (Reals,Ints,Order,OrderValid,NMiss)

logical, pointer, dimension (:)			:: Found
real, pointer, dimension (:)			:: Data,ValidData
real, pointer, dimension (:), optional 		:: Reals
integer, pointer, dimension (:), optional 	:: Order,OrderValid,Ints

integer, intent (out), optional		:: NMiss

real, parameter :: MissVal = -999.0
integer :: XDatum, XValid, ValidN, XSort, AllocStat, DataN, MissN

if (present(Reals)) then
  DataN = size(Reals)
  allocate (Data (DataN), stat=AllocStat)
  if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Allocation failure: Data #####"
  do XDatum = 1, DataN
    Data(XDatum) = Reals(XDatum)
  end do
else if (present(Ints)) then
  DataN = size(Ints)
  allocate (Data (DataN), stat=AllocStat)
  if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Allocation failure: Data #####"
  do XDatum = 1, DataN
    Data(XDatum) = real(Ints(XDatum))
  end do
else
  print*, "  > ##### ERROR: QuickSort: no data supplied! #####"
end if

MissN = 0
do XDatum = 1, DataN
  if (Data(XDatum).EQ.MissVal) MissN = MissN + 1
end do
ValidN = DataN - MissN
if (present(NMiss)) NMiss = MissN

allocate (ValidData (ValidN), stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Allocation failure #####"

XValid = 0
do XDatum = 1, DataN
  if (Data(XDatum).NE.MissVal) then
  	XValid = XValid + 1
	ValidData(XValid) = Data(XDatum)
  end if
end do

if (ValidN.GT.1) call REFSOR (ValidData)

if (present(Order).OR.present(OrderValid)) then
 allocate (Found(DataN), stat=AllocStat)
 if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Allocation failure #####"
 Found=.FALSE.
 
 if (present(Order)) then
   allocate (Order(DataN), stat=AllocStat)
   if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Allocation failure #####"
   Order = MissVal

   do XSort = 1, ValidN
    XDatum = 0
    do
      XDatum = XDatum + 1
      if (ValidData(XSort).EQ.Data(XDatum).AND.(.NOT.Found(XDatum))) then
        Found(XDatum) = .TRUE.
        Order(XSort)  = XDatum
      end if
      if (Order(XSort).NE.MissVal) exit
    end do
   end do
 else
   OrderValid = nint(MissVal)
   if (size(OrderValid).LT.ValidN) print*, "  > ##### ERROR: QuickSort: OrderValid #####"
   
   do XSort = 1, ValidN
    XDatum = 0
    do
      XDatum = XDatum + 1
      if (ValidData(XSort).EQ.Data(XDatum).AND.(.NOT.Found(XDatum))) then
        Found(XDatum) = .TRUE.
        OrderValid(XSort) = XDatum
      end if
      if (OrderValid(XSort).NE.nint(MissVal)) exit
    end do
   end do
 end if
 
 deallocate (Found, stat=AllocStat)
 if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Dealloc: Found #####"
else
 if (present(Reals)) then 
   do XDatum = 1, ValidN
     Reals (XDatum) = ValidData (XDatum)
   end do
   do XDatum = (ValidN+1), DataN
     Reals (XDatum) = MissVal
   end do
 else if (present(Ints)) then
   do XDatum = 1, ValidN
     Ints (XDatum) = ValidData (XDatum)
   end do
   do XDatum = (ValidN+1), DataN
     Ints (XDatum) = MissVal
   end do
 end if
end if

deallocate (ValidData,Data, stat=AllocStat)
if (AllocStat.NE.0) print*, "  > ##### ERROR: QuickSort: Deallocation failure #####"

end subroutine QuickSort

!*******************************************************************************
! quick sort in ascending order - the actual procedure - do not call directly

Subroutine REFSOR (XDONT)
!  Sorts XDONT into ascending order - Quicksort
! __________________________________________________________
!  Quicksort chooses a "pivot" in the set, and explores the
!  array from both ends, looking for a value > pivot with the
!  increasing index, for a value <= pivot with the decreasing
!  index, and swapping them when it has found one of each. 
!  The array is then subdivided in 2 ([3]) subsets:
!  { values <= pivot} {pivot} {values > pivot}
!  One then call recursively the program to sort each subset.
!  When the size of the subarray is small enough, one uses an
!  insertion sort that is faster for very small sets.
!  Michel Olagnon - Apr. 2000
! __________________________________________________________
!
!      Real, Dimension (:), Intent (InOut) :: XDONT
!  modified by Tim Mitchell, 11.7.00
      real, dimension (:), pointer :: XDONT
! __________________________________________________________
      Integer :: IDEB, IFIN
!
      IDEB = 1
      IFIN = Size (XDONT)
      Call l_subsor (IDEB, IFIN)
      Call l_inssor
      Return
Contains      
   Recursive Subroutine l_subsor (IDEB1, IFIN1)
!  Sorts XDONT from IDEB1 to IFIN1
! __________________________________________________________
      Integer, Intent (In) :: IDEB1, IFIN1
! __________________________________________________________
      Integer, Parameter :: NINS = 16 ! Max for insertion sort 
      Integer :: ICRS, IDEB, IDCR, IFIN, IMIL
      Real (Kind(XDONT)) :: XPIV, XWRK
!
      IDEB = IDEB1
      IFIN = IFIN1
!
!  If we don't have enough values to make it worth while, we leave
!  them unsorted, and the final insertion sort will take care of them
!
      If ((IFIN - IDEB) > NINS) Then
         IMIL = (IDEB+IFIN) / 2
!
!  One chooses a pivot, median of 1st, last, and middle values
!
         If (XDONT(IMIL) < XDONT(IDEB)) Then
            XWRK = XDONT (IDEB)
            XDONT (IDEB) = XDONT (IMIL)
            XDONT (IMIL) = XWRK
         End If
         If (XDONT(IMIL) > XDONT(IFIN)) Then
            XWRK = XDONT (IFIN)
            XDONT (IFIN) = XDONT (IMIL)
            XDONT (IMIL) = XWRK
            If (XDONT(IMIL) < XDONT(IDEB)) Then
               XWRK = XDONT (IDEB)
               XDONT (IDEB) = XDONT (IMIL)
               XDONT (IMIL) = XWRK
            End If
         End If
         XPIV = XDONT (IMIL)
!
!  One exchanges values to put those > pivot in the end and
!  those <= pivot at the beginning
!
         ICRS = IDEB
         IDCR = IFIN
         ECH2: Do
            Do
               ICRS = ICRS + 1
               If (ICRS >= IDCR) Then
!
!  the first  >  pivot is IDCR
!  the last   <= pivot is ICRS-1
!  Note: If one arrives here on the first iteration, then
!        the pivot is the maximum of the set, the last value is equal
!        to it, and one can reduce by one the size of the set to process,
!        as if XDONT (IFIN) > XPIV
!
                  Exit ECH2
!
               End If
               If (XDONT(ICRS) > XPIV) Exit
            End Do
            Do
               If (XDONT(IDCR) <= XPIV) Exit
               IDCR = IDCR - 1
               If (ICRS >= IDCR) Then
!
!  The last value < pivot is always ICRS-1
!
                  Exit ECH2
               End If
            End Do
!
            XWRK = XDONT (IDCR)
            XDONT (IDCR) = XDONT (ICRS)
            XDONT (ICRS) = XWRK
         End Do ECH2
!
!  One now sorts each of the two sub-intervals
!
         Call l_subsor (IDEB1, ICRS-1)
         Call l_subsor (IDCR, IFIN1)
      End If
      Return
   End Subroutine l_subsor
   Subroutine l_inssor
!  Sorts XDONT into increasing order (Insertion sort)
!  This subroutine uses insertion sort. It does not use any
!  work array and is faster when XDONT is of very small size
!  (< 20), or already almost sorted, so it is used in a final
!  pass when the partial quicksorting has left a sequence
!  of small subsets and that sorting is only necessary within
!  each subset to complete the process.
!  Michel Olagnon - Apr. 2000
! __________________________________________________________
      Integer :: ICRS, IDCR
      Real (Kind(XDONT)) :: XWRK
!
      Do ICRS = 2, Size (XDONT)
         XWRK = XDONT (ICRS)
         If (XWRK >= XDONT(ICRS-1)) Cycle
         XDONT (ICRS) = XDONT (ICRS-1)
         Do IDCR = ICRS - 2, 1, - 1
            If (XWRK >= XDONT(IDCR)) Exit
            XDONT (IDCR+1) = XDONT (IDCR)
         End Do
         XDONT (IDCR+1) = XWRK
      End Do
!
      Return
!
   End Subroutine l_inssor
End Subroutine REFSOR

!*******************************************************************************

end module SortMod
