5

I want to fetch data from a file, which can have variable size in its data content. However, the structure is quite simple. 3 columns and undefined number of rows. I figured that using the allocatable multi-dimensional array and an explicit DO loop get me solve my problem. Here is my code so far

program arraycall
    implicit none

    integer, dimension(:,:), allocatable :: array
    integer :: max_rows, max_cols, row, col

    allocate(array(row,col))

    open(10, file='boundary.txt', access='sequential', status='old', FORM='FORMATTED')

     DO row = 1, max_rows
       DO col = 1, max_cols
            READ (10,*) array (row, col)
       END DO
     END DO

     print *, array (row,col)

     deallocate(array)

 end program arraycall 

Now the problem I am facing is that I don't know how should I define these max_rows and max_cols which resonates with the fact that it's of unknown size.

Sample file would may look like

11 12 13

21 22 23

31 32 33

41 42 43

So I figured the way to estimate record length of the file on the fly (dynamically). Updating for the future reference to others

!---------------------------------------------------------------------
! Estimate the number of records in the inputfile
!---------------------------------------------------------------------
    open(lin,file=inputfile,status='old',action='read',position='rewind')

    loop1: do
      read(lin,*,iostat=eastat) inputline
      if (eastat < 0) then
        write(*,*) trim(inputfile),": number of records = ", numvalues
        exit loop1
      else if (eastat > 0 ) then
        stop "IO-Error!"
      end if

      numvalues=numvalues+1
    end do loop1
!-----------------------------------------------------------------------
! Read the records from the inputfile
!-----------------------------------------------------------------------
    rewind(lin)
    allocate (lon(numvalues),lat(numvalues),value(numvalues))

    do i=1,numvalues
      read(lin,*) lon(i),lat(i),value(i)
    end do

    close(lin)
PT2009
  • 117
  • 1
  • 10

2 Answers2

3

I think you have 3 options, two of which have already been described:

  1. Read the file twice. First read the number of rows, then allocate and read the values. As you said, inefficient if the I/O time is relevant.

  2. As @AlexanderVogt suggested, estimate the maxmimum number of rows. You don't need to carry around this large matrix during all your code. You can define a second array and do something like (based on @AlexanderVogt code):

    allocate(array2(3,tot_rows))
    array2 = array(:, :tot_rows)
    deallocate(array)
    

    Unfortunately, I'm afraid you need 2 different arrays, as you cannot resize in-place. This also means that, for a short while, you'll be using a lot of memory if array and arrray2 are large.

  3. Use linked-lists. That is the most elegant solution and allows you to read the file just once without the need to pre-allocate an array. But it's the hardest to code. Here is a simple example that works for one of the arrays. You need three linked lists or a single linked list with:

    integer, dimension(3) :: data
    

    if you want it to work with 3 columns.

Linked List code:

program LinkedList
implicit none
  integer :: i, eastat, value, numvalues
  type node
      integer :: data
      type( node ), pointer :: next
  end type node
  integer, dimension(:), allocatable :: lon
  type( node ), pointer :: head, current, previous


  nullify( head )   ! Initialize list to point to no target.

  open(10,file='data.dat',status='old',action='read', position='rewind')
  numvalues = 0 
  do 
      read(10,*,iostat=eastat) value
      if (eastat < 0) then
          write(*,*) "number of records = ", numvalues
          exit
      else if (eastat > 0 ) then
          stop "IO-Error!"
      end if
      allocate( current )
      current%data = value
      current%next => head
      head => current
      numvalues=numvalues+1
  end do 
  close(10)
! The list is read. You can now convert it into an array, if needed for
! numerical efficiency

  allocate(lon(numvalues))  
  current => head
  ! You could transverse the list this way if you hadn't kept numvalues
  !do  while ( associated( current ) )
  do i= numvalues, 1, -1
      lon(i) = current%data
      previous => current
      current => current%next
!       head => current
      deallocate(previous)
   end do


! Output the list, deallocating them after use.
print *,"lon = ", lon

end program LinkedList
Ramon Crehuet
  • 3,679
  • 1
  • 22
  • 37
1

You could define a maximally allowed number of rows and make use of iostat to check for the end of the file (or an error):

program arraycall 

  implicit none
  integer, dimension(:,:), allocatable :: array
  integer           :: row
  integer           :: stat ! Check return values
  ! Define max. values
  integer,parameter :: max_rows=1000
  integer,parameter :: max_cols=3    ! As stated in the question
  ! Total number of rows in the file
  integer           :: tot_rows

  allocate( array(max_cols,max_rows), stat=stat)
  ! Always a good idea to check the return value
  if ( stat /= 0 ) stop 'Cannot allocate memory!'

  open(10, file='boundary.txt', access='sequential', &
       status='old', FORM='FORMATTED')

  DO row = 1, max_rows
    ! You can directly read in arrays! 
    READ (10,*,iostat=stat) array(:,row)
    if ( stat > 0 ) then
      stop 'An error occured while reading the file'
    elseif ( stat < 0 ) then
      tot_rows = row-1
      print *, 'EOF reached. Found a total of ', tot_rows, 'rows.'
      exit
    endif
  END DO

  close(10)

  ! Do stuff, e.g. re-allocate the array
  print *,array(:,:tot_rows)

  deallocate(array)
end program arraycall 

iostat > 0 is an error, iostat < 0 is end-of-file (or end-of-record for some compilers).

Alexander Vogt
  • 17,879
  • 13
  • 52
  • 68
  • Thank you very much for the answer Alexander. little extension to the question. What if the file contains more than 1000 rows? Is there a way where I don't have to care about number of rows at all? If yes, then is it also possible to do with the columns as well? – PT2009 Jun 12 '14 at 13:09
  • You could read through the file just using a blank `read` statement, counting rows until you hit the end. (ie checking with iostat) Then you can allocate the necessary arrays, and use `rewind` to start back at the beginning and put the data in. – dwwork Jun 12 '14 at 15:09
  • @SuperCow Yeah, but that would require reading in the file twice. I would prefer reallocating the array, which requires only operations in memory (and no I/O). – Alexander Vogt Jun 12 '14 at 15:17
  • In my view what PT2009 asked is to dynamically allocate the array (at least row size). For which I agree with SuperCow. You must go through file twice (unfortunately). First, scan to get row count, allocate array and then Read again. Although, if there was something like dynamically expanding array like higher level languages. – Indigo Jun 13 '14 at 08:20
  • @Indigo I know... But especially for large files it is much faster to re-allocate the array then even scanning through the file. – Alexander Vogt Jun 13 '14 at 08:36
  • @AlexanderVogt: Indeed totally agreed. Scanning file twice is super inefficient. Particularly imagine if there is a really large data file. However, it seems that PT2009 does not know exact size of data and so that's the question. We can't restrict to only 1000 rows. :-) – Indigo Jun 13 '14 at 09:30
  • @Indigo Well then, set `max_rows` to something higher ;-) – Alexander Vogt Jun 13 '14 at 10:41
  • @AlexanderVogt: Yes, or maybe expand array on the fly as needed, keeping the process recursively until the end of file using iostat. Sorry just an idea until I get time to try it myself later. Ref: http://www.hicest.com/ALLOCATE.htm and http://stackoverflow.com/questions/8384406/how-to-increase-array-size-on-the-fly-in-fortran – Indigo Jun 13 '14 at 12:25