stdlib_sorting_unique.fypp Source File


Source Code

#include "macros.inc"
#:include "common.fypp"
#:set INT_TYPES_ALT_NAME = list(zip(INT_TYPES, INT_TYPES, INT_KINDS, INT_CPPS))
#:set REAL_TYPES_ALT_NAME = list(zip(REAL_TYPES, REAL_TYPES, REAL_KINDS, REAL_CPPS))
#:set CHAR_TYPES_ALT_NAME = list(zip(["character(len=*)"], ["character(len=len(array))"], ["char"], [""]))
#:set STRING_TYPES_ALT_NAME = list(zip(STRING_TYPES, STRING_TYPES, STRING_KINDS, STRING_CPPS))
#:set CMPLX_TYPES_ALT_NAME = list(zip(CMPLX_TYPES, CMPLX_TYPES, CMPLX_SUFFIX, len(CMPLX_TYPES) * [""]))
#:set IRSCX_INDEX_TYPES_ALT_NAME = INT_TYPES_ALT_NAME + REAL_TYPES_ALT_NAME + CHAR_TYPES_ALT_NAME + STRING_TYPES_ALT_NAME &
    & + CMPLX_TYPES_ALT_NAME

submodule(stdlib_sorting) stdlib_sorting_unique
#if STDLIB_HASHMAPS
    use stdlib_hashmaps, only: chaining_hashmap_type
    use stdlib_hashmap_wrappers, only: key_type, set
#endif
    use stdlib_constants
    use stdlib_string_type, only: string_type, char_string => char, operator(==), operator(/=)
    implicit none

contains
    #:for t1, t2, name1, cpp1 in IRSCX_INDEX_TYPES_ALT_NAME
    module subroutine ${name1}$_unique(array, output${", sorted_output" if not t1.startswith('complex') else ""}$ &
                                        ${", tolerance" if t1.startswith('real') else ""}$)
        ${t1}$, intent(in) :: array(:)
        ${t2}$, allocatable, intent(inout) :: output(:)
        #:if not t1.startswith('complex')
        logical, optional, intent(in) :: sorted_output
        #:endif
        #:if t1.startswith('real')
        ${t1}$, optional, intent(in) :: tolerance
        #:endif

        #:if t1.startswith('real')
        ${t1}$ :: tolerance_
        #:endif
        #:if not t1.startswith('complex')
        logical :: sorted_output_
        ${t2}$, allocatable :: temp(:)
        #:endif

        if(size(array) == 0) then
            if (allocated(output)) then
                deallocate(output)
            end if
            allocate(output(0))
            return
        end if

        #:if not t1.startswith('complex')
        sorted_output_ = optval(sorted_output, .false.)
        #:endif

        #:if t1.startswith('real')
        if (.not. sorted_output_ .and. present(tolerance)) then
            error stop "tolerance requires sorted_output=.true."
        end if
        tolerance_ = optval(tolerance, 0.0_${name1}$)
        if(tolerance_ < 0.0_${name1}$) error stop "tolerance must be non-negative"
        #:endif
        #:if not t1.startswith('complex')
        if(sorted_output_) then
            allocate(temp, source=array)
            call sort_unique(temp, output${", tolerance_" if t1.startswith('real') else ""}$)
            deallocate(temp)
        else
        #:endif
#if !STDLIB_HASHMAPS
                error stop "unsorted version requires STDLIB_HASHMAPS"
#endif
        #:if name1 != 'xdp' and name1 != 'cxdp'
            call unsorted_unique(array, output)
        #:else
            error stop 'unsorted version does not support xdp kinds'
        #:endif
        #:if not t1.startswith('complex')
        end if
        #:endif
    end subroutine
    #:endfor

    #:for t1, t2, name1, cpp1 in IRSCX_INDEX_TYPES_ALT_NAME
    #:if not t1.startswith('complex')
    module subroutine ${name1}$_sort_unique(array, output${", tolerance" if t1.startswith('real') else ""}$)
        ${t1}$, intent(inout) :: array(:)
        ${t2}$, allocatable, intent(inout) :: output(:)
        #:if t1.startswith('real')
        ${t1}$, intent(in) :: tolerance
        #:endif

        logical, allocatable :: mask(:)
        integer :: i
        #:if t1.startswith('real')
        ${t1}$ :: last_unique
        #:endif

        allocate(mask(size(array)))
        mask(1) = .true.
        call sort(array)
        #:if t1.startswith('real')
        last_unique = array(1)
        #:endif
        do i = 2, size(array)
            #:if t1.startswith('real')
            mask(i) = abs(array(i)-last_unique) > tolerance
            if(mask(i)) last_unique = array(i)
            #:else
            mask(i) = array(i) /= array(i-1)
            #:endif
        end do
        if (.not. allocated(output)) then
            allocate(output(size(array)))
        else if (size(output) < size(array)) then
            deallocate(output)
            allocate(output(size(array)))
        end if
        output = pack(array, mask)
        deallocate(mask)
    end subroutine
    #:endif
    #:endfor

#if STDLIB_HASHMAPS
    #:for t1, t2, name1, cpp1 in IRSCX_INDEX_TYPES_ALT_NAME
    #:if name1 != 'xdp' and name1 != 'cxdp'
    module subroutine ${name1}$_unsorted_unique(array, output)
        ${t1}$, intent(in) :: array(:)
        ${t2}$, allocatable, intent(inout) :: output(:)

        type(chaining_hashmap_type) :: map
        logical, allocatable :: mask(:)
        logical :: present
        integer :: i
        #:if t1.startswith('char') or name1.startswith('string')
        type(key_type) :: key
        #:else
        integer(int8) :: key(storage_size(array(1))/8)
        #:endif

        call map%init()
        allocate(mask(size(array)))
        do i = 1, size(array)
            #:if t1.startswith('char')
            call set(key, array(i))
            #:elif name1.startswith('string')
            call set(key, char_string(array(i)))
            #:else
            key = transfer(array(i), key)
            #:endif
            call map%key_test(key, present)
            if (.not. present) then
                call map%map_entry(key)
                mask(i) = .true.
            else
                mask(i) = .false.
            end if
        end do
        if (.not. allocated(output)) then
            allocate(output(size(array)))
        else if (size(output) < size(array)) then
            deallocate(output)
            allocate(output(size(array)))
        end if
        output = pack(array, mask)
        deallocate(mask)
    end subroutine
    #:endif
    #:endfor
#endif

end submodule stdlib_sorting_unique