Smart printing of integers in fortran90 - fortran

I'm learning Fortran90 after a brief introduction to Fortran77 a few years ago. When printing integers in Fortran, you must specify how many spaces you want to reserve for printing the integer. Consider this program...
implicit none
integer :: i
i = 123
write(*, '(A, I3, A)') "'", i, "'" !3 spaces for output = no padding
write(*, '(A, I5, A)') "'", i, "'" !5 is too many, so output is padded
write(*, '(A, I2, A)') "'", i, "'" !2 is too few, so output is jibberish
write(*, '(A, I:, A)') "'", i, "'" !Default behavior
end program
...which generates the following output.
'123'
' 123'
'**'
' 123'
How do I allocate the correct amount of space for integer printing when I do not know how many digits are in the integer?
Update: If your compiler is F95-compliant, you can use the I0 edit descriptor (i.e., put '(A, I0, A)' for the second argument of the write function in my example above. Thanks #janneb!

Use the I0 edit descriptor. Well, to be pedantic IIRC that is Fortran 95, so if you're really strict about no more than F90, then I suppose this won't 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.

Format: add trailing spaces to character output to left-justify

How do you format a string to have constant width and be left-justified? There is the Aw formatter, where w denotes desired width of character output, but it prepends the spaces if w > len(characters), instead of appending them.
When I try
44 format(A15)
print 44, 'Hi Stack Overflow'
I get
> Hi Stack Overflow<
instead of
>Hi Stack Overflow <
Is there any simple Fortran formatting solution that solves this?
As noted in the question, the problem is that when a character expression of length shorter than the output field width the padding spaces appear before the character expression. What we want is for the padding spaces to come after our desired string.
There isn't a simple formatting solution, in the sense of a natural edit descriptor. However, what we can do is output an expression with sufficient trailing spaces (which count towards the length).
For example:
print '(A50)', 'Hello'//REPEAT(' ',50)
or
character(50) :: hello='Hello'
print '(A50)', hello
or even
print '(A50)', [character(50) :: 'hello']
That is, in each case the output item is a character of length (at least) 50. Each will be padded on the right with blanks.
If you chose, you could even make a function which returns the extended (left-justified) expression:
print '(A50)', right_pad('Hello')
where the function is left as an exercise for the reader.
To complete #francescalus excellent answer for future reference, the proposed solution also works in case of allocatables in place of string literals:
character(len=:), allocatable :: a_str
a_str = "foobar"
write (*,"(A,I4)") a_str, 42
write (*,"(A,I4)") [character(len=20) :: a_str], 42
will output
foobar 42
foobar 42
a bit ugly but you can concatenate a blank string:
character*15 :: blank=' '
print 44, 'Hi Stack Overflow'//blank
program test ! Write left justified constant width character columns
! declare some strings.
character(len=32) :: str1,str2,str3,str4,str5,str6
! define the string values.
str1 = " Nina "; str2 = " Alba " ; str3 = " blue "
str4 = " Jamil "; str5 = " Arnost " ; str6 = " green "
write(*,'(a)') "123456789012345678901234567890"
! format to 3 columns 10 character wide each.
! and adjust the stings to the left.
write(*,'(3(a10))') adjustl(str1), adjustl(str2), adjustl(str3)
write(*,'(3(a10))') adjustl(str4), adjustl(str5), adjustl(str6)
end program test
$ ./a.out
123456789012345678901234567890
Nina Alba blue
Jamil Arnost green
adjustl() moves leading spaces to the end of the string.
I suggest that you do not limit the number of output characters.
Change it to the following will work:
44 format(A)
print 44, 'Hi Stack Overflow'

Error counting substrings from file in Fortran 95

I'm trying to write a code in Fortran 95 that read strings from a file and then print a frequency table of letters from the file. The file has a text like the example below, with no more than 128 characters per line:
Ablblb lbla sdrtwfwefw
Waerfaw efeafawef awef
Pefwae fwefawefw efawe
Fcicnj ioejo, o njcdid
Pweko jai, wadwed awdd
So I did one program that is working fine for the first letter (A or a), but the counter doesn't work for the rest of the letters and always print "0". This is what I did:
program read_file
implicit none
integer :: count, i, j, k, n
character(len=80) :: arch
character(len=128) :: line
character :: c, d
logical :: rexist
print *,'Name of the file (example: "text1.txt"): '
read *,arch
count=0
inquire(file=arch, exist=rexist)
if(rexist) then
open(unit=10, file=arch)
j=97
do i=65,90
c=achar(i)
d=achar(j)
do
read(unit=10, fmt='(A128)', end=999)line
n=len_trim(line)
do k=1,n
if(line(k:k)==c .or. line(k:k)==d)then
count=count+1
end if
end do
cycle
999 exit
end do
print *, "Letter ", c, " or ", d, " Total: ", count
j=j+1
count=0
end do
close(unit=10)
else
print *,"Invalid file!"
end if
end program read_file
The first count works fine (letter A or a) but it prints "0" for the rest of the letters. Is there something wrong with the DO loop that doesn't reset the variable count properly?

Removing whitespace in string

I have the following code:
program main
character (len=15) :: abc = "te st tex t"
print *, trim(abc)
end program main
Which outputs:
te st tex t
I excepted all the whitespace to be removed but it wasn't. How can I remove all the whitespace from the string?
Trim will remove spaces only at the edges, not in the middle (this is common behaviour on almost all languages/libraries). If you want to remove all spaces in the string, you will have to create your own function to do this, iterating through the string.
Ex.:
program Test
implicit none
! Variables
character(len=200) :: string
! Body of Test
string = 'Hello World 7 9'
print *, string
call StripSpaces (string)
print *, string
contains
subroutine StripSpaces(string)
character(len=*) :: string
integer :: stringLen
integer :: last, actual
stringLen = len (string)
last = 1
actual = 1
do while (actual < stringLen)
if (string(last:last) == ' ') then
actual = actual + 1
string(last:last) = string(actual:actual)
string(actual:actual) = ' '
else
last = last + 1
if (actual < last) &
actual = last
endif
end do
end subroutine
end program Test
This was tested on intel compiler, not on gfortran, but I think it will work.
I was able to do this using the variable string library described here ( http://schonfelder.co.uk/is1539-2-99.htm ). The source code link is found in the introduction section of the ISO document.
Here is the code
program Console1
use ISO_VARYING_STRING
implicit none
! Body of Console1
character(LEN=50) :: text = 'Hello World John Mary '
character(LEN=50) :: res
print *, trim(text)
! 'Hello World John Mary'
res = REPLACE(text,' ','', every=.TRUE.)
print *, trim(res)
! 'HelloWorldJohnMary'
end program Console1
Here's a dirty, shameful way to eliminate the spaces. This is only likely to work if a compiler lays out a length-15 string in the same order and space as it would a 15-element array of characters. While this is likely to be true, and in my recent experience is true, it is not guaranteed to be so by the standard. That aside, this approach may be good enough.
! declarations
CHARACTER (len=15) :: abc = "te st tex t"
CHARACTER, DIMENSION(LEN(abc)) :: abc_array
! or CHARACTER, DIMENSION(:), ALLOCATABLE :: abc_array if your compiler supports
! automatic allocation
! transfer the string into an array of characters
abc_array = TRANSFER(abc,abc_array)
! eliminate the spaces, and transfer back to the string
abc = TRANSFER(PACK(abc_array,abc_array/=' '),abc)
! now all the spaces are at the end of abc so the following statement writes the
! string with no spaces
WRITE(*,*) TRIM(abc)
Use this approach at your own risk.
For those averse to TRANSFER perhaps a nice little recursive function would appeal. As written this depends on Fortran 2003's ability to automatically allocate character scalars, but it shouldn't be too hard to modify if your compiler doesn't support this feature yet.
RECURSIVE FUNCTION stripper(string,ch) RESULT(stripped)
CHARACTER(len=*), INTENT(in) :: string
CHARACTER, INTENT(in) :: ch
CHARACTER(:), ALLOCATABLE :: stripped
IF (LEN(string)==1) THEN
IF (string==ch) THEN
stripped = ''
ELSE
stripped = string
END IF
ELSE
IF (string(1:1)==ch) THEN
stripped = stripper(string(2:),ch)
ELSE
stripped = string(1:1)//stripper(string(2:),ch)
END IF
END IF
END FUNCTION stripper
You can try this:
program test
!erase blank space in a string
!run over every character of the string and just take every non-blank in other variable.
implicit none
character (len=100) str1,str2
integer i
str2='' !in this variable will be save non-blank spaces
str1=' a b c de ' !Test string with blank spaces
write(*,*)len_trim(str1), str1
do i=1,len(str1)
if (str1(i:i).ne.' ')str2=trim(str2)//trim(str1(i:i))
end do
write(*,*)len_trim(str2), str2
end

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'