Using do loop in a Fortran 90 program to read different number of lines for n frames?
There is a file that has,say, 1000 frames. Each frame contains different number of lines.Each line has two columns of integers.But,I do not know how many number of lines each frame contains. Every frame is separated by one blank line.I want to read these values and store them in an array. But,I cannot allocate the array size as I do not know how many lines every frame has. So, I have two questions:
- How can I use a "do" loop to read the different number of lines in a fortran90 program ? I cannot use "count-controlled do" loop as I do not know the exact number of lines in each frame.
- How do I store the numbers in an array if I cannot allocate the size of it earlier ? The file looks something like this:
1 2
2 1
3 2
2 8
4 5
4 17
2 10
and so on...
Any suggestions?
Solution 1:
The easiest way is like this (reverse the order of indexes for row and column if necessary and add declarations for all variables I skipped to keep it short). It first reads the file and determines the number of lines. Then it rewinds the file to start from the beginning and reads the known number of lines.
integer,allocatable :: a(:,:)
integer :: pair(2)
integer :: unit
unit = 11
open(unit,file="test.txt")
n = 0
do
read(unit,*,iostat=io) pair
if (io/=0) exit
n = n + 1
end do
rewind(unit)
allocate(a(n,2))
do i=1,n
read(unit,*) a(i,:)
end do
close(unit)
print *, a
end
The check if (io/=0) exit
is generic and will trigger on any error or end of record/file condition encountered.
Fortran 2003 contains constants specific constants in the iso_fortran_env
module that contain specific values the iostat=
can have.
IOSTAT_END:
The value assigned to the variable passed to the IOSTAT= specifier of an input/output statement if an end-of-file condition occurred.
IOSTAT_EOR:
The value assigned to the variable passed to the IOSTAT= specifier of an input/output statement if an end-of-record condition occurred.
So if you only want to check for the end of file condition, use
if (io/=iostat_end) exit
end use
the iso_fortran_env
module before.
One pass solution also can be done, using temporary array you can grow the array easily (the C++ vector does the same behind the scenes). You could even implement your own grow able class, but it is out of scope of this question.
If you have a long fixed-size array with your data and not an allocatable one, you can skip the first pass and just read the file with the iostat=
specifier and finish reading when it is nonzero or equal to iostat_end
.
Solution 2:
One of the weaknesses of even modern versions of Fortran (IMO) is the lack of expanding arrays like std::vector
in C++ or GArray
in GLib/C. Yes, you could write such a thing yourself, but the lack of generics/templates means that you'd have to specialise for every type and kind, or mess about with class(*)
and allocatable scalars... urgh.
But I digress.
There are two options I can see. The first is to do two passes through the file, first counting the number of lines between each space, and allocating your frame arrays as you go, and the second pass to actually fill the arrays with data.
The second, possibly more "Fortran-y" way to do it is to have a temporary work array which is as large as you ever expect a single frame to be. Fill this with data, then when you reach the end of a frame, allocate an array of the right size and copy your just-read data into it.
Neither solution is particularly great though unfortunately.