简体   繁体   中英

Reading a known number of variable from a file when one of the variables are missing in input file

I already checked similar posting. The solution is given by MSB here Reading data file in Fortran with known number of lines but unknown number of entries in each line

So, the problem I am having is that from text file I am trying to read inputs. In one line there is supposed to be 3 variables. But sometimes the input file may have 2 variables. In that case I need to make the last variable zero. I tried using READ statement with IOSTAT but if there is only two values it goes to the next line and reads the next available value. I need to make it stop in the 1st line after reading 2 values when there is no 3rd value.

I found one way to do that is to have a comment/other than the type I am trying to read (in this case I am reading float while a comment is a char) which makes a IOSTAT>0 and I can use that as a check. But if in some cases I may not have that comment. I want to make sure it works even than.

Part of the code

    read(15,*) x
    read(15,*,IOSTAT=ioerr) y,z,w
    if (ioerr.gt.0) then
        write(*,*)'No value was found'
        w=0.0;
        goto 409
        elseif (ioerr.eq.0) then
        write(*,*)'Value found', w
        endif
  409   read(15,*) a,b
        read(15,*) c,d

INPUT FILE is of the form

    -1.000  abcd                                                                        
    12.460  28.000  8.00 efg                                                                            
    5.000   5.000   hijk                                                                            
    20.000  21.000  lmno                                                                            

I need to make it work even when there is no "8.00 efg"

for this case

    -1.000  abcd                                                                        
     12.460 28.000                                                                              
     5.000  5.000   hijk                                                                            
     20.000 21.000  lmno

I can not use the string method suggested by MSB. Is there any other way?

I seem to remember trying to do something similar in the past. If you know that the size of a line of the file won't exceed a certain number, you might be able to try something like:

...
character*(128) A  

read(15,'(A128)') A  !This now holds 1 line of text, padded on the right with spaces
read(A,*,IOSTAT=ioerror) x,y,z
if(IOSTAT.gt.0)then
   !handle error here
endif

I'm not completely sure how portable this solution is from one compiler to the next and I don't have time right now to read up on it in the f77 standard...

I have a routine that counts the number of reals on a line. You could adapt this to your purpose fairly easily I think.

subroutine line_num_columns(iu,N,count)
    implicit none

    integer(4),intent(in)::iu,N

    character(len=N)::line
    real(8),allocatable::r(:)
    integer(4)::r_size,count,i,j

    count=0 !Set to zero in case of premature return

    r_size=N/5 !Initially try out this max number of reals
    allocate(r(r_size))

    read(iu,'(a)') line

50  continue
    do i=1,r_size
        read(line,*,end=99) (r(j),j=1,i) !Try reading i reals
        count=i
        !write(*,*) count
    enddo
    r_size=r_size*2 !Need more reals
    deallocate(r)
    allocate(r(r_size))
    goto 50

    return

99  continue
    write(*,*) 'I conclude that there are ',count,' reals on the first line'


end subroutine line_num_columns

If a Fortran 90 solution is fine, you can use the following procedure to parse a line with multiple real values:

subroutine readnext_r1(string, pos, value)
  implicit none
  character(len=*), intent(in)    :: string
  integer,          intent(inout) :: pos
  real,             intent(out)   :: value

  integer                         :: i1, i2

  i2 = len_trim(string)

  ! initial values:
  if (pos > i2) then
     pos   = 0
     value = 0.0
     return
  end if

  ! skip blanks:
  i1 = pos
  do
     if (string(i1:i1) /= ' ') exit
     i1 = i1 + 1
  end do

  ! read real value and set pos:
  read(string(i1:i2), *) value
  pos = scan(string(i1:i2), ' ')
  if (pos == 0) then
     pos = i2 + 1
  else
     pos = pos + i1 - 1
  end if

end subroutine readnext_r1

The subroutine reads the next real number from a string 'string' starting at character number 'pos' and returns the value in 'value'. If the end of the string has been reached, 'pos' is set to zero (and a value of 0.0 is returned), otherwise 'pos' is incremented to the character position behind the real number that was read.

So, for your case you would first read the line to a character string:

character(len=1024) :: line
...
read(15,'(A)') line
...

and then parse this string

real    :: y, z, w
integer :: pos
...
pos = 1
call readnext_r1(line, pos, y)
call readnext_r1(line, pos, z)
call readnext_r1(line, pos, w)
if (pos == 0) w = 0.0

where the final 'if' is not even necessary (but this way it is more transparent imho).

Note, that this technique will fail if there is a third entry on the line that is not a real number.

You might be able to use the wonderfully named colon edit descriptor. This allows you to skip the rest of a format if there are no further items in the I/O list:

Program test

  Implicit None

  Real :: a, b, c
  Character( Len = 10 ) :: comment


  Do

     c = 0.0
     comment = 'No comment'
     Read( *, '( 2( f7.3, 1x ), :, f7.3, a )' ) a, b, c, comment

     Write( *, * ) 'I read ', a, b, c, comment

  End Do

End Program test

For instance with gfortran I get:

Wot now? gfortran -W -Wall -pedantic -std=f95 col.f90
Wot now? ./a.out
  12.460  28.000  8.00 efg
 I read    12.460000       28.000000       8.0000000     efg       
  12.460  28.000  
 I read    12.460000       28.000000      0.00000000E+00           
^C

This works with gfortran, g95, the NAG compiler, Intel's compiler and the Sun/Oracle compiler. However I should say I'm not totally convinced I understand this - if c or comment are NOT read are they guaranteed to be 0 and all spaces respectively? Not sure, need to ask elsewhere.

I know the following simple solution:

      w = 0.0
      read(15,*,err=600)y, z, w
      goto 610
600   read(15,*)y, z
610   do other stuff

But it contains "goto" operators

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM