Как получить ранее неизвестный массив в качестве вывода функции в Фортране

В Python:

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

который работает как:

x = [1,0,2,0,0,3]
select(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

Вопросы на Фортране:

  1. как объявить y(?)?
  2. как объявить предопределенные значения для x
  3. как избежать информации об измерении n

для 1, если он определен как y(n), вывод будет:

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

что нежелательно!
!--------------------------------
Комментарии:
1- Все данные ответы полезны в этом посте. Особенно M.S.B и eryksun.
2- Я попытался адаптировать идеи для своей проблемы и скомпилировать с помощью F2Py, однако это не увенчалось успехом. Я уже отлаживал их с помощью GFortran, и все прошло успешно. Это может быть ошибка в F2Py или что-то, что я не знаю о правильном использовании. Я постараюсь осветить этот вопрос в другом посте.

Обновление: связанный вопрос можно найти по адресу здесь.


person Developer    schedule 25.11.2011    source источник
comment
Как именно вы оказались в ситуации обратного переноса с Python на Fortran? о_О Довольно необычно. Или вы просто изучаете Фортран в качестве академического упражнения?   -  person Karl Knechtel    schedule 25.11.2011
comment
Общее заявление: не единственная причина, но, по крайней мере, для производительности, я всегда должен учитывать перевод некоторых кодов на Фортран, а затем вызывать их внутри Python (через F2Py). До сих пор это было лучшее совпадение для меня с качеством и производительностью (Python), а также скоростью и производительностью (Fortran).   -  person Developer    schedule 25.11.2011


Ответы (3)


Я надеюсь, что появится настоящий программист на Фортране, но в отсутствие лучшего совета я бы указал только форму, а не размер x(:), использовал временный массив temp(size(x)) и сделал вывод y allocatable. Затем после первого прохода allocate(y(j)) и скопируйте значения из временного массива. Но я не могу не подчеркнуть, что я не программист на Фортране, поэтому я не могу сказать, есть ли в языке расширяемый массив или существует ли библиотека для последнего.

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

Редактировать:

Основываясь на ответе MSB, вот пересмотренная версия функции, которая увеличивает temp y при перераспределении. Как и раньше, он копирует результат в 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
person Eryk Sun    schedule 25.11.2011
comment
Спасибо, что поделились своими знаниями с сообществом. Ваш ответ и MSB были очень близки друг к другу и оба очень полезны. Кстати, в части j+j/8+8 речь шла о размере целого числа, поэтому для других типов данных требуются изменения! - person Developer; 28.11.2011
comment
@Supporter: Может быть, это было неясно. Я добавил обновление в качестве альтернативы альтернативному ответу MSB. Вместо того, чтобы увеличивать массив один за другим, что, как мне кажется, связано с чрезмерным копированием, я выделил его слишком много. Текущий требуемый размер равен j элементам, поэтому я выделяю это плюс еще одну восьмую (также плюс постоянную 8, если он маленький). Таким образом, размер перераспределения растет пропорционально текущему размеру массива, что сводит к минимуму количество перераспределений. В конце я уменьшаю его, надеюсь, компилятор оптимизирует его, чтобы не требовалось копирование. - person Eryk Sun; 28.11.2011
comment
Для меня оба ответа полезны и правильны. Только что перепроверил время отправки ответов и понял, что ты первый (7.19 ‹ 7.26). Таким образом, ваш ответ выбран сейчас. - person Developer; 28.11.2011

Вот пример функции 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
person M. S. B.    schedule 25.11.2011
comment
Выполнение 2 полных проходов — еще один способ. Я выбрал временный массив, думая, что работа, проделанная в функции (не этой, а вообще), вероятно, дороже, чем копирование результата. Кроме того, разве выделяемые массивы не являются функцией Fortran 90/95? - person Eryk Sun; 25.11.2011
comment
выделяемые массивы являются частью Fortran 90. В Fortran 95 и 2003 были внесены улучшения. В этом примере возвращаемая функция, которая является размещаемой, не поддерживалась до Fortran 2003. Как и автоматическое выделение, которое происходит в операторе присваивания в тестовый драйвер, ответ = выбор (массив). - person M. S. B.; 25.11.2011
comment
Спасибо за Ваш ответ. Однако это было очень близко к ответу eryksun. Этот пост также был полезен для меня, помогая выяснить некоторые улучшения в Fortran 2003. - person Developer; 28.11.2011
comment
В первом блоке кода вы также можете объявить результат как integer :: y(count(x /= 0)). (На самом деле это может быть действительный Fortran 95, но я не уверен в результате автоматической функции.) Я предполагаю, что программа построит промежуточный логический массив, в зависимости от того, насколько умен компилятор. - person knia; 13.01.2019

Если пример в вашем вопросе действительно то, что вы хотите сделать, вы можете использовать встроенный пакет Fortran90:

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

person alexurba    schedule 25.11.2011
comment
Спасибо за подсказку. Однако пример в вопросе был только для демонстрации проблемы. В любом случае ваша подсказка очень полезна для других простых ситуаций, которые вы указали. Действительно, вопрос относится к тому, как pack реализует возврат массива переменного размера в качестве вывода в зависимости от условия, например, «x/= 0» на основе x без заданной информации о размере! Другой момент заключается в том, что вывод «pack» является подмножеством ввода «x» и, например, не может быть больше по размеру. Ведь это хороший намек. - person Developer; 26.11.2011