ssr
ssr

Reputation: 1

How can you sort rows in an array based on ascending column values?

For example, if I have the following 2D array:

2   1   4
1   2   3
2   1   2

and want to sort by each row, how I can I use the column values to have ascending order? In this case I am able to sort the first column in the array, getting

1 2 3
2 1 4
2 1 2 

With this example, my final result should be this:

1 2 3
2 1 2
2 1 4

First I want to look at the first column, then sort rows. Since two rows begin with 2, I want to look at the second column, and sort. If those are still the same number, I want to look at the next column and so on. How should I do this?

Upvotes: 0

Views: 1855

Answers (2)

user5713492
user5713492

Reputation: 974

I tried the lazy approach of writing an interface to the standard C library subroutine qsort. To avoid global data, I made the comparison function an internal function to the subroutine that invokes qsort and holds the array to be sorted. The intermediate subroutine allocates an array of indices that qsort actually sorts and then the index array is used to straighten of the input array. Here's how it works:

module sortmod
   use ISO_C_BINDING
   implicit none
   interface
      subroutine qsort(base,nitems,size,compar) bind(C,name='qsort')
         import
         implicit none
         type(C_PTR), value :: base
         integer(C_SIZE_T), value :: nitems
         integer(C_SIZE_T), value :: size
         interface
            function compar(x,y) bind(C)
               import
               implicit none
               integer(C_INT) compar
               type(C_PTR),value :: x
               type(C_PTR),value :: y
            end function compar
         end interface
      end subroutine qsort
   end interface
   contains
      recursive subroutine startsort(array)
         integer(C_INT) array(:,:)
         integer(C_INT), allocatable, target :: indices(:)
         integer i
         indices = [(i,i=1,size(array,1))]
         call qsort(C_LOC(indices),size(indices,1,kind=C_SIZE_T),C_SIZEOF(indices(1)),callback)
         array = array(indices,:)
         contains
            function callback(x,y) bind(C)
               integer(C_INT) callback
               type(C_PTR), value :: x, y
               integer(C_INT), pointer :: ix,iy
               integer j
               call C_F_POINTER(x,ix)
               call C_F_POINTER(y,iy)
               callback = 0
               do j = 1, size(array,2)
                  callback = array(ix,j) - array(iy,j)
                  if(callback /= 0) return
               end do
            end function callback
      end subroutine startsort
end module sortmod

program testsort
   use sortmod
   implicit none
   integer(C_INT), allocatable :: array(:,:)
   character(20) fmt
   array = reshape([2, 1, 4, &
                    1, 2, 3, &
                    2, 1, 2], &
                    [3, 3], order = [2, 1])
   call startsort(array)
   write(fmt,'(*(g0))') '(',size(array,2),'i3)'
   write(*,fmt) transpose(array)
end program testsort

Output with gfortran:

  1  2  3
  2  1  2
  2  1  4

Upvotes: 0

chw21
chw21

Reputation: 8140

What you need are two different procedures:

One that compares two rows with each other and decides which of these should go earlier, and another that actually does the sorting.

Here is a version using a poorly implemented bubble sort:

program sort

    implicit none
    integer, parameter :: num_rows = 3
    integer, parameter :: num_cols = 3
    character(len=*), parameter :: fmt = '(3I4)'
    integer :: a(num_cols,num_rows)

    a = reshape([2, 1, 4, 1, 2, 3, 2, 1, 2], [3, 3])

    call sortrow(a)

    print fmt, a

contains

    subroutine sortrow(a)
        implicit none
        integer, intent(inout) :: a(num_cols, num_rows)
        integer :: i, j
        integer :: tmp(num_cols)
        do i = 1, num_rows
            do j = i+1, num_rows
                if (islarger(a(:,i), a(:,j))) then
                    tmp(:) = a(:, i)
                    a(:, i) = a(:, j)
                    a(:, j) = tmp(:)
                end if
            end do
        end do
    end subroutine sortrow

    function islarger(a, b)
        implicit none
        integer, intent(in) :: a(num_cols), b(num_cols)
        logical :: islarger
        integer :: i
        do i = 1, num_cols
            if (a(i) > b(i)) then
                islarger = .TRUE.
                return
            end if
            if (b(i) > a(i)) then
                islarger = .FALSE.
                return
            end if
        end do
        islarger = .FALSE.
        return
    end function islarger

end program sort

Alternatively you could write a function that maps a row onto a single integer value in such a way that if row n has to come after m, then this value of n is larger than that of m.

For example, if all values single digits (0 through 9) then you could convert [2, 1, 4] to 214 which would be much easier to sort.

Upvotes: 1

Related Questions