如何在Fortran中获得函数的先前未知数组作为输出

15

Python中:

def select(x):
    y = []
    for e in x:
        if e!=0:
            y.append(e)
    return y

这将作为:

x = [1,0,2,0,0,3]
call select(x)
! x 现在为 [1,2,3]

翻译成 Fortran

function select(x,n) result(y)
    implicit none
    integer:: x(n),n,i,j,y(?)
    j = 0
    do i=1,n
        if (x(i)/=0) then
            j = j+1
            y(j) = x(i)
        endif
    enddo
end function

这些问题是关于Fortran的:

  1. 如何声明y(?)?
  2. 如何为x定义预设值
  3. 如何避免使用维度信息n

对于第一个问题,如果它被定义为 y(n),输出将会是:

x = (/1,0,2,0,0,3/)
print *,select(x,6)
1,2,3,0,0,0

这不是我们想要的结果!
!-------------------------------
注释:
1- 这篇帖子中提供的所有答案都很有用。特别是M.S.B和eryksun's的。
2- 我尝试为我的问题适应这些想法并使用F2Py编译,但是不成功。我已经使用GFortran进行了调试,并且全部都成功了。这可能是F2Py中的一个错误或者是我不知道如何正确使用它的原因。我将在另一篇帖子中尝试解决这个问题。

更新: 相关问题可以在这里找到。


1
你是怎么从Python回溯到Fortran的位置的?这似乎相当不寻常。或者你只是把学习Fortran作为一种学术练习吗? - Karl Knechtel
3
一般性声明:并非唯一原因,但至少出于性能考虑,我经常需要将一些代码翻译成Fortran,并通过F2Py在Python中调用它们。到目前为止,这是最适合我的选择,既具备了质量和生产力(Python),又具有速度和性能(Fortran)。 - Developer
3个回答

13

我希望有一位真正的Fortran程序员能提供更好的建议,但在没有更好的建议的情况下,我只会指定x(:)的形状而不是大小,使用临时数组temp(size(x)),并使输出的y为allocatable。然后在第一次传递之后,allocate(y(j))并从临时数组复制值。但我无法强调这一点:我不是Fortran程序员,因此无法确定该语言是否具有可增长的数组,或者是否存在用于后者的库。

program test
    implicit none
    integer:: x(10) = (/1,0,2,0,3,0,4,0,5,0/)
    print "(10I2.1)", select(x)

contains

    function select(x) result(y)
        implicit none
        integer, intent(in):: x(:) 
        integer:: i, j, temp(size(x))
        integer, allocatable:: y(:)

        j = 0
        do i = 1, size(x)
            if (x(i) /= 0) then
                j = j + 1
                temp(j) = x(i)
            endif
        enddo

        allocate(y(j))
        y = temp(:j)
    end function select

end program test

编辑:

基于 M.S.B. 的答案,这是函数的修订版本,它使用了过度分配来扩大 tempy和之前一样,在最后将结果复制到 y 中。 结果表明,在最终大小处显式分配新数组并不必要。而可以通过赋值自动完成。

    function select(x) result(y)
        implicit none
        integer, intent(in):: x(:) 
        integer:: i, j, dsize
        integer, allocatable:: temp(:), y(:)

        dsize = 0; allocate(y(0))

        j = 0
        do i = 1, size(x)
            if (x(i) /= 0) then
                j = j + 1

                if (j >= dsize) then         !grow y using temp
                    dsize = j + j / 8 + 8 
                    allocate(temp(dsize))
                    temp(:size(y)) = y
                    call move_alloc(temp, y) !temp gets deallocated
                endif

                y(j) = x(i)
            endif
        enddo
        y = y(:j)
    end function select

感谢您与社区分享您的知识。您和M.S.B的答案非常接近,都非常有帮助。顺便说一下,在j+j/8+8部分,这是为整数大小而设计的,因此对于其他类型的数据需要进行更改! - Developer
@支持者:也许那不太清楚。我添加了更新作为M.S.B.备用答案的替代方案。我没有一个一个地增加数组大小,因为我认为这会涉及过多的复制,而是进行了超额分配。当前所需大小为j项,因此我分配了它加上八分之一的空间(还有一个常数8,用于当它很小的时候)。因此,超额分配的大小与数组的当前大小成比例增长,从而最大程度地减少了必须重新分配的次数。最后,我将其缩小回来,希望编译器能够优化,不需要复制。 - Eryk Sun
对我来说,两个答案都很有帮助且正确。只是我仔细检查了答案发送的时间,然后发现你是第一个(7.19 < 7.26)。所以你的答案被选择了。 - Developer

