problems with getting substrings from strings in FORTRAN [duplicate] - fortran

This question already has answers here:
Extract substring of Fortran string array
(3 answers)
Extract a single character from a Fortran string
(1 answer)
Access character at specific index in a string in Fortran
(1 answer)
Closed 3 years ago.
I would like to evaluate if the 3rd letter of variable myline is 'C' or not.
I try this:
program main
implicit none
type line
integer :: count = 5
character(len=48) :: list = 'ABCDE'
end type
type(line) :: myline
character(len=1) :: letter = 'C'
write(*,*) myline%count, myline%list
if(myline%list(3) == letter) then
write(*,*) 'TRUE'
else
write(*,*) 'FALSE'
end if
end program
But I get:
$ /usr/local/bin/gfortran8 -mcmodel=medium -fcheck=all -Wl,-rpath=/usr/local/lib/gcc8 -o test test.f90
test.f90:15:15:
if(myline%list(3) == letter) then
1
Error: Syntax error in IF-expression at (1)
test.f90:17:5:
else
1
Error: Unexpected ELSE statement at (1)
test.f90:19:4:
end if
1
Error: Expecting END PROGRAM statement at (1)
I am using gfortran (gcc8) and the Fortran 90 standard.

In Fortran, a character substring reference always needs a start and end position. So what you want here is myline%list(3:3).
You can omit the end position (retaining the colon), for example (3:), and that means the rest of the string. Similarly you can omit the start position and it means from the first character (:3).
As a suggestion, letter would be better declared with the parameter attribute as it is a constant, but what you have would work.

Related

Combining strings and integers to make a variable format: Missing initial left parenthesis in format (Fortran)

I want to make a variable format for my "write" statement. To do so, I wrote a small program (nvari is the variable):
program VariableFormat
implicit none
integer :: x = 1,y = 2, z = 3, i, nvari
double precision :: pi = 3.14
integer, allocatable :: var(:)
integer :: A(3) = (/1,2,3/)
character(100) :: fmt,fmt2,str1,str2,str3
print*, size(A)
allocate(var(size(A)))
do i = 1, size(A)
var(i) = A(i)
end do
nvari = 2
!
! first part
!
fmt = '(a,f4.2)'
write(*,fmt) "The value of pi is ", pi
!
! second part
!
write (str1, "(1A2,1I1,1A1,1I2,1A1)") "'(", 3, "I", 15, ","
print*, str1
write(str2,'(I10)') nvari
print*, str2
write (str3, "(1A1,1I2,1A2)") "I", 15, ")'"
print*, str3
fmt2 = trim(adjustl(str1))//trim(adjustl(str2))//trim(adjustl(str3))
print*, fmt2
write(*,fmt2) x,y,z,(var(i),i=1,nvari)
end program VariableFormat
First question:
It can be seen that fmt2 is similar to fmt in the sense that in both we have '(X,Y)'.
However, while the first part of the code is executed correctly, for the second part, I have the error:
At line 32 of file VariableFormat.F90 (unit = 6, file = 'stdout')
Fortran runtime error: Missing initial left parenthesis in format
'(3I15,2I15)'
I don't understand which parenthesis is missing as fmt2 is similar to fmt.
To execute my program I use the gfortran compiler: gfortran -o binary_VariableFormat VariableFormat.F90 and then:./binary_VariableFormat
Second question:
Is there a way to write fmt2 in a more compact way?
This is perhaps more a misunderstanding about character variables and literal constants than about formats.
In
fmt = '(a,f4.2)'
we have the character variable's name (fmt) on the left-hand side of the assignment and the value to assign on the right. '(a,f4.2)' is a literal character constant and its value is given to fmt.
The two, matching, ''s act as delimiters for the value of the constant: they are not themselves part of the value.1 The value of the literal constant is (a,f4.2). Go on, print the value of fmt to check. (One actually has to do something quite different to get the value of a character variable with delimiters.)
A character value in a format must be something like (...): the first non-blank value must be a (. Because you've set the value to be like '(...)' the compiler is (quite rightly) complaining.
Where we have something like
write(str2,'(I10)') nvari
we are using this literal constant which has value (I10) not '(I10)'. If we instead had
write(str2,"'(I10)'") nvari
we'd see exactly the same complaint (or perhaps a complaint when compiling instead of running).
Finally, note that when using a FORMAT statement (many here really don't like such things), we'd not have these delimiters at all:
format (I10) ! Correct
format '(I10)' ! Badly wrong
1 Similarly, if we have literal constants used like
complex :: a = (1., 2.)
integer :: i = INT(B'001')
the delimiters for the complex and BOZ literal constants are not part of the value. Equally in an array constructor like [0,1,2,3] the square brackets are a delimiter part of the syntax rather than the value.

check if list/array of strings contains string [duplicate]

This question already has answers here:
Compact if check involving multiple strings in Fortran [duplicate]
(1 answer)
gfortran does not allow character arrays with varying component lengths
(2 answers)
Different CHARACTER lengths (3/4) in array constructor, how to trim strings - fortran
(1 answer)
Closed 5 years ago.
I would like to check if a given string is contained in a list/array of strings. In python I would do something like this:
test_string = 'alpha'
if test_string in ['alpha', 'beta', 'gamma', 'delta']:
print('found it')
else:
print('it's not there')
A simple FORTRAN program mimicking this obviously doesn't work:
PROGRAM STRINGCHECK
IMPLICIT NONE
CHARACTER(len = 10) :: test_string
CHARACTER(len = 10), DIMENSION(4) :: string_array
test_string='alpha'
string_array = ['alpha', 'beta', 'gamma', 'delta']
IF (ANY( string_array == test_string)) THEN
WRITE(*,*) 'found it'
END IF
END PROGRAM
In fact, it already fails at the array assignment with
Error: Different CHARACTER lengths (5/4) in array constructor at (1)
Is there really no other way than to check every string explicitly?:
IF ((test_string == 'alpha') .or. (test_string == 'beta') .or. &
(test_string == 'gamma') .or. (test_string == 'delta')) THEN
WRITE(*,*) 'found it'
END IF
UPDATE
The commenters are indeed right, changing the construction of the array makes the string comparison work just as expected:
CHARACTER(len = 10) :: test_string
CHARACTER(len = 10), DIMENSION(4) :: string_array
test_string='alpha'
string_array(1) = 'alpha'
string_array(2) = 'beta'
string_array(2) = 'gamma'
string_array(2) = 'delta'
IF (ANY(string_array == test_string)) THEN
WRITE(*,*) 'found it'
END IF
However, constructing the array in this way somewhat defeats the purpose of this undertaking, which would be to define the array of strings inside the IF statement, just like in my example python code.
UPDATE #2
Thanks to High Performance Mark, the answer to my problem was as easy as padding
all strings inside the array with whitespaces to make them the same length (EDIT: I replaced the [...] with (/.../) according to VladimirF's commend):
IF (ANY((/ 'alpha', 'beta ', 'gamma', 'delta'/) == 'beta')) THEN
WRITE(*,*) 'found it'
END IF
This is distinctly different from python, where the whitespace would make the two strings differ.

How do I repeat one string to match the length of another string?

I am trying to repeat the string "k" to match the length of "text" without going over the length. So it would output "treetreetreetreetreet" and not "treetreetreetreetreetree". I really dont know where to start other than just outputing more characters than needed.
PROGRAM test
IMPLICIT NONE
CHARACTER*30 :: text, k
INTEGER :: times
text = 'hello my name is anon'
k = 'tree'
times = (LEN_TRIM(text)/LEN_TRIM(k)) + 1
WRITE(*,*) REPEAT(k,times)
END PROGRAM test
First, your sample program as-is doesn't produce treetreetreetreetreetree as you expect, it actually produces tree tree tree .... When you pass the string k to REPEAT, the spaces after tree also get repeated. You should trim the string before repeating it, such as REPEAT(trim(k),times).
There are several ways to solve your main problem - I recommend using what you have so far but reducing the final result to the length you want - in this case LEN_TRIM(text). A good way to do this is to store the output of REPEAT in a temporary variable and output only a subset of this final string.
With both of these modifications and some other cleanup, your code looks like:
program main
implicit none
character(len=30) :: text, k, str
integer :: times
text = 'hello my name is anon'
k = 'tree'
times = (LEN_TRIM(text)/LEN_TRIM(k)) + 1 ! -- Note integer division
str = REPEAT(trim(k),times)
write(*,*) str(1:LEN_TRIM(text))
end program main
which gives the desired output
> gfortran main.f90 && ./a.out
treetreetreetreetreet

Fortran - String with unknown characters into substrings

I am trying to put an input string into sub-string arrays. The number of data in the input file are less than 10 but unknown. The number of spaces between each data is also unclear.
Example:
Asd B Cwqe21 Ddsw Eww
I am quite novice to Fortran, so I do not know which format I should use. My problem is that I do not know the number of data (here I assumed that there are 5), so how can I make the code work?
I tried the following which did not work:
CHARACTER (LEN=100), DIMENSION(10) :: string
READ (1,*) (string,I=1,10)
It seems that the error I got was because there was no 6th string to read and put into string(6).
I tried using the "Index" to find the space, but since I do not know how many spaces are in the string, it did not help me.
I don't know if this is more or less elegant/efficient than the standard approach in M.S.B's comment, but an interesting alternative.
integer istart,nw
character (len=100) line,wd,words(100)
open(1,file='t.dat')
read(1,'(a)')line
istart=1
nw=0
do while(len(trim(line(istart:))).gt.0)
read(line(istart:),*)wd
istart=istart+index(line(istart:),trim(wd))+len(trim(wd))
nw=nw+1
words(nw)=trim(wd)
enddo
write(*,*)trim(line)
write(*,*)('/',trim(words(k)),k=1,nw),'/'
end
An inefficient approach that is simple to program is to try to read the maximum number of items, and if this fails to successively try to read one fewer items until the read is successful, as shown below:
program xread_strings
integer, parameter :: nw = 10
character (len=1000) :: text
character (len=20) :: words(nw)
integer :: i,ierr,nread
text = "Asd B Cwqe21 Ddsw Eww"
nread = 0
do i=nw,1,-1
read (text,*,iostat=ierr) words(:i)
if (ierr == 0) then
nread = i
exit
end if
end do
if (nread > 0) write (*,*) "read ",nread," words: ",("'"//trim(words(i)) // "' ",i=1,nread)
end program xread_strings
! g95 Output:
! read 5 words: 'Asd' 'B' 'Cwqe21' 'Ddsw' 'Eww'

Fortran: Integer to String Example

I'm trying to write a simple Fortran code, for practicing. It is supposed to multiply numbers in a range. Each time, the resulting product is converted into a string because I want to see if it consists of the same digits.
I tested the way I transform an integer into a string and typed the components of the string, and everything was going correctly. Then, I need to compare the components of the string, for which I use string(number:number). But I couldn't get the code to do this correctly.
Here's the code and the output:
program test
implicit none
character(10) myString
character(1) a,b,c,d,e,f
integer::i,j,k
do i=900,901,1
j=900
k=i*j
write(*,*)'k =', k
write(myString,'(i10)') k
write(*,*)'myString = ', myString
a=myString(1:1)
b=myString(2:2)
c=myString(3:3)
d=myString(4:4)
e=myString(5:5)
f=myString(6:6)
print*,a,b,c,d,e,f
if (d==f) then
print*,'hobla'
else
print*,'fobla'
end if
end do
stop
end program test
So I defined characters: a,b,c,d,e,f to contain the components of the string. And used myString(i:i) to locate each component and store it in one of the characters a,b,c,d,e,f.
But it seems only the first two are working correctly, the rest is not being stored!
Output:
k = 810000
myString = 810000
81
fobla
k = 810900
myString = 810900
81
fobla
Notice 81. This was supposed to give 810000 the first time, and print "hobla". And give 810900 the second time and print "fobla". But this didn't happen!
Can anybody show me how to let myString accept the zeros as characters?
This statement
write(myString,'(i10)') k
writes the value of k into a 10-character field. Since k has only 6 significant digits the first 4 characters in myString are filled with blanks. Then you assign the first 6 characters of myString (that is 4 blanks and the digits 8 and 1) to the variables a,b,c,d,e,f and print them out -- 4 blanks and 2 digits.
Try printing out the other characters in positions 7..10 of myString and you should see your 'missing' digits.
Okay, so I figured out the problem. Consider the following modifications to your code:
program test
implicit none
character(10) myString
character(1) a,b,c,d,e,f
integer::i,j,k
do i=900,901,1
j=900
k=i*j
write(*,*)'k =', k
write(myString,'(i10)') k
write(*,*)'myString = ', myString
a=myString(1:1)
b=myString(2:2)
c=myString(3:3)
d=myString(4:4)
e=myString(5:5)
f=myString(6:6)
write(*,*) a
write(*,*) b
write(*,*) c
write(*,*) d
write(*,*) e
write(*,*) f
if (d==f) then
print*,'hobla'
else
print*,'fobla'
end if
end do
stop
end program test
The resulting output is:
k = 810000
myString = 810000
8
1
fobla
k = 810900
myString = 810900
8
1
fobla
This happens because the I format right-justifies numbers when printed. So there is a bunch of leading white-spaces because your number is only 6 digits while your string you are writing into is 10. So you get 4 leading spaces.
If you change your format string so you have:
write(myString,'(i10.10)') k
then instead of padding with spaces it will pad with 0's. That way you can always have digits to compare if you would rather.