I haven't done any Fortran programming for year and it seems I'm rather rusty now. So, I won't provide you with all my failed attempts but will humbly ask you to help me with the following.
I've got the following "input" file
1 5 e 4
A b & 1
c Z ; b
y } " N
t r ' +
It can have more columns and/or rows. I would now like to assign each of these ASCII characters to arrays x(i,j) so that I can process them further after ICHAR conversions. In this example i=1,4, j=1,5, but it can be any No depending on the input file. The simplest example
PROGRAM Example
integer :: i, j
CHARACTER, ALLOCATABLE, DIMENSION(:,:) :: A
READ *, A
ALLOCATE (A(i,j))
PRINT *, A
END PROGRAM Example
compiles (Example.f95) but
cat input | ./Example.f95
does not give any output.
I would greatly appreciate an advice on how to import the afore-mentioned strings into the program as x(i,j) terms of an array.
In Fortran, it's always best to know in advance how big your arrays need to be. I understand that in your case you can't know.
Assuming that your input is at least formatted correctly (i.e. the columns match up and have only a single space in between them), I've created a code that should in theory be able to read them in an arbitrary shape. (Not quite arbitrary, it assumes that there are fewer than 511 columns.)
It uses two ways:
It simply reads the first line in at once (1024 characters, hence the 511 limit on columns) then calculates from the length the number of columns
It then allocates an array with a guessed number of rows, and once it notices that the guess was too small, it creates a new allocation with double the number of rows. It then uses the move_alloc command to swap the allocations.
To find when it should end reading the values, it simply checks whether the read returns the IOSTAT_END error code.
Here's the code:
program read_input
use iso_fortran_env, only: IOSTAT_END
implicit none
character, dimension(:,:), allocatable :: A, A_tmp
character(len=1024) :: line ! Assumes that there are never more than 500 or so columns
integer :: i, ncol, nrow, nrow_guess
integer :: ios
character :: iom
! First, read the first line, to see how many columns there are
read(*, '(A)', iostat=ios, iomsg=iom) line
call iocheck('read first line', ios, iom)
ncol = (len_trim(line) + 1) / 2
! Let's first allocate memory for two rows, we can make it more later.
nrow_guess = 2
allocate(A(ncol, nrow_guess))
! Instead of standard input, we're reading from the line we read before.
read(line, '(*(A1,X))', iostat=ios, iomsg=iom) A(:, 1)
call iocheck('read first line into vals', ios, iom)
! Now loop over all the rows
nrow = 1
read_loop: do
if (nrow >= nrow_guess) then ! We have guessed too small.
! This is a bit convoluted, but the best
! way to increase the array shape.
nrow_guess = nrow_guess * 2
allocate(A_tmp(ncol, nrow_guess))
A_tmp(:, 1:nrow_guess/2) = A(:,:)
call move_alloc(A_tmp, A)
end if
read(*, '(*(A1,X))', iostat = ios, iomsg=iom) A(:, nrow+1)
if (ios == IOSTAT_END) exit read_loop ! We're done reading.
call iocheck('read line into vals', ios, iom)
nrow = nrow + 1
end do read_loop
! The last guess was probably too large,
! let's move it to an array of the correct size.
if (nrow < nrow_guess) then
allocate(A_tmp(ncol, nrow))
A_tmp(:,:) = A(:, 1:nrow)
call move_alloc(A_tmp, A)
end if
! To show we have all values, print them out.
do i = 1, nrow
print '(*(X,A))', A(:, i)
end do
contains
! This is a subroutine to check for IO Errors
subroutine iocheck(op, ios, iom)
character(len=*), intent(in) :: op, iom
integer, intent(in) :: ios
if (ios == 0) return
print *, "IO ERROR"
print *, "Operation: ", op
print *, "Message: ", iom
end subroutine iocheck
end program read_input
Edited to add
I had trouble with the special characters in your example input file, otherwise I'd just have made a read(*, *) A(:, nrow) -- but that messed the special characters up. That's why I chose the explicit (*(A1, X)) format. Of course that messes up when your characters don't start at the first position in the line.
You need to read the first line and determine how characters there in the line. Then read the entire file to determine the number of lines. Allocate the 2D array to hold characters. Then read the file and parse each line into the 2D array. There are more elegant ways of doing this, but here you go
program foo
implicit none
character(len=:), allocatable :: s
character, allocatable :: a(:,:)
integer fd, i, j, n, nr, nc
!
! Open file for reading
!
open(newunit=fd, file='tmp.dat', status='old', err=9)
!
! Determine number of characters in a row. Assumes all rows
! are of the same length.
!
n = 128
1 if (allocated(s)) then
deallocate(s)
n = 2 * n
end if
allocate(character(len=n) :: s)
read(fd,'(A)') s
if (len_trim(s) == 128) goto 1
s = adjustl(s)
n = len_trim(s)
deallocate(s)
!
! Allocate a string of the correct length.
!
allocate(character(len=n) :: s)
!
! Count the number of rows
!
rewind(fd)
nr = 0
do
read(fd,*,end=2)
nr = nr + 1
end do
!
! Read file and store individual characters in a(:,:)
!
2 rewind(fd)
nc = n / 2 + 1
allocate(a(nr,nc))
do i = 1, nr
read(fd,'(A)') s
do j = 1, nc
a(i,j) = s(2*j-1:2*j-1)
end do
end do
close(fd)
write(s,'(I0)') nc
s = '(' // trim(s) // '(A,1X))'
do i = 1, nr
write(*,s) a(i,:)
end do
stop
9 write(*,'(A)') 'Error: cannot open tmp.dat'
end program foo
Apparently, GOTO is verbotem, here. Here's an elegant solution.
program foo
implicit none
character, allocatable :: s(:), a(:,:)
integer fd, i, j, n, nr, nc
! Open file for reading
open(newunit=fd, file='tmp.dat', status='old', access='stream', err=9)
inquire(fd, size = n) ! Determine file size.
allocate(s(n)) ! Allocate space
read(fd) s ! Read the entire file
close(fd)
nr = count(ichar(s) == 10) ! Number of rows
nc = (count(ichar(s) /= 32) - nr) / nr ! Number of columns
a = reshape(pack(s, ichar(s) /= 32 .and. ichar(s) /= 10), [nc,nr])
a = transpose(a)
do i = 1, nr
do j = 1, nc
write(*,'(A,1X)',advance='no') a(i,j)
end do
write(*,*)
end do
stop
9 write(*,'(A)') 'Error: cannot open tmp.dat'
end program foo
Related
program factorial
implicit none
integer:: n1
real:: fact = 1.0
integer:: n = n1
integer, dimension(1:n):: x
integer:: i
print *, "Enter a number:"
read *, n1
x(1) = n1
do i=1,n1-1
x(i+1) = n1-i
fact = fact*x(i)
end do
print *, fact
end program factorial
I have written a code for calculating factorial of a number. I am asking the user to put in an integer 'n1', after which it will create an array variable containing n1 compartments. I am unsuccessful in compiling this. I keep getting the following error!
factorial.F95(6) : error 542 - N appears in the dimension of a variable, yet is not a dummy argument, a variable available through USE or CONTAINS association, a COMMON variable, a PARAMETER, or a PURE FUNCTION
Compilation failed.
How can I fix this? I want the array dimension to be equal to the input number. For example, say I want to calculate 5! (5 factorial), I want the x array to be of 5 (row or column) element length. Somehow, I am unable to do that!
The constant n1 needs to be a compile time constant to be used as a static array dimension
program factorial
implicit none
integer, parameter:: n1
integer, dimension(1:n1):: x
or you need to use allocatable arrays.
As Vladimir suggested, you have to allocate the array:
integer, dimension(:), allocatable :: x
integer :: alloc_stat
print *, "Enter a number:"
read *, n1
ALLOCATE( x(1:n1), STAT=alloc_stat )
IF ( alloc_stat .ne. 0 ) THEN
WRITE(ERROR_UNIT,*) "Array allocation failed."
ERROR_STOP alloc_stat
ENDIF
(I always check the status of my ALLOCATE statements. It's not whether you are paranoid or not, it's whether you're paranoid enough.)
If one has a file containing, for example:
1 2 3 4 5
6 7 8 9 10
11 12 13
14 15
16 17
18
19 20
How can one get the correct number of integers (in the given example, 20) from counting them in the file in Fortran?
Here's a little program wot I wrote for this problem. I've subjected it to a tiny battery of tests. It should be fairly clear what the program and subroutine are doing but if you want any explanation of what is going on, ask. If you spot any errors, fix them.
PROGRAM test
USE iso_fortran_env
IMPLICIT NONE
INTEGER :: cnt, filstat
CHARACTER(len=132) :: aline ! change the length if you want to
cnt = 0
OPEN(101,file='data.txt')
DO
READ(101,'(a132)',iostat=filstat) aline
IF (filstat/=0) EXIT
CALL get_int(cnt,aline)
END DO
WRITE(*,*) 'counted ', cnt, 'integers'
CLOSE(101)
CONTAINS
RECURSIVE SUBROUTINE get_int(cnt, str)
INTEGER, INTENT(inout) :: cnt
CHARACTER(*), INTENT(in) :: str
! Local variables
CHARACTER(len= 10), PARAMETER :: digits = '0123456789'
INTEGER :: d1, os, n
! First strip off any leading spaces
d1 = SCAN(str,digits)
IF (d1==0) THEN ! no digit was found
RETURN
END IF
! Read the first integer on the line
READ(str(d1:),*) n
cnt = cnt+1
! Figure out how many character positions to skip over
os = INT(LOG10(REAL(n)))+1
! Recurse
CALL get_int(cnt,str(d1+os+1:))
END SUBROUTINE get_int
END PROGRAM TEST
Interesting question.
Normally you should first tell us what you have tried so far. But anyway.
One solution that I came up with is to create an array that is clearly larger, set all of them to a value that is not valid, then read in from the file.
Make sure that you capture the iostat parameter, otherwise your program will crash.
Then by looking at the first occurrence of that value, you can deduce the size (and you have the values there already):
program read_data
implicit none
integer, dimension(100) :: d
integer :: s, err
d = -9999
open(unit=100, file='data.txt', action='READ', status='OLD')
read(100, *, iostat=err) d
s = 0
do
s = s + 1
if ((s == size(d)) .or. (d(s+1) == -9999)) exit
end do
if (s == size(d)) then
print *, "We might have missed some..."
end if
write(*, '(5I5)') d(1:s)
close(100)
end program read_data
Now this isn't a very good program. You're wasting memory (large array) and to scale up you have to change the code and re-compile.
I'll think about that a bit more later.
I want to write an 3 x 4 matrix from a 12 line txt numerical file
I have written a Fortran 90 program for the same
program array
implicit none
integer, parameter :: I4B = selected_int_kind(4)
integer (I4B), allocatable, dimension (:,:) :: arr
integer (I4B) :: i
open(unit=99, file='1.txt')
open(unit=100, file='out.txt')
Allocate (arr(3,4))
do i=1, 12
read(99,*)arr(3,4)
write(100,*),arr(3,4)
enddo
close (99)
deAllocate (arr)
stop
endprogram array
but it's giving an error
At line 10 of file array.f90 (unit = 99, file = '1.txt')
Fortran runtime error: End of file
Line number 10 is read(99,*)arr(3,4).
Here's a very simple implementation of your array. It uses the fact that the first index is the fastest changing. So I just keep reading until all 12 elements of the array are filled.
Then, for output, I specify a format that it should write 3 values per line.
program readfile
implicit none
integer, dimension(:, :), allocatable :: arr
open(unit=99, file='1.txt', action='READ', status='OLD')
open(unit=100, file='out.txt', action='WRITE', status='NEW')
allocate(arr(3, 4))
read(99, *) arr
write(100, '(3I4)') arr
close(99)
close(100)
end program readfile
If you want to do it explicitly, you have to calculate the two indices independently for each value read:
program readfile
implicit none
integer, dimension(:, :), allocatable :: arr
integer :: i, row, col
open(unit=99, file='1.txt', action='READ', status='OLD')
open(unit=100, file='out.txt', action='WRITE', status='NEW')
allocate(arr(3, 4))
! Read the elements:
do i = 1, 12
row = mod(i-1, 3)+1
col = (i-1) / 3 + 1
read(99, *) arr(row, col)
end do
! write the elements:
do i = 1, 4
write(100, '(3I4)') arr(:, i)
end do
close(99)
close(100)
end program readfile
By the way, your code:
do i = 1, 12
read(99, *) arr(3, 4)
write(100, *) arr(3, 4)
end do
would just 12 times read a single number from the input file, store it in the last location of the array, then write that same number back to the output file.
Also, your error message suggests that you have tried to read past the end of the file. Either your 1.txt doesn't contain 12 lines, or you might have read something else first, for example to find out how many elements there are. In that case, you would need to add a rewind(99) before you start reading the actual numbers.
I provide an example when converting a string to an integer array. I pass an array using an index range.The offset is being initiated by 0 rather than one, so the values in the array are being shifted.
s = "1,2,3,5,8"
Call str_to_num_tu (s, ",", tu(1:8))
$ Output:
$ tu(1): 0 ; tu(2): 1 ; tu(3): 2
Call str_to_num_tu (s, ",", tu)
$ Output:
$ tu(i): 1 ; tu(2): 2 ; tu(3): 3
Here is my subroutine using an unlimited polymorphic variable.
Subroutine str_to_num_tu &
( &
s, dl, tu, pos &
)
Class(*), Intent (InOut) :: tu(:)
Character (len=*), Intent (In) :: s, dl
Character (len=*), Intent (In), Optional :: pos
Integer, Allocatable :: ipos(:)
Integer :: nf, npos, ip, i, j, k
!!$ Sets tu.
!!$ s = "Pablo/Neruda"; tu = ["Pablo","Neruda"]
!!$ s = "0.2/1.3/1.5"; tu = [0.2,1.3,1.5]
nf = nfields (s, dl)
Write (*,*) ""
Write (*,*) "nf: ", nf, "; Size(tu): ", Size(tu)
i = 1
Do k = 1, nf-1
j = Index (s(i:), dl)
Select Type (tu)
Type Is (Integer (Int32))
Call str_to_num (s(i:i+j-2), tu(k))
Write (*,*) Trim (s(i:))
Write (*,*) "k: ", k, "; tu(k): ", tu(k)
End Select !!$ tu
i = i + j
End Do
!!$ Gets last field.
j = Index (s, dl, back=.true.)
Write (*,*) "j:", j, "; nf:", nf
Select Type (tu)
Type Is (Integer (Int32))
Call str_to_num (s(j+1:), tu(nf))
End Select !!$ tu
End Subroutine str_to_num_tu
To elaborate on my earlier comment. (I don't have Fortran on this computer so the syntax may be a little wonky.) What I meant is that something like this
integer, dimension(6), allocatable :: arr
integer :: ios
character(len=:), allocatable :: str
...
arr = 0
str = "1,2,3,5,8"
read(str,*,iostat=ios) arr
will read the first 6 integers from str into the elements of arr. As it happens str only has 5 integers so the last element of arr is left as 0. iostat is necessary here because an attempt to read more integers than str provides will otherwise produce an end-of-file error at run time.
Of course, this approach generalises to reals, characters and logicals too. Fortran has, built-in, polymorphic reading of intrinsic types. Up to a point.
Fortran will recognise blanks and spaces as value-separators (the Fortran standard uses the word delimiter to mean something else). Note that by setting the decimal mode on an i/o statement to comma Fortran will recognise the comma as a decimal point and treat a semi-colon, ;, as a value-separator. Technically a slash is also a value-separator but it also causes termination of list-directed input of the record so doesn't really act like a value-separator.
If concerned about strings containing other value-separators I might write a function which took a string containing such, returned one with only whitespace, and then perform the internal read on that.
Basically I am looking to enter X,Y pairs read from a file into arrays of length n where n is the number of lines(and thus x,y pairs) in the file. Unfortunately all my attempts at determining the length of the file then using that to set the size of the array have been unsuccessful. How can I accomplish this in Fortran 77? Hoping I am not missing something obvious, I am more used to Python and Java where this is rather trivial.
PS. Before asking this I looked around and it seemed that the general feeling was that you just set the size larger then you would expect it to be but that seems very memory wasteful and inefficient.
The solution is to use Fortran 90/95/2003/2008, which has the capabilities needed for your problem, while FORTRAN 77 doesn't. Read the file once to determine the number of data items. Rewind the file. Allocate the array of the required length. Read the file again, reading into the arrays.
Using Fortran 2003/2008 (not tested):
use iso_fortran_env
real :: xtmp, ytmp
real, dimension (:), allocatable :: x, y
integer :: i, n
integer :: Read_Code
open (unit=75, file=...)
n = 0
LengthLoop: do
read ( 75, *, iostat=Read_Code) xtmp, ytmp
if ( Read_Code /= 0 ) then
if ( Read_Code == iostat_end ) then
exit LengthLoop
else
write ( *, '( / "read error: ", I0 )' ) Read_Code
stop
end if
end if
n = n + 1
end do LengthLoop
allocate (x(n))
allocate (y(n))
rewind (75)
do i=1, n
read (75, *) x(i), y(i)
end do
close (75)