How to get priorly-unknown array as the output of a function in Fortran
In Python:
def select(x):
y = []
for e in x:
if e!=0:
y.append(e)
return y
that works as:
x = [1,0,2,0,0,3] select(x) [1,2,3]
to be translated into 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
The questions are in Fortran:
- how to declare y(?)?
- how to declare predefined values for x
- how to avoid dimension info n
for 1 if it is defined as y(n) the output will be:
x = (/1,0,2,0,0,3/) print *,select(x,6) 1,2,3,0,0,0
which is not desired!
!-------------------------------
Comments:
1- All given answers are useful in this post. Specially M.S.B and eryksun's.
2- I tried to adapt the ideas for my problem and compile with F2Py
however it was not successful. I had already debugged them using GFortran and all were successful. It might be a bug in F2Py
or something that I don't know about using it properly. I will try to cover this issue in another post.
Update: A linked question could be found at here.
I hope a real Fortran programmer comes along, but in the absence of better advice, I would only specify the shape and not the size of x(:)
, use a temporary array temp(size(x))
, and make the output y allocatable
. Then after the first pass, allocate(y(j))
and copy the values from the temporary array. But I can't stress enough that I'm not a Fortran programmer, so I can't say if the language has a growable array or if a library exists for the latter.
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
Edit:
Based on M.S.B.'s answer, here's a revised version of the function that grows temp y
with over-allocation. As before it copies the result to y at the end. It turns out i's not necessary to explicitly allocate a new array at the final size. Instead it can be done automatically with assignment.
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
Here is an example of a Fortran function returning a variable length array. This is a feature of Fortran 2003. Also used in the test driver is automatic allocation on assignment, another Fortran 2003 feature.
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
Here is an alternative solution that uses a temporary array to "grow" the output array (function return) as needed. While two passes through the input array are avoided, array copies are required. Another Fortran 2003 feature, move_alloc, reduces the number of copies needed. move_alloc also takes care of the (re)allocation of the output array (here "y") and deallocation of the input array (here "temp"). Perhaps this is more elegant, but it is probably less efficient since multiple copies are used. This version is probably more educational then useful. @eryksun's version uses one pass and one copy, at the expense of making the temporary array full size.
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
If the example in your question really is what you want to do, you can use the Fortran90 intrinsic `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
The output of the example program is: 1 2 3