diff --git a/doc/specs/stdlib_sorting.md b/doc/specs/stdlib_sorting.md index 3a44d84f8..a58e0c0a9 100644 --- a/doc/specs/stdlib_sorting.md +++ b/doc/specs/stdlib_sorting.md @@ -46,6 +46,9 @@ data: * `ORD_SORT` is intended to sort simple arrays of intrinsic data that have significant sections that were partially ordered before the sort; +* `SORT_ADJ` is based on `ORD_SORT`, but in addition to sorting the + input array, it returns a related array re-ordered in the + same way; * `SORT_INDEX` is based on `ORD_SORT`, but in addition to sorting the input array, it returns indices that map the original array to its sorted version. This enables related arrays to be re-ordered in the @@ -60,10 +63,10 @@ data: The Fortran Standard Library is distributed under the MIT License. However components of the library may be based on code with additional licensing restrictions. In particular `ORD_SORT`, -`SORT_INDEX`, and `SORT` are translations of codes with their +`SORT_ADJ`, `SORT_INDEX`, and `SORT` are translations of codes with their own distribution restrictions. -The `ORD_SORT` and `SORT_INDEX` subroutines are essentially +The `ORD_SORT`, `SORT_ADJ` and `SORT_INDEX` subroutines are essentially translations to Fortran 2008 of the `"Rust" sort` of the Rust Language distributed as part of [`slice.rs`](https://github.com/rust-lang/rust/blob/90eb44a5897c39e3dff9c7e48e3973671dcd9496/src/liballoc/slice.rs). @@ -140,6 +143,24 @@ argument or allocated internally on the stack. Arrays can be also sorted in a decreasing order by providing the argument `reverse = .true.`. +#### The `SORT_ADJ` subroutine + +The `SORT` and `ORD_SORT` subroutines can sort rank 1 isolated +arrays of intrinsic types, but do nothing for the coordinated sorting +of related data, e.g., a related rank 1 array. Therefore the module +provides a subroutine, `SORT_ADJ`, that re-order such a rank 1 array +in the same way as the input array based on the `ORD_SORT` algorithm, +in addition to sorting the input array. + +The logic of `SORT_ADJ` parallels that of `ORD_SORT`, with +additional housekeeping to keep the associated array consistent with +the sorted positions of the input array. Because of this additional +housekeeping it has slower runtime performance than `ORD_SORT`. +`SORT_ADJ` requires the use of two "scratch" arrays, that may be +provided as optional `work` and `iwork` arguments or allocated +internally on the stack. + + #### The `SORT_INDEX` subroutine The `SORT` and `ORD_SORT` subroutines can sort rank 1 isolated @@ -198,7 +219,7 @@ factor of six. Still, even when it shows enhanced performance, its performance on partially sorted data is typically an order of magnitude slower than `ORD_SORT`. Its memory requirements are also low, being of order O(Ln(N)), while the memory requirements of -`ORD_SORT` and `SORT_INDEX` are of order O(N). +`ORD_SORT`, `SORT_ADJ` and `SORT_INDEX` are of order O(N). #### The `RADIX_SORT` subroutine @@ -385,6 +406,84 @@ element of `array` is a `NaN`. {!example/sorting/example_radix_sort.f90!} ``` +#### `sort_adj` - sorts an associated array in the same way as the input array, while also sorting the array. + +##### Status + +Experimental + +##### Description + +Returns the input `array` sorted in the direction requested while +retaining order stability, and an associated array whose elements are +sorted in the same way as the input `array`. + +##### Syntax + +`call ` [[stdlib_sorting(module):sort_adj(interface)]] `( array, index[, work, iwork, reverse ] )` + +##### Class + +Generic subroutine. + +##### Arguments + +`array`: shall be a rank one array of any of the types: +`integer(int8)`, `integer(int16)`, `integer(int32)`, `integer(int64)`, +`real(sp)`, `real(dp)`, `real(qp)`, `character(*)`, `type(string_type)`, +`type(bitset_64)`, or `type(bitset_large)`. +It is an `intent(inout)` argument. On input it +will be an array whose sorting indices are to be determined. On return +it will be the sorted array. + +`index`: shall be a rank one `integer` or `real` array of +the size of `array`. It is an `intent(inout)` argument. On return it +shall have values that are the indices needed to sort the original +array in the desired direction. + +`work` (optional): shall be a rank one array of any of the same type as +`array`, and shall have at least `size(array)/2` elements. It is an +`intent(out)` argument. It is intended to be used as "scratch" +memory for internal record keeping. If associated with an array in +static storage, its use can significantly reduce the stack memory +requirements for the code. Its contents on return are undefined. + +`iwork` (optional): shall be a rank one integer array of the same kind +of the array `index`, and shall have at least `size(array)/2` elements. It +is an `intent(out)` argument. It is intended to be used as "scratch" +memory for internal record keeping. If associated with an array in +static storage, its use can significantly reduce the stack memory +requirements for the code. Its contents on return are undefined. + +`reverse` (optional): shall be a scalar of type default logical. It +is an `intent(in)` argument. If present with a value of `.true.` then +`array` will be sorted in order of non-increasing values in stable +order. Otherwise `array` will be sorted in order of non-decreasing +values in stable order. + +##### Notes + +`SORT_ADJ` implements the hybrid sorting algorithm of `ORD_SORT`, +keeping the values of `index` consistent with the elements of `array` +as it is sorted. As a `merge sort` based algorithm, it is a stable +sorting comparison algorithm. The optional `work` and `iwork` arrays +replace "scratch" memory that would otherwise be allocated on the +stack. If `array` is of any kind of `REAL` the order of the elements in +`index` and `array` on return are undefined if any element of `array` +is a `NaN`. Sorting of `CHARACTER(*)` and `STRING_TYPE` arrays are +based on the operator `>`, and not on the function `LGT`. + +It should be emphasized that the order of `array` will typically be +different on return + +##### Examples + +Sorting a rank one array with `sort_adj`: + +```Fortran +{!example/sorting/example_sort_adj.f90!} +``` + #### `sort_index` - creates an array of sorting indices for an input array, while also sorting the array. ##### Status diff --git a/example/sorting/CMakeLists.txt b/example/sorting/CMakeLists.txt index 4628ce20c..46cd530f9 100644 --- a/example/sorting/CMakeLists.txt +++ b/example/sorting/CMakeLists.txt @@ -1,5 +1,6 @@ ADD_EXAMPLE(ord_sort) ADD_EXAMPLE(sort) +ADD_EXAMPLE(sort_adj) ADD_EXAMPLE(sort_index) ADD_EXAMPLE(radix_sort) ADD_EXAMPLE(sort_bitset) diff --git a/example/sorting/example_sort_adj.f90 b/example/sorting/example_sort_adj.f90 new file mode 100644 index 000000000..20b3c004c --- /dev/null +++ b/example/sorting/example_sort_adj.f90 @@ -0,0 +1,15 @@ +program example_sort_adj + use stdlib_sorting, only: sort_adj + implicit none + integer, allocatable :: array(:) + real, allocatable :: adj(:) + + array = [5, 4, 3, 1, 10, 4, 9] + allocate(adj, source=real(array)) + + call sort_adj(array, adj) + + print *, array !print [1, 3, 4, 4, 5, 9, 10] + print *, adj !print [1., 3., 4., 4., 5., 9., 10.] + +end program example_sort_adj diff --git a/src/stdlib_sorting.fypp b/src/stdlib_sorting.fypp index fd2d4203f..7f7478128 100644 --- a/src/stdlib_sorting.fypp +++ b/src/stdlib_sorting.fypp @@ -295,6 +295,76 @@ module stdlib_sorting !! ! Sort the random data !! call radix_sort( array ) !! ... +!!``` + + public sort_adj +!! Version: experimental +!! +!! The generic subroutine implementing the `SORT_ADJ` algorithm to +!! return an index array whose elements are sorted in the same order +!! as the input array in the +!! desired direction. It is primarily intended to be used to sort a +!! rank 1 `integer` or `real` array based on the values of a component of the array. +!! Its use has the syntax: +!! +!! call sort_adj( array, index[, work, iwork, reverse ] ) +!! +!! with the arguments: +!! +!! * array: the rank 1 array to be sorted. It is an `intent(inout)` +!! argument of any of the types `integer(int8)`, `integer(int16)`, +!! `integer(int32)`, `integer(int64)`, `real(real32)`, `real(real64)`, +!! `real(real128)`, `character(*)`, `type(string_type)`, +!! `type(bitset_64)`, `type(bitset_large)`. If both the +!! type of `array` is real and at least one of the elements is a `NaN`, +!! then the ordering of the `array` and `index` results is undefined. +!! Otherwise it is defined to be as specified by reverse. +!! +!! * index: a rank 1 `integer` or `real` array. It is an `intent(inout)` +!! argument of the type `integer(int_index)`. Its size shall be the +!! same as `array`. On return, its elements are sorted in the same order +!! as the input `array` in the direction specified by `reverse`. +!! +!! * work (optional): shall be a rank 1 array of the same type as +!! `array`, and shall have at least `size(array)/2` elements. It is an +!! `intent(out)` argument to be used as "scratch" memory +!! for internal record keeping. If associated with an array in static +!! storage, its use can significantly reduce the stack memory requirements +!! for the code. Its value on return is undefined. +!! +!! * iwork (optional): shall be a rank 1 integer array of the same type as `index`, +!! and shall have at least `size(array)/2` elements. It is an +!! `intent(out)` argument to be used as "scratch" memory +!! for internal record keeping. If associated with an array in static +!! storage, its use can significantly reduce the stack memory requirements +!! for the code. Its value on return is undefined. +!! +!! * `reverse` (optional): shall be a scalar of type default logical. It +!! is an `intent(in)` argument. If present with a value of `.true.` then +!! `array` will be sorted in order of non-increasing values in stable +!! order. Otherwise `array` will be sorted in order of non-decreasing +!! values in stable order. +!! +!!#### Examples +!! +!! Sorting a related rank one array: +!! +!!```Fortran +!!program example_sort_adj +!! use stdlib_sorting, only: sort_adj +!! implicit none +!! integer, allocatable :: array(:) +!! real, allocatable :: adj(:) +!! +!! array = [5, 4, 3, 1, 10, 4, 9] +!! allocate(adj, source=real(array)) +!! +!! call sort_adj(array, adj) +!! +!! print *, array !print [1, 3, 4, 4, 5, 9, 10] +!! print *, adj !print [1., 3., 4., 4., 5., 9., 10.] +!! +!!end program example_sort_adj !!``` public sort_index diff --git a/src/stdlib_sorting_ord_sort.fypp b/src/stdlib_sorting_ord_sort.fypp index b96ea295a..c77e1c797 100644 --- a/src/stdlib_sorting_ord_sort.fypp +++ b/src/stdlib_sorting_ord_sort.fypp @@ -117,6 +117,8 @@ contains integer :: stat array_size = size( array, kind=int_index ) + +! If necessary allocate buffers to serve as scratch memory. if ( present(work) ) then if ( size(work, kind=int_index) < array_size/2 ) then error stop "${name1}$_${sname}$_ord_sort: work array is too small." diff --git a/src/stdlib_sorting_sort_adj.fypp b/src/stdlib_sorting_sort_adj.fypp index e397b32f8..e5afc01e0 100644 --- a/src/stdlib_sorting_sort_adj.fypp +++ b/src/stdlib_sorting_sort_adj.fypp @@ -42,7 +42,7 @@ !! TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE !! SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. !! -!! The generic subroutine, `SORT_INDEX`, is substantially a translation to +!! The generic subroutine, `SORT_ADJ`, is substantially a translation to !! Fortran 2008 of the `"Rust" sort` sorting routines in !! [`slice.rs`](https://github.com/rust-lang/rust/blob/90eb44a5897c39e3dff9c7e48e3973671dcd9496/src/liballoc/slice.rs) !! The `rust sort` implementation is distributed with the header: @@ -95,7 +95,6 @@ contains ! estimation of the optimal `run size` as suggested in Tim Peters' ! original `listsort.txt`, and the optional `work` and `iwork` arrays to be ! used as scratch memory. - ${t1}$, intent(inout) :: array(0:) ${ti}$, intent(inout) :: index(0:) ${t3}$, intent(out), optional :: work(0:) @@ -104,7 +103,8 @@ contains ${t2}$, allocatable :: buf(:) ${ti}$, allocatable :: ibuf(:) - integer(int_index) :: array_size, i, stat + integer(int_index) :: array_size, i + integer(int_index) :: stat array_size = size(array, kind=int_index) @@ -136,6 +136,7 @@ contains call merge_sort( array, index, work, ibuf ) end if else +! Allocate a buffer to use as scratch memory. #:if t1[0:4] == "char" allocate( ${t3}$ :: buf(0:array_size/2-1), & stat=stat ) @@ -495,3 +496,4 @@ contains #:endfor end submodule stdlib_sorting_sort_adj +