PDA

View Full Version : how to read from a nonformatted file in fortran




ssmmgg
Dec 26, 2009, 11:32 AM
hi,
i have a file that contains lots of numbers like this:

1 2 34 998 3 1 4
2 4
3
6
77789 908
...

so we dont know how many numbers in line and even how many lines

i want to read numbers from this file one by one and find how many even and odd numbers exist.
what's the best?
tanx a lot



lee1210
Dec 26, 2009, 12:27 PM
I would just set open this as text, and make reads into a long character(*). you can define long based on your needs, 32768 would probably be the longest you'd want to deal with, but that's up to you. Read into that using '(a)' for your format. I would then use the index primitive to find the whitespace (hopefully it's well-known in advance, such as a single space. Then you can read from your character(*) using i5, i13, etc. Whatever is appropriate using array slicing, i.e.:
read(input,'(i13)') tmp_int
if you need these in an arbitrary length array, you can make an allocatable array. I always make a subroutine that takes an allocatable array, a single element to add, and the current position. You can make it internal and just take the element to add. Then as needed you can resize the array using a second allocatable array that's local to the subroutine, size it to the current size, copy the elements, deallocate the original, size it to a new bigger size, copy back, etc.

-Lee

ssmmgg
Dec 26, 2009, 07:19 PM
tanx Lee
could u show me an example of code that do this

without an allocable array. :)

crackpip
Dec 26, 2009, 11:04 PM
Why don't you use a shell script with the 'wc' command to either count the number of words or the number of lines in the file? Then pass the count as an argument to the Fortran program. If you are using an older Fortran standard and can't pass arguments, you could always just save the results to a temporary file and read it in the Fortran code.

crackpip

ssmmgg
Dec 27, 2009, 12:40 AM
i forced to do it with read command
nothing else allowed:(

crackpip
Dec 27, 2009, 06:00 PM
This sounds like some sort of homework assignment, so I'll give you few hints. You could use a do (or do while) loop. Inside the loop use non-advancing input to read each variable in the record, then use the iostat or eor specifier to check if you reached the end of the record. Once at the end of a record, Fortran should automatically advance to the next record. You can also use the end argument to handle reaching the end of the file or again error handling with iostat. A negative iostat means end of record or end of file, while positive means a formatting error.

Lee's solution will be much quicker because it's not making so many discrete file operations.

Good luck,
crackpip

lee1210
Dec 27, 2009, 08:27 PM
program evenodd
implicit none
character(32767) :: inp
integer(4) :: tmp,oddCnt = 0,evenCnt = 0
integer(4), allocatable :: allVals(:)
integer(2) :: st,numVals = 0
logical :: keepReading

open(unit=9,file='./myInput.txt')
keepReading = .true.
do while(keepReading)
read(9,'(a)',iostat=st) inp
if(st < 0) then
keepReading = .false.
else
call countLine()
endif
enddo
oddCnt = count(btest(allVals(1:numVals),0))
evenCnt = numVals - oddCnt
write(6,'("Num even: ",i5," Num odd: ",i5)') evenCnt,oddCnt

contains
subroutine countLine()
implicit none
integer(2) :: stpos = 1,endpos
inp=trim(adjustl(inp))
if(len_trim(inp) == 0) return
do while(.true.)
if(index(inp(1:len_trim(inp)),' ') > 0) then
endpos=index(inp,' ')
else
endpos=len_trim(inp)
endif
read(inp(stpos:endpos),'(i13)',iostat=st) tmp
if(st == 0) call addVal(tmp)
if(endpos == len_trim(inp)) exit
inp=trim(adjustl(inp(endpos+1:)))
enddo
end subroutine countLine

subroutine addVal(x)
implicit none
integer(4), intent(in) :: x
integer(4), allocatable :: tmpStore(:)
if(.not. allocated(allVals)) allocate(allVals(8))
if(size(allVals) < numVals + 1) then
if(allocated(tmpStore)) deallocate(tmpStore)
allocate(tmpStore(size(allVals)))
tmpStore=allVals
deallocate(allVals)
allocate(allVals(size(tmpStore)*4))
allVals=tmpStore
deallocate(tmpStore)
endif
numVals = numVals + 1
allVals(numVals) = x
end subroutine addVal

end program evenodd


This isn't the cleanest... but it's not for work, etc. so i'm not that concerned.

I did it with an allocatable array, even though for this purpose there is no reason to keep all values in memory, you can do the test an item at a time. I did it this way because you asked me not to, and this sounds like an assignment. Now if you try to turn this in unaltered, you won't be able to explain the (presumably uncovered) use of allocatables, etc. I have a very similar version that just does the count an item at a time, but don't want to post it in a manner that you could just turn it in.

This isn't rigorously tested. I used the following file and it seemed to work, skipping invalid values silently:

1 2 34 998 3 1 4
2 4
3
6
77789 908 8 9 7 5
asdf tr 4 9 2
3 4 3 9 1 3 4 2 1 34 90 ad 4
5 9 1


If you have more questions, please ask, but if this is homework please say so to begin with.

-Lee

EDIT: Please excuse the do while(.true.). I hate code like that, but using cycle/exit is easier, if a little dirtier. Sorry.

EDIT 2: I thought i'd mention a design compromise i made. To simplify the logic in determining where the next value to read is, i made a sacrifice of execution time. This:
inp=trim(adjustl(inp(endpos+1:)))
Is going to take some time to do, memory manipulation, etc. There's not a need to do this, but tracking both start and end position, and doing all of the slicing to achieve this, etc. was going to require too much brain power. If execution time is of critical importance, you could change the algorithm to do this with the line "fixed" with no modifications.

EDIT 3: Bah, forget some things i wanted to mention. Company has been in town, i've been working on this on and off for the last two days, etc. What i wanted to add was that splitting up a string like this is best to have in a library that you can always use. At work we have something that will return the pointer to an array that's allocated to the appropriate size, with each item as an element. With something like that, it would be easy to just take each line, split it with that, and act on each element. Same goes for the code to add an element to an allocatable array. It's best to have one for each type, preferably with an interface so you can call the same name for all of your types, that just takes the current item count, the allocatable, and the value to add. I only implemented these things here since you wouldn't have them in a library.

ssmmgg
Dec 27, 2009, 10:43 PM
i'm going to check it
really tanx

but it's not a home work
it's a kind of work! ;)

ssmmgg
Dec 27, 2009, 11:09 PM
but this happens:

first 1
and after exiting 2

lee1210
Dec 27, 2009, 11:28 PM
I don't know what compiler/runtime you're using (certainly not running on a Mac...). I think the issue has to do with resizing of the allocatable arrays? Or doing assignment from arrays with mismatched dimensions? It might help to add slicing in the addVal copies to/from the temp array. For reference, this all works in g95.

I can post the other code in the morning, but basically the change was, in checkline, if st is 0, do the btest and increment the appropriate count. Then the count line would come out, the array would come out, and addVal would come out. If you can't make the changes yourself, I'll post the other code tomorrow. It's a bit simpler, so it may work better with your compiler and runtime.

-Lee

edit:
change
allVals=tmpStore
to:
allVals(1:size(tmpStore))=tmpStore
or even:
allVals(1:size(tmpStore))=tmpStore(1:size(tmpStore))
I think your runtime will be happier. The 7 and 31 threw me off, but it's really the first resize from 8 to 32 elements.

ssmmgg
Dec 28, 2009, 01:24 AM
i used to do it on 7
i'm trying any os
thanx a lot
really


it works @ last! :apple: