I know that IACHAR(s) returns the code for the ASCII character in the first character position of the string s, but I need to convert the entire string to an integer. I also have a few number of strings (around 30 strings, each consists of at most 20 characters). Is there any way to convert each one of them to a unique integer in Fortran 90?
You can read a string into an integer variable:
module str2int_mod
contains
elemental subroutine str2int(str,int,stat)
implicit none
! Arguments
character(len=*),intent(in) :: str
integer,intent(out) :: int
integer,intent(out) :: stat
read(str,*,iostat=stat) int
end subroutine str2int
end module
program test
use str2int_mod
character(len=20) :: str(3)
integer :: int(3), stat(3)
str(1) = '123' ! Valid integer
str(2) = '-1' ! Also valid
str(3) = 'one' ! invalid
call str2int(str,int,stat)
do i=1,3
if ( stat(i) == 0 ) then
print *,i,int(i)
else
print *,'Conversion of string ',i,' failed!'
endif
enddo
end program
You can use the read() method as suggested, or you could use faiNumber for Fortran(faiNumber-Fortran) that was written by me at https://github.com/kevinhng86/faiNumber-Fortran. faiNumber-Fortran operated about 10x faster than read()(tested with gfortran8 with build version legacy, f95, f2003, and f2018).
Also, if you use faiNumber-Fortran, you are guarded against invalid string such as "1 abc", "125 7895", and so on. Those formats are parsable by the read() procedure(tested with gfortran8 with build version legacy, f95, f2003, and f2018). Where faiNumber will notify you that the input string is invalid.
For version one you get two versions, one to use with pure procedures, of which slightly slower than the version that can only be used by impure procedures.
FaiNumber-Fortran also let you choose where to start and end in your string. This below is a small example of what you can do. There is a lot more than the example. Nonetheless, I documented the code very thoroughly(I hope). The example is for the version that built as an all pure procedures library.
program example
! For 64/128, use fnDecimalUtil64/fnDecimalUtil128.
! To use procedures of 64/128, The right module have to be called.
use fnDecimalUtil
implicit none
! For 64/128, integer kind are k_int64/k_int128.
integer(k_int32) :: resultValue, startpos, endpos
! Where there is an error code return, it will always be an int32 value.
integer(k_int32) :: errorInt
logical :: errorLogical
! For 64/128, call decToInt64/decToInt128.
call decToInt32("123", resultValue, errorLogical)
if ( errorLogical .eqv. .FALSE. ) then
print *, resultValue
else
print *, "There was an error during parsing."
end if
startpos = 13
endpos = 17
call decToInt32(" This here($12345)can be parse with start and end", &
resultValue, errorLogical, startpos, endpos)
if ( errorLogical .eqv. .FALSE. ) then
print *, resultValue
else
print *, "There was an error during parsing."
end if
! This procedure below is where you need to know what was wrong
! during parsing the input string.
!
! This may run slower if the strings are long. The TrueError procedure
! has exactly the same feature as the normal one, they are just
! different by how errors are handled.
!
! Empty string will be checked first then error 5.
!
! If error 5 is encountered, nothing else will be check. For error
! 5, startpos will be checked first before endpos.
!
! For 64/128, call decToInt64TrueError/decToInt128TrueError
startpos = 12
call decToInt32TrueError(" line 24: 1278421", resultValue, errorInt, startpos) ! startpos can be used without endpos,
if ( errorInt == 0 ) then
print *, resultValue
else if ( errorInt == 1 ) then
print *, "The input string was empty."
else if ( errorInt == 2 ) then
print *, "The input string contained an invalid decimal integer."
else if ( errorInt == 3 ) then
print *, "The input string contained a value that is smaller than the minimum value of the data type."
else if ( errorInt == 4 ) then
print *, "The input string contained a value that is larger than the maximum value of the data type."
else if ( errorInt == 5 ) then
print *, "It was either startpos > length, endpos < startpos, or endpos < 1."
end if
end program example
Related
I know that IACHAR(s) returns the code for the ASCII character in the first character position of the string s, but I need to convert the entire string to an integer. I also have a few number of strings (around 30 strings, each consists of at most 20 characters). Is there any way to convert each one of them to a unique integer in Fortran 90?
You can read a string into an integer variable:
module str2int_mod
contains
elemental subroutine str2int(str,int,stat)
implicit none
! Arguments
character(len=*),intent(in) :: str
integer,intent(out) :: int
integer,intent(out) :: stat
read(str,*,iostat=stat) int
end subroutine str2int
end module
program test
use str2int_mod
character(len=20) :: str(3)
integer :: int(3), stat(3)
str(1) = '123' ! Valid integer
str(2) = '-1' ! Also valid
str(3) = 'one' ! invalid
call str2int(str,int,stat)
do i=1,3
if ( stat(i) == 0 ) then
print *,i,int(i)
else
print *,'Conversion of string ',i,' failed!'
endif
enddo
end program
You can use the read() method as suggested, or you could use faiNumber for Fortran(faiNumber-Fortran) that was written by me at https://github.com/kevinhng86/faiNumber-Fortran. faiNumber-Fortran operated about 10x faster than read()(tested with gfortran8 with build version legacy, f95, f2003, and f2018).
Also, if you use faiNumber-Fortran, you are guarded against invalid string such as "1 abc", "125 7895", and so on. Those formats are parsable by the read() procedure(tested with gfortran8 with build version legacy, f95, f2003, and f2018). Where faiNumber will notify you that the input string is invalid.
For version one you get two versions, one to use with pure procedures, of which slightly slower than the version that can only be used by impure procedures.
FaiNumber-Fortran also let you choose where to start and end in your string. This below is a small example of what you can do. There is a lot more than the example. Nonetheless, I documented the code very thoroughly(I hope). The example is for the version that built as an all pure procedures library.
program example
! For 64/128, use fnDecimalUtil64/fnDecimalUtil128.
! To use procedures of 64/128, The right module have to be called.
use fnDecimalUtil
implicit none
! For 64/128, integer kind are k_int64/k_int128.
integer(k_int32) :: resultValue, startpos, endpos
! Where there is an error code return, it will always be an int32 value.
integer(k_int32) :: errorInt
logical :: errorLogical
! For 64/128, call decToInt64/decToInt128.
call decToInt32("123", resultValue, errorLogical)
if ( errorLogical .eqv. .FALSE. ) then
print *, resultValue
else
print *, "There was an error during parsing."
end if
startpos = 13
endpos = 17
call decToInt32(" This here($12345)can be parse with start and end", &
resultValue, errorLogical, startpos, endpos)
if ( errorLogical .eqv. .FALSE. ) then
print *, resultValue
else
print *, "There was an error during parsing."
end if
! This procedure below is where you need to know what was wrong
! during parsing the input string.
!
! This may run slower if the strings are long. The TrueError procedure
! has exactly the same feature as the normal one, they are just
! different by how errors are handled.
!
! Empty string will be checked first then error 5.
!
! If error 5 is encountered, nothing else will be check. For error
! 5, startpos will be checked first before endpos.
!
! For 64/128, call decToInt64TrueError/decToInt128TrueError
startpos = 12
call decToInt32TrueError(" line 24: 1278421", resultValue, errorInt, startpos) ! startpos can be used without endpos,
if ( errorInt == 0 ) then
print *, resultValue
else if ( errorInt == 1 ) then
print *, "The input string was empty."
else if ( errorInt == 2 ) then
print *, "The input string contained an invalid decimal integer."
else if ( errorInt == 3 ) then
print *, "The input string contained a value that is smaller than the minimum value of the data type."
else if ( errorInt == 4 ) then
print *, "The input string contained a value that is larger than the maximum value of the data type."
else if ( errorInt == 5 ) then
print *, "It was either startpos > length, endpos < startpos, or endpos < 1."
end if
end program example
I am trying to write a code where I want to read a variable (Delta), and based on that I am trying to copy the corresponding file (TESTDIR/Delta0.5_DOS_2D_TBM.data DOS.data for Delta=0.5) to the present directory.
Program Modify_variable_based_file
character(LEN=100):: command
character(LEN=10):: chDelta
real*8:: Delta
Print*,'Enter Delta'
Read*,Delta
write(chDelta,'(f0.1)') Delta
print*,'chDelta=',chDelta,' Delta=',Delta
command='cp TESTDIR/Delta' // trim(adjustl(chDelta)) //'_DOS_2D_TBM.data DOS.data'
call system(command)
End Program Modify_variable_based_file
However, I can see chDelta is .5 instead of 0.5 when I input Delta.
Can you suggest me the correct format? And is there an alternative where I can avoid the string conversion?
Note that here my files are named with number having the most significant digit on the left of the decimal, i.e. if it Delta is 1.5, file is Delta1.5_DOS_2D_TBM.data. Zero arises before the decimal only when there are no other significant digits.
If you need only one digit after the decimal point, you can use f20.1 etc such that
character(LEN=100) :: chDelta !! use a sufficiently large buffer
write( chDelta,'(f20.1)' ) Delta
chDelta = adjustL( chDelta )
Then the chDelta corresponding to Delta=0.5 becomes "0.5" (note that adjustL() removes all the blanks before 0). Similarly, you can retain 4 digits by using the format
write( chDelta,'(f20.4)' ) Delta
which gives chDelta = "0.5000". To obtain a flexible number of nonzero digits after the decimal point, we may need to remove unnecessary zeros manually. This can be done, for example, by searching for the last nonzero digit and removing the trailing zeros.
real*8 x( 5 )
character(100) str
x(:) = [ 1.0d0, 0.2d0, 1.23d0, -123.456d0, 123.678901d0 ]
do i = 1, 5
write( str, "(f20.4)" ) x(i)
call truncate( str, 4 )
print *, "file", trim(str), ".dat"
enddo
...
subroutine truncate( str, dmax )
implicit none
character(*), intent(inout) :: str
integer, intent(in) :: dmax !! maximum number of nonzero digits
integer :: dot, k, last
str = adjustL( str )
dot = index( str, '.' )
do k = dot + dmax, dot, -1
if ( str( k:k ) /= '0' ) then
last = k
exit
endif
enddo
if ( last == dot ) last = last + 1 !! retain at least one digit
str = str( 1:last )
end
Then the output becomes
file1.0.dat
file0.2.dat
file1.23.dat
file-123.456.dat
file123.6789.dat
I think this is quite a basic question, but I can't seem to find the answer. I'm trying to read a file of the following form:
1 filedir/i03j12_fort.4
71 filedir/i04j01_fort.4
224 filedir/i04j02_fort.4
I use the following command to get the initial integer, plus the 'i' and 'j' values from the filename (ldir is a string containing the length of filedir).
read(filenumber,'(i6,'//ldir//'x,i2,x,i2)') n,pix_i,pix_j
the problem is that the amount of whitespace preceding the integer varies between files, so I have to manually change the width each time. I have also tried not specifying a format, and reading the whole filename as a string, i.e.
read(filenumber,*) n, filename
but the filename returns weird characters (n works though).
Is there any format statement that will read the integer up to the first whitespace it finds, to replace the 'i6' I have above?
Thanks!
No - you will need to process the file "manually". Read it into a string, go looking for the first non-blank, then go looking for the next blank, then use internal io to read the relevant bits, etc.
As you have found, list directed io (using * as the format specifier) has some surprising features - one of them being that the slash character (/) in input means "stop reading here and leave remaining variables in the IO list as they were". This doesn't work well when you have paths that contain slashes!
Just for fun...
PROGRAM read_some_things
IMPLICIT NONE
! Some number bigger than most of the lines to be read.
INTEGER, PARAMETER :: line_buffer_size = 28
! Index of the start of the value of i in filename.
INTEGER, PARAMETER :: pos_i_in_filename = 10
! Index of the start of the value of j in filename.
INTEGER, PARAMETER :: pos_j_in_filename = 13
CALL process_a_file
CONTAINS
SUBROUTINE process_a_file
INTEGER :: unit ! Unit number for IO.
CHARACTER(:), ALLOCATABLE :: line ! A line from the file.
INTEGER :: iostat ! IOSTAT code.
CHARACTER(256) :: iomsg ! IOMSG to go with IOSTAT
INTEGER :: n, i, j ! Numbers of interest.
OPEN( NEWUNIT=unit, FILE='2015-01-09 read_some_things.txt', &
ACTION='READ', STATUS='OLD', POSITION='REWIND' )
DO
CALL read_a_line(unit, line, iostat, iomsg)
IF (IS_IOSTAT_END(iostat)) EXIT
IF (iostat /= 0) THEN
PRINT "('Error number ',I0,' reading file: ',A)", &
iostat, TRIM(iomsg)
ERROR STOP ':('
END IF
! What to do with an empty record?
! IF (LEN_TRIM(line) == 0) CALL Start_WW3
CALL chop_a_line(line, n, i, j)
PRINT "(2X,I0,1X,I0,1X,I0)", n, i, j
END DO
CLOSE(unit)
END SUBROUTINE process_a_file
! Parse a line into numbers of interest.
SUBROUTINE chop_a_line(line, n, i, j)
CHARACTER(*), INTENT(IN) :: line ! The line to chop.
INTEGER, INTENT(OUT) :: n ! Things we got...
INTEGER, INTENT(OUT) :: i
INTEGER, INTENT(OUT) :: j
! Various significnat character positions in the line.
INTEGER :: first_non_blank_pos
INTEGER :: next_blank_pos
INTEGER :: before_filename_pos
! Buffer for assembling a format specification.
CHARACTER(100) :: fmt
! Find start of first non-blank group.
first_non_blank_pos = VERIFY(line, ' ')
! Tolerate its non-existence - this may be zero.
! Find start of the following blank group, starting from after
! the beginning of the first non-blank group.
next_blank_pos = SCAN(line(first_non_blank_pos+1:), ' ')
! It had better exist. If it doesn't, confuse user.
IF (next_blank_pos == 0) ERROR STOP 'I didn''t draw any blanks'
next_blank_pos = next_blank_pos + first_non_blank_pos
! Find start of the second group of non-blanks, backup one.
before_filename_pos = VERIFY(line(next_blank_pos:), ' ')
! It had better exist. If it doesn't, annoy user.
IF (before_filename_pos == 0) ERROR STOP 'Line in file with no file!'
! Note -2 to backup one and remember position before filename.
before_filename_pos = before_filename_pos + next_blank_pos - 2
! This specifies:
! - read all prior to filename as integer,
! - then skip to start of i, read I2,
! - then skip to start of j, read I2.
WRITE (fmt, "('(I',I0,',T',I0,',I2,T',I0,',I2)')") &
before_filename_pos, &
before_filename_pos + pos_i_in_filename, &
before_filename_pos + pos_j_in_filename
READ (line, fmt) n, i, j
END SUBROUTINE chop_a_line
! Read a record into a character variable. Pretty common task...
SUBROUTINE read_a_line(unit, line, iostat, iomsg)
INTEGER, INTENT(IN) :: unit ! Unit to read from.
CHARACTER(:), INTENT(OUT), ALLOCATABLE :: line ! The record read.
INTEGER, INTENT(OUT) :: iostat ! +ve on error, -ve on eof.
CHARACTER(*), INTENT(OUT) :: iomsg ! IOMSG if iostat /= 0
! Buffer to read record fragment.
CHARACTER(line_buffer_size) :: buffer
INTEGER :: size ! Amount read per read.
line = ''
DO
! Read a bit without always advancing to the next record.
READ ( unit, "(A)", ADVANCE='NO', SIZE=size, IOSTAT=iostat, &
IOMSG=iomsg ) buffer
IF (iostat > 0) RETURN ! Bail on fail.
! Philosophical discussion about whether EOF is possible
! and SIZE /= 0 goes here (consider STREAM access).
line = line // buffer(:size) ! Append what we got.
! Exit loop on end of file or end of record.
IF (iostat < 0) EXIT
END DO
! End of record is expected, not a relevant condition to return.
IF (IS_IOSTAT_EOR(iostat)) iostat = 0
END SUBROUTINE read_a_line
END PROGRAM read_some_things
For completeness, here is my chosen solution, which relies on the fact that the string following the integer always starts with '/' (as it's a filepath):
! first determine how much whitespace is around the first integer
! and store this as string ln
read(20, '(a)') filestring
write(ln, "(I1)") INDEX(filestring, ' /')-1
rewind(20)
! use ln to as the integer width
read(filenumber,'(i'//ln//','//ldir//'x,i2,x,i2)') n,pix_i,pix_j
How can I read each character in a String? For example, I want to read each character in String "a7m4d0". After that I want to verify that each character is a character or a number. Any tips or ideas?
DATA: smth TYPE string VALUE `qwert1yua22sd123bnm,`,
index TYPE i,
length TYPE i,
char TYPE c,
num TYPE i.
length = STRLEN( smth ).
WHILE index < length.
char = smth+index(1).
TRY .
num = char.
WRITE: / num,'was a number'.
CATCH cx_sy_conversion_no_number.
WRITE: / char,'was no number'.
ENDTRY.
ADD 1 TO index.
ENDWHILE.
Here's your problem solved :P
A bit convoluted and on a recent 740 ABAP server. :)
DATA: lv_text TYPE string VALUE `a7m4d0`.
DO strlen( lv_text ) TIMES.
DATA(lv_single) = substring( val = lv_text off = sy-index - 1 len = 1 ) && ` is ` &&
COND string( WHEN substring( val = lv_text off = sy-index - 1 len = 1 ) CO '0123456789' THEN 'Numeric'
ELSE 'Character' ).
WRITE : / lv_single.
ENDDO.
Here is how you can access a single character within a string:
This example will extract out the character "t" into the variable "lv_char1".
DATA: lv_string TYPE char10,
lv_char TYPE char1.
lv_string = "Something";
lv_char1 = lv_string+4(1).
Appending "+4" to the string name specifies the offset from the start of the string (in this case 4), and "(1)" specifies the number of characters to pick up.
See the documentation here for more info:
http://help.sap.com/saphelp_nw04/Helpdata/EN/fc/eb341a358411d1829f0000e829fbfe/content.htm
If you want to look at each character in turn, you could get the length of the field using "strlen( )" and do a loop for each character.
One more approach
PERFORM analyze_string USING `qwert1yua22sd123bnm,`.
FORM analyze_string USING VALUE(p_string) TYPE string.
WHILE p_string IS NOT INITIAL.
IF p_string(1) CA '0123456798'.
WRITE: / p_string(1) , 'was a number'.
ELSE.
WRITE: / p_string(1) , 'was no number'.
ENDIF.
p_string = p_string+1.
ENDWHILE.
ENDFORM.
No DATA statements, string functions or explicit indexing required.
I know the post it's old but this might be useful, this is what use :)
DATA lv_counter TYPE i.
DO STRLEN( lv_word ) TIMES.
IF lv_word+lv_counter(1) CA '0123456789'
"It's a number
ENDIF.
lv_counter = lv_counter + 1.
ENDDO.
I'm looking for a Visual FoxPro function which is similar to the PHP function is_numeric().
I have found this, but I could not use VARTYPE or TYPE because the variable is always a character string which contains digits only.
I found ISDIGIT() function, but the manual says that it only checks the first character.
Determines whether the leftmost character of the specified character
expression is a digit (0 through 9).
ISDIGIT(cExpression)
Parameters
cExpression
Specifies the character expression that ISDIGIT( ) tests. Any
characters after the first character in cExpression are ignored.
I would create my own function using the regular expression object VBScript.RegExp
FUNCTION isNumeric( tcValue )
LOCAL oRE
oRE = CreateObject("VBScript.RegExp")
oRE.Pattern = '^[0-9]+$'
RETURN oRE.test( tcValue )
ENDFUNC
? isNumeric( '123' )
But, is there any function provided by FoxPro for this purpose?
Am I just overlooking?
Also same for ISALHPA() which determines whether the leftmost character in a character expression is alphabetic. I want to check if the variable contain only alphabets.
You can create your own function like this.
FUNCTION IsAllDigits
LPARAMETERS tcSearched, tcOptionalSearch
* tcSearched = the string of characters to test.
* tcOptionalSearch = optional, additional characters to allow.
LOCAL lcSearch
m.lcSearch = "01234567989" + IIF(VARTYPE(m.tcOptionalSearch) = "C", m.tcOptionalSearch, "")
LOCAL lcRemaining
m.lcRemaining = CHRTRAN(m.tcSearched, m.lcSearch, "")
RETURN ( LEN(m.lcRemaining) = 0 )
ENDFUNC
FUNCTION ISNUMERIC
LPARAMETERS cVal
LOCAL llNumeric, lnLen, lcChr, lnDecs, lnVal
llNumeric = VARTYPE(cVal) = "N" && Donkey has sent a numeric value
lnDecs = 0
DO CASE
CASE llNumeric
CASE VARTYPE(cVal)<>"C" && Not a character
OTHERWISE
cVal = ALLTRIM(cVal) && Trim spaces
lnLen = LEN(cVal) && How many characters
llNumeric = .T. && Assume
i = 0
DO WHILE llNumeric AND i<lnLen
i = i+1
lcChr = SUBSTR(cVal,i,1) && Get next char
lnVal = VAL(lcChr)
DO CASE
CASE lcChr = "0" && Allowed
CASE lnVal>0 && 1 - 9 OK
CASE INLIST(lcChr, "-", "+") && Allowed but ONLY at the start
llNumeric = i = 1
CASE lcChr = "." && Decimal point but ONLY one
lnDecs = lnDecs+1
llNumeric = lnDecs = 1
OTHERWISE
llNumeric = .F.
ENDCASE
ENDDO
ENDCASE
RETURN llNumeric
ENDFUNC
This could work for ISDIGIT() or ISALPHA().
Function IsAllDigits(myValue)
lReturn = .t.
FOR i = 1 TO LEN(myvalue)
IF !ISDIGIT( SUBSTR(myValue, i, 1) )
lReturn = .f.
EXIT
ENDIF
ENDFOR
RETURN lReturn
ENDFUNC
How about a one liner?
Function IsNumeric
Lparameters pString
Return m.pString == Chrtran(m.pString, Chrtran(m.pString, "0123456789", ""), "")
EndFunc
You can any other valid characters to "0123456789" like "." or ","
There is more simple to test if a string is numeric or not :
If String="123" => val('String')>0
If String="AB123" => val('String')=0
That's all...
Using only Visual Fox Pro, you can do something like this:
FUNCTION is_numeric(var_name)
error_trigger=.f. &&** first initialization
&&** evaluate("any_char") will generate an error so I'm using TRY-CATCH-ENDTRY
TRY
EVALUATE(var_name)
CATCH &&** evaluate() generates an error then the string is character
WAIT WINDOW "character"
error_trigger=.t. &&** there was an error returned by evaluate()
EXIT && stop and jump after ENDTRY
ENDTRY
IF error_trigger=.f. &&** there was no error returned by evaluate() then the string was numeric
WAIT WINDOW "numeric"
ENDIF
ENDFUNC
Then call to the function:
is_numeric("333") will show: numeric
is_numeric("aaa") will show: character
is_numeric("333a333") will show: character
I hope it will help you