13

这里有一个Fortran函数返回可变长度数组的示例。这是Fortran 2003的一个特性。测试驱动程序中还使用了自动分配赋值,这也是另一个Fortran 2003的特性。

module my_subs

contains

function select(x) result(y)
    implicit none
    integer, dimension (:), intent (in) :: x
    integer, dimension (:), allocatable :: y
    integer :: i, j

    j = 0
    do i=1, size (x)
        if (x(i)/=0) j = j+1
    enddo

    allocate ( y (1:j) )

    j = 0
    do i=1, size (x)
        if (x(i)/=0) then
            j = j+1
            y(j) = x(i)
        endif
    enddo

    return

end function select

end module my_subs

program test

use my_subs

implicit none
integer, dimension (6) :: array = [ 5, 0, 3, 0, 6, 1 ]
integer, dimension (:), allocatable :: answer

answer = select (array)

write (*, *) size (array), size (answer)
write (*, *) array
write (*, *) answer

stop


end program test

这里有一种替代方案,它使用临时数组来“扩展”输出数组(函数返回),以满足需要。虽然避免了对输入数组的两次通行,但需要复制数组。另一个Fortran 2003功能move_alloc减少了所需的副本数量。move_alloc还负责(重新)分配输出数组(此处为“y”)和输入数组(此处为“temp”)的释放。也许这更优雅,但可能不太高效,因为使用了多个副本。此版本可能比实用性更强。@eryksun的版本在进行一次通行和一次复制的同时,以全尺寸的临时数组为代价。

function select(x) result(y)
    implicit none
    integer, dimension (:), intent (in) :: x
    integer, dimension (:), allocatable :: y, temp
    integer :: i, j

    j = 0
    do i=1, size (x)
        if (x(i)/=0) then
            j = j+1
            allocate (temp (1:j))
            if ( allocated (y) ) temp (1:j-1) = y
            call move_alloc (temp, y)
            y(j) = x(i)
        endif
    enddo

    return

end function select

做两次完整的遍历是另一种方法。我选择了一个临时数组,认为在函数中完成的工作(不是这个函数,而是一般情况下)可能比复制结果更昂贵。此外,可分配数组不是Fortran 90/95的特性吗? - Eryk Sun
可分配数组是Fortran 90的一部分。Fortran 95和2003有所改进。在此示例中,在Fortran 2003之前不支持函数返回值为可分配类型。与测试驱动程序中赋值语句中发生的自动分配一样,“answer = select (array)”也是如此。 - M. S. B.
谢谢你的回答。不过和 eryksun 的回答非常接近。这篇帖子对我也很有用,帮助我找出了 Fortran 2003 中的一些改进。 - Developer
在第一个代码块中,你也可以将结果声明为integer :: y(count(x /= 0))。(这实际上可能是有效的Fortran 95,但我不确定自动函数结果。)我猜程序会构建一个中间逻辑数组,这取决于编译器的聪明程度。 - knia

5
如果你所提的问题确实是你想要解决的问题,那么你可以使用Fortran90的内置函数`pack`。
program pack_example

implicit none

integer, dimension(6) :: x

x = (/ 1,0,2,0,0,3 /)

! you can also use other masks than 'x/=0'
write(*,*) pack(x, x/=0)

end program pack_example

示例程序的输出结果是:1 2 3。

谢谢你的提示。问题中的示例只是为了演示问题。无论如何,你提供的提示对于其他简单情况非常有用。实际上,问题涉及到pack如何根据条件(例如'x/=0')返回大小不同的数组作为输出,而没有给出大小信息!另一个要点是,'pack'的输出是输入'x'的子集,不能比输入更大,例如。总之,这是一个很好的提示。 - Developer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接