相当于 unique 的 fortran

A fortran equivalent to unique

我发现了很多解决这个问题的问题,但是 none 直接回答了这个问题:

-在 fortran 中,什么是 (a) 最快的(挂钟)和 (b) 最优雅(简洁明了)的方式来消除整数列表中的重复项

一定有比我的无力尝试更好的方法:

Program unique
 implicit none
 !   find "indices", the list of unique numbers in "list"
 integer( kind = 4 ) :: kx, list(10)
 integer( kind = 4 ),allocatable :: indices(:)
 logical :: mask(10)
 !!$    list=(/3,2,5,7,3,1,4,7,3,3/)
 list=(/1,(kx,kx=1,9)/)
 mask(1)=.true.
 do kx=10,2,-1
   mask(kx)= .not.(any(list(:kx-1)==list(kx)))
 end do
 indices=pack([(kx,kx=1,10)],mask)
 print *,indices
End Program unique

我的尝试希望列表是有序的,但如果取消该要求会更好

我就是忍不住,所以我写了一个你可能会喜欢的答案。以下代码将 return 一个由未排序整数输入数组 升序排列的唯一值数组。请注意,输出结果是实际值,而不仅仅是索引。

program unique_sort
    implicit none
    integer :: i = 0, min_val, max_val
    integer, dimension(10) :: val, unique
    integer, dimension(:), allocatable :: final

    val = [ 3,2,5,7,3,1,4,7,3,3 ]
    min_val = minval(val)-1
    max_val = maxval(val)
    do while (min_val<max_val)
        i = i+1
        min_val = minval(val, mask=val>min_val)
        unique(i) = min_val
    enddo
    allocate(final(i), source=unique(1:i))   !<-- Or, just use unique(1:i) 
    print "(10i5:)", final
end program unique_sort
! output:    1    2    3    4    5    7

参见 this gist for timing comparisons between (unique_sort) above, your example (unique_indices), and the example at Rosetta Code (remove_dups) 以及一些变体。我想测试@High Performance Mark 的代码,但还没有。

Run program 1,000,000 times, 100 integers 0<=N<=50
- unique_sort     t~2.1 sec  input: unsorted, w/duplicates  output: sorted unique values
- remove_dup      t~1.4      input: unsorted, w/duplicates  output: unsorted unique values
- unique_indices  t~1.0      input: sorted, w/duplicates    output: unsorted indices for unique values
- BONUS!(Python)  t~4.1      input: unsorted, w/duplicates  output: sorted unique values

底线:在我的机器上(i7 8GB 笔记本电脑)unique_indicesremove_dups 稍快。但是,remove_dups 不需要对输入数组进行预排序,实际上 return 是值而不是索引(请参阅 unique_indices 的修改版本的要点 returns 而不是值,这似乎根本不会减慢它的速度)。

另一方面,unique_sort 需要大约两倍的时间,但设计用于处理未排序的输入,并且 returns 按排序顺序排列的值,在 8 LOC 中(减去 var声明)。所以这似乎是一个公平的权衡。任何人,我确信 unique_sort 可以使用某种掩码语句进行优化以提高速度,但那是另一天的事了。


更新 上面显示的时序是从一个测试程序中获得的,其中每个子例程都放在一个模块中并通过过程调用执行。然而,当 unique_sort 直接放在主程序中时,我发现性能有了惊人的巨大改进,运行 100 万次仅需约 0.08 秒。仅仅通过不使用过程来加速约 25 倍对我来说似乎很奇怪 - 通常,我假设编译器优化了过程调用的成本。例如,我发现 remove_dupunique_indices 的性能没有差异,无论它们是通过过程执行还是直接放在主程序中。

在@VladimirF 指出我比较过度后,我发现我可以对我的原始代码进行矢量化(删除 do 循环 do kx....)。我已将 "unique" 函数与基于维基百科的合并排序算法松散地耦合在一起。内容包含在模块 SortUnique

Module SortUnique
contains
  Recursive Subroutine MergeSort(temp, Begin, Finish, list)
    ! 1st 3 arguments are input, 4th is output sorted list
    implicit none
    integer(kind=4),intent(inout) :: Begin,list(:),temp(:)
    integer(kind=4),intent(in) :: Finish
    integer(kind=4) :: Middle
    if (Finish-Begin<2) then    !if run size =1
       return                   !it is sorted
    else
       ! split longer runs into halves
       Middle = (Finish+Begin)/2
       ! recursively sort both halves from list into temp
       call MergeSort(list, Begin, Middle, temp)
       call MergeSort(list, Middle, Finish, temp)
       ! merge sorted runs from temp into list
       call Merge(temp, Begin, Middle, Finish, list)
     endif
  End Subroutine MergeSort

  Subroutine Merge(list, Begin, Middle, Finish, temp)
    implicit none
    integer(kind=4),intent(inout) :: list(:),temp(:)
    integer(kind=4),intent(in) ::Begin,Middle,Finish
    integer(kind=4)    :: kx,ky,kz
    ky=Begin
    kz=Middle
    !! While there are elements in the left or right runs...
    do kx=Begin,Finish-1
       !! If left run head exists and is <= existing right run head.
       if (ky.lt.Middle.and.(kz.ge.Finish.or.list(ky).le.list(kz))) then
          temp(kx)=list(ky)
          ky=ky+1
       else
          temp(kx)=list(kz)
          kz = kz + 1
       end if
    end do

  End Subroutine Merge

  Function Unique(list)
    !! usage sortedlist=Unique(list)
    implicit none
    integer(kind=4) :: strt,fin,N
    integer(kind=4), intent(inout) :: list(:)
    integer(kind=4), allocatable  :: unique(:),work(:)
    logical,allocatable :: mask(:)
    ! sort
    work=list;strt=1;N=size(list);fin=N+1
    call MergeSort(work,strt,fin,list) 
    ! cull duplicate indices
    allocate(mask(N));
    mask=.false.
    mask(1:N-1)=list(1:N-1)==list(2:N)
    unique=pack(list,.not.mask)

  End Function Unique

End Module SortUnique

Program TestUnique
  use SortUnique
  implicit none
  !   find "indices", the list of unique numbers in "list"
  integer (kind=4),allocatable :: list(:),newlist(:)
  integer (kind=4)  :: kx,N=100000 !N  even
  real (kind=4) :: start,finish,myrandom

  allocate(list(N))
  do kx=1,N
     call random_number(myrandom)
     list(kx)=ifix(float(N)/2.*myrandom)
  end do

  call cpu_time(start)

  newlist=unique(list)

  call cpu_time(finish)
  print *,"cull duplicates: ",finish-start
  print *,"size(newlist) ",size(newlist)

End Program TestUnique

在@HighPerformanceMark 的建议下,该函数被简单地调用为 newlist=unique(list)。上面当然不简洁,但看起来很清楚,而且比我原来的或其他提出的解决方案快大约 200 倍。