1

I'm not new to Fortran 90, but I'm curious if a straightforward solution to my question exists. I have a file which looks like this:

1
2
3
...
n

where n could be any integer and I want to read each number in the file into an integer array A. In other words, A=(1,2,3,...,n). I don't know how big is n before running the code. My solution was to open the file, read everything from it, find n, then allocate A(n) and then reread the file:

read(unit=20,fmt=*,iostat=io) a
if (io/=0) exit
A(i)=a

But, just for the sake of elegance, could this double reading be avoided?

francescalus
  • 30,576
  • 16
  • 61
  • 96
user3653831
  • 235
  • 6
  • 19
  • I've never used Fortran 90 but it seems you want to use a stack or linked list for fast reading and eventually sorting if you need too. I don't know if Fortran supports those natively but maybe that will help inspire something. – Blizzardengle Nov 24 '15 at 16:57
  • 2
    You mention Fortran 90 a bit, but are you really interested in solutions that conform to that standard of the language rather than modern Fortran? – francescalus Nov 24 '15 at 17:32
  • 3
    I think it was here before. Read the values to a scalar, put them into a moderately sized array and when the array becomes to small, copy the values somewhere, deallocate the array, allocate it two time larger and copy the values back. Repeat and at the and copy the values somewhere, deallocate the aray, allocate it to the final size and copy the values back. – Vladimir F Героям слава Nov 24 '15 at 17:55
  • http://stackoverflow.com/q/19713207 seems moderately similar, although it doesn't (yet) have a very complete set of answers. – francescalus Nov 24 '15 at 18:07
  • 1
    If the file does contain just the integers `1..n` then surely read the file once, find the value of `n`, then allocate a large enough array and assign `a(i)=i` from `i=1,n`. No need to read the file again. But I expect OP's example is misleadingly simple. – High Performance Mark Nov 24 '15 at 18:17

2 Answers2

2

Here is an example of a very simple linked list:

module list_mod
implicit none
  type list_int
    integer                 :: val = 0
    integer                 :: length = 0
    type(list_int), pointer :: next => NULL()
    type(list_int), pointer :: tail => NULL()
  end type
contains
  subroutine append( list, val )
    type(list_int), intent(inout) :: list
    integer                       :: val

    if ( associated(list%next) ) then
      allocate( list%tail%next )
      list%tail => list%tail%next
    else
      allocate( list%next )
      list%tail => list%next
    endif
    list%tail%val = val
    list%length = list%length + 1
  end subroutine
end module

You can use this to read in the file entry by entry. The length of the list is incremented with each item appended. This information is used to allocate an array which is then file element-wise.

program test
  use list_mod
  implicit none
  type(list_int)          :: ilist
  type(list_int), pointer :: cur
  integer,allocatable     :: array(:)
  integer                 :: i, io

  open( unit=20, file="test.txt", status='old' )
  do
    read(unit=20,fmt=*,iostat=io) i
    if (io/=0) exit
    call append(ilist, i)
  enddo 

  allocate( array( ilist%length ) )
  cur => ilist%next
  i = 1
  do while (associated(cur))
    array( i ) = cur%val
    i = i + 1
    cur => cur%next
  enddo

  print *,array
end program
Alexander Vogt
  • 17,879
  • 13
  • 52
  • 68
  • This might be a stupid question, but why do you need to allocate an array and go through the `do while (associated(cur))` loop? Can't you just use `ilist` and be done? – gothicVI Jan 18 '21 at 10:03
  • 1
    @gothicVI Well, the question asked to read the file into an array... Also, doing numerics on a linked list would be quite inefficient. Having it in a continuous block in memory is much neater. – Alexander Vogt Jan 28 '21 at 18:33
1

I have translated the suggestion by @VladimirF (in the comment) as in the following. Though the definition of vector_t is rather lengthy (which may be somewhat similar to vector in extending the size automatically), the caller program becomes a bit shorter.

module vector_mod
    implicit none

    type vector_t
        integer, allocatable :: elem(:)  !! elements
        integer              :: n        !! current vector size (<= size(elem))
    contains
        procedure :: push
        procedure :: resize
        procedure :: fit
    end type

contains

    subroutine push ( v, x )
        class(vector_t) :: v
        integer         :: x

        if ( allocated( v% elem ) ) then
            if ( v% n == size( v% elem ) ) call v% resize( v% n * 2 )
            v% n = v% n + 1
            v% elem( v% n ) = x
        else
            allocate( v% elem( 1 ) )
            v% n = 1
            v% elem( 1 ) = x
        endif
    end subroutine

    subroutine resize ( v, n )
        class(vector_t) :: v
        integer         :: n
        integer, allocatable :: buf(:)
        integer :: m

        allocate( buf( n ), source=0 )
        m = min( n, size(v% elem) )
        buf( 1:m ) = v% elem( 1:m )

        deallocate( v% elem )
        call move_alloc ( buf, v% elem )
    end subroutine

    subroutine fit ( v )
        class(vector_t) :: v
        call v% resize( v% n )
    end subroutine
end

program main
    use vector_mod, only: vector_t
    implicit none
    type(vector_t) :: v
    integer x, ios

    open( 10, file="test.dat", status="old" )
    do
        read( 10, *, iostat=ios ) x
        if ( ios /= 0 ) exit
        call v% push( x )
    enddo
    close( 10 )

    call v% fit()
    print *, v% elem(:)
end

But IMHO I guess the solution given in the Question (i.e., open the file, read everything from it, find n, then allocate A(n) and then reread the file) will be the simplest after all...

roygvib
  • 7,218
  • 2
  • 19
  • 36