相当于唯一的堡垒



我发现很多问题可以解决这个问题,但没有一个直接回答这个问题:

-在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

我的尝试希望对列表进行排序,但如果取消该要求会更好

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

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

请参阅此要点,了解上面的(unique_sort)之间的时序比较,您的示例(unique_indices)和罗塞塔代码(remove_dups)的示例以及一些变体。我想测试性能标记的代码@High但还没有。

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不需要对输入数组进行预排序,并且实际上返回值而不是索引(请参阅 gist 以获取返回值的unique_indices的修改版本,这似乎根本没有减慢它的速度)。

另一方面,unique_sort需要大约两倍的时间,但旨在处理未排序的输入,并且还以 8 LOC(减去 var 声明)的排序顺序返回值。所以这似乎是一个公平的权衡。无论如何,我相信可以使用某种掩蔽语句优化unique_sort以获得更高的速度,但那是另一天。


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

在@VladimirF指出我过度比较之后,我发现我可以矢量化我的原始代码(删除 do 循环 do kx....)。 我将"唯一"函数与基于维基百科的合并排序算法相结合。 内脏包含在模块 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 倍。

最新更新