Fortran: Automatic determination of character length during initialization - fortran

In Fortran is it possible to automatically deduct the length of a character (string) when using an initializer during declaration? I want something like this (not working as it stands)
character(*) :: a = 'John Doe'
character(:) :: a = 'John Doe'
Example code
program char
implicit none
character(8) :: a = 'John Doe' !<-automatically?
write(*,*) a
write(*,*) len(a)
end program char
One correct way would be to use
character(8) :: a = 'John Doe'
by simply counting the characters, which however is error-prone. Alternatively I could make the character longer than necessary and use trim()
program char
implicit none
character(100) :: a = 'John Doe'
write(*,*) trim(a)
write(*,*) len(trim(a))
end program char
But can the 8 be determined automatically? I am aware that this is an academic question, but I am still wondering...

In addition to #AlexanderVogt's solution using parameter, you can also use automatic allocation in Fortran 2003, like this:
character(len=:), allocatable :: name
then initialise like this
name = 'John Doe'
and reset in the same way
name = 'John Doe Jr'

For parameters and dummy arguments you can use character(len=*):
program char
implicit none
character(100) :: a = 'John Doe'
character(len=*),parameter :: b = 'John Doe'
write(*,*) trim(a)
write(*,*) len_trim(a), len(a)
write(*,*) trim(b)
write(*,*) len_trim(b), len(b)
end program char
This results in:
./a.out
John Doe
8 100
John Doe
8 8

Without using allocatable:
character(len=LEN_TRIM('John Doe') :: a = 'John Doe'
If you're worried about the repetition, a parameter can be used.
Of course, an allocatable character variable has its uses, and is quite likely suitable for you, but care must be taken if you really want the variable to retain its length through various assignments:
a(:) = 'J. Doe'

Related

Is there a function that removes all nonalphanumeric numbers in Fortran?

New to Fortran,been trying to think of a function that replaces all non alphanumeric characters and spaces on a string so that it turns something like [AS:1] to AS1.
Anyone here got a clue how to?
Like I got a trimmer for open spaces to work but I don't know how to make it work for anything that's a non-alphanumeric character.
The intrinsic function SCANcan be used for membership searches.
If we have a character char of length-1 and a set set of non-zero size, then we have that SCAN(char, set) returns 1 (0) if char is in (not in) the set. (SCAN will return 0 if the set is of size zero.)
This functions is elemental so, for example, SCAN(char_array, set) returns an indicator for which elements of char_array are in the set.
We also have PACK which returns another array corresponding to a selection mask:
print*, PACK(char_array, SCAN(char_array,set).eq.1)
Which means we can write a subroutine like
subroutine s(in, out, keep, len)
integer, intent(in) :: len
character, intent(in) :: in(len), keep*(*)
character, intent(out) :: out(len)
integer :: i
out = PACK(in, SCAN(in,keep).eq.1, [(' ',i=1,len)])
end subroutine s
taking an input character array of size len and returning an output character array of the same size with the elements which are in keep (and trailing elements being blanks).
Naturally, we don't like working with character arrays instead of scalars, so let's provide a nice subroutine using sequence association:
subroutine strip(in, out, keep)
character(*), intent(in) :: in, keep
character(*), intent(out) :: out
call s(in, out, keep, LEN(in))
end subroutine
Complete example:
module stripping
implicit none
private s
contains
subroutine strip(in, out, keep)
character(*), intent(in) :: in, keep
character(*), intent(out) :: out
call s(in, out, keep, len(in))
end subroutine strip
subroutine s(in, out, keep, len)
integer, intent(in) :: len
character, intent(in) :: in(len), keep*(*)
character, intent(out) :: out(len)
integer :: i
out = PACK(in, SCAN(in,keep).eq.1, [(' ',i=1,len)])
end subroutine s
end module stripping
program test
use stripping, only : strip
implicit none
character(10) in, out
character(*), parameter :: keep="abcd"
in = "a1b*2sdc]a"
call strip(in, out, keep)
print*, TRIM(out)
end program
There are doubtless better and clearer ways to do this: this answer mostly serves to have you think about what intrinsic functions there are and how they can be applied. There isn't an intrinsic function to do what you want in one step.
You need to define an external verification procedure that tells if a given character is to be kept or discarded. Then replace the equivalence check typically done in replace routines with this external function.
Here is an implementation that achieves the goal,
module str_mod
implicit none
integer, parameter :: IK = kind(0)
integer, parameter :: SK = kind("a")
integer, parameter :: LK = kind(.false.)
contains
! Returns `.true.` if it is a desired character.
function isDesired(char) result(desired)
character(1, SK), intent(in) :: char
logical(LK) :: desired
desired = (SK_"0" <= char .and. char <= SK_"9") .or. &
(SK_"A" <= char .and. char <= SK_"Z") .or. &
(SK_"a" <= char .and. char <= SK_"z")
end function
function replace(str, isDesired) result(strrep)
character(*, SK), intent(in) :: str
character(:, SK), allocatable :: strrep
procedure(logical(LK)) :: isDesired
integer(IK) :: i, counter
allocate(character(len(str), SK) :: strrep)
counter = 0_IK
do i = 1, len(str, kind = IK)
if (.not. isDesired(str(i:i))) cycle
counter = counter + 1_IK
strrep(counter:counter) = str(i:i)
end do
strrep = strrep(1:counter)
end function
end module str_mod
use str_mod
print *, replace("Fortran", isDesired)
print *, replace("(Fortran)", isDesired)
print *, replace("(Fortran) (Is) [_A_] (GREAT) {language}.", isDesired)
print *, replace("[AS:1]", isDesired)
end
Here is the program output,
Fortran
Fortran
FortranIsAGREATlanguage
AS1
Test it here. Note that this implementation performs two allocations of the output strings, which you could likely avoid by counting the desired characters in str first and then allocating the output string to the proper size and filling it with the identified characters. But any performance gain or difference will likely be negligible in most scenarios. You would likely see better performance benefits if you instead reimplement replace() in the above as a subroutine with str input argument being an allocatable with intent(inout). In such a case, you can avoid an extra copy on exit from the procedure, which can lead to ~25% runtime speedup for small arrays. But again, such performance concerns become relevant only when you call replace() on the order of billions of times.
You would have to write a function to do it. As inspiration, here's a subroutine I recently wrote to do SQL "escaping" of quotes in a string. The key here is having separate indexes for input and output position. Your requirement is even easier - if the character is not alphanumeric or space, don't advance the output length. There are several ways of doing the comparison, an exercise left for the reader.
subroutine escape (text)
character(*), intent(inout) :: text
character(100) :: newtext
integer i,j
newtext = ' '
j = 1
do i=1,len_trim(text)
if (text(i:i) == '"') then
newtext(j:j) = "\"
j = j + 1
end if
newtext(j:j) = text(i:i)
j = j + 1
end do
text = newtext
end subroutine escape

How can I change number to character without any transformation in fortran? [duplicate]

I have a program in Fortran that saves the results to a file. At the moment I open the file using
OPEN (1, FILE = 'Output.TXT')
However, I now want to run a loop, and save the results of each iteration to the files 'Output1.TXT', 'Output2.TXT', 'Output3.TXT', and so on.
Is there an easy way in Fortran to constuct filenames from the loop counter i?
you can write to a unit, but you can also write to a string
program foo
character(len=1024) :: filename
write (filename, "(A5,I2)") "hello", 10
print *, trim(filename)
end program
Please note (this is the second trick I was talking about) that you can also build a format string programmatically.
program foo
character(len=1024) :: filename
character(len=1024) :: format_string
integer :: i
do i=1, 10
if (i < 10) then
format_string = "(A5,I1)"
else
format_string = "(A5,I2)"
endif
write (filename,format_string) "hello", i
print *, trim(filename)
enddo
end program
A much easier solution IMHO ...................
character(len=8) :: fmt ! format descriptor
fmt = '(I5.5)' ! an integer of width 5 with zeros at the left
i1= 59
write (x1,fmt) i1 ! converting integer to string using a 'internal file'
filename='output'//trim(x1)//'.dat'
! ====> filename: output00059.dat
Well here is a simple function which will return the left justified string version of an integer:
character(len=20) function str(k)
! "Convert an integer to string."
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
And here is a test code:
program x
integer :: i
do i=1, 100
open(11, file='Output'//trim(str(i))//'.txt')
write (11, *) i
close (11)
end do
end program x
I already showed this elsewhere on SO (How to use a variable in the format specifier statement? , not an exact duplicate IMHO), but I think it is worthwhile to place it here. It is possible to use the techniques from other answers for this question to make a simple function
function itoa(i) result(res)
character(:),allocatable :: res
integer,intent(in) :: i
character(range(i)+2) :: tmp
write(tmp,'(i0)') i
res = trim(tmp)
end function
which you can use after without worrying about trimming and left-adjusting and without writing to a temporary variable:
OPEN(1, FILE = 'Output'//itoa(i)//'.TXT')
It requires Fortran 2003 because of the allocatable string.
For a shorten version.
If all the indices are smaller than 10, then use the following:
do i=0,9
fid=100+i
fname='OUTPUT'//NCHAR(i+48) //'.txt'
open(fid, file=fname)
!....
end do
For a general version:
character(len=5) :: charI
do i = 0,100
fid = 100 + i
write(charI,"(A)"), i
fname ='OUTPUT' // trim(charI) // '.txt'
open(fid, file=fname)
end do
That's all.
I've tried #Alejandro and #user2361779 already but it gives me an unsatisfied result such as file 1.txt or file1 .txt instead of file1.txt. However i find the better solution:
...
integer :: i
character(len=5) :: char_i ! use your maximum expected len
character(len=32) :: filename
write(char_i, '(I5)') i ! convert integer to char
write(filename, '("path/to/file/", A, ".dat")') trim(adjustl(char_i))
...
Explanation:
e.g. set i = 10 and write(char_i, '(I5)') i
char_i gives " 10" ! this is original value of char_i
adjustl(char_i) gives "10 " ! adjust char_i to the left
trim(adjustl(char_i)) gives "10" ! adjust char_i to the left then remove blank space on the right
I think this is a simplest solution that give you a dynamical length filename without any legacy blank spaces from integer to string conversion process.
Try the following:
....
character(len=30) :: filename ! length depends on expected names
integer :: inuit
....
do i=1,n
write(filename,'("output",i0,".txt")') i
open(newunit=iunit,file=filename,...)
....
close(iunit)
enddo
....
Where "..." means other appropriate code for your purpose.
To convert an integer to a string:
integer :: i
character* :: s
if (i.LE.9) then
s=char(48+i)
else if (i.GE.10) then
s=char(48+(i/10))// char(48-10*(i/10)+i)
endif
Here is my subroutine approach to this problem. it transforms an integer in the range 0 : 9999 as a character. For example, the INTEGER 123 is transformed into the character 0123. hope it helps.
P.S. - sorry for the comments; they make sense in Romanian :P
subroutine nume_fisier (i,filename_tot)
implicit none
integer :: i
integer :: integer_zeci,rest_zeci,integer_sute,rest_sute,integer_mii,rest_mii
character(1) :: filename1,filename2,filename3,filename4
character(4) :: filename_tot
! Subrutina ce transforma un INTEGER de la 0 la 9999 in o serie de CARACTERE cu acelasi numar
! pentru a fi folosite in numerotarea si denumirea fisierelor de rezultate.
if(i<=9) then
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+0)
filename4=char(48+i)
elseif(i>=10.and.i<=99) then
integer_zeci=int(i/10)
rest_zeci=mod(i,10)
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=100.and.i<=999) then
integer_sute=int(i/100)
rest_sute=mod(i,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+0)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=1000.and.i<=9999) then
integer_mii=int(i/1000)
rest_mii=mod(i,1000)
integer_sute=int(rest_mii/100)
rest_sute=mod(rest_mii,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+integer_mii)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
endif
filename_tot=''//filename1//''//filename2//''//filename3//''//filename4//''
return
end subroutine nume_fisier

How to open,read and write multiple files in Fortran using two sets of loops? [duplicate]

I have a program in Fortran that saves the results to a file. At the moment I open the file using
OPEN (1, FILE = 'Output.TXT')
However, I now want to run a loop, and save the results of each iteration to the files 'Output1.TXT', 'Output2.TXT', 'Output3.TXT', and so on.
Is there an easy way in Fortran to constuct filenames from the loop counter i?
you can write to a unit, but you can also write to a string
program foo
character(len=1024) :: filename
write (filename, "(A5,I2)") "hello", 10
print *, trim(filename)
end program
Please note (this is the second trick I was talking about) that you can also build a format string programmatically.
program foo
character(len=1024) :: filename
character(len=1024) :: format_string
integer :: i
do i=1, 10
if (i < 10) then
format_string = "(A5,I1)"
else
format_string = "(A5,I2)"
endif
write (filename,format_string) "hello", i
print *, trim(filename)
enddo
end program
A much easier solution IMHO ...................
character(len=8) :: fmt ! format descriptor
fmt = '(I5.5)' ! an integer of width 5 with zeros at the left
i1= 59
write (x1,fmt) i1 ! converting integer to string using a 'internal file'
filename='output'//trim(x1)//'.dat'
! ====> filename: output00059.dat
Well here is a simple function which will return the left justified string version of an integer:
character(len=20) function str(k)
! "Convert an integer to string."
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
And here is a test code:
program x
integer :: i
do i=1, 100
open(11, file='Output'//trim(str(i))//'.txt')
write (11, *) i
close (11)
end do
end program x
I already showed this elsewhere on SO (How to use a variable in the format specifier statement? , not an exact duplicate IMHO), but I think it is worthwhile to place it here. It is possible to use the techniques from other answers for this question to make a simple function
function itoa(i) result(res)
character(:),allocatable :: res
integer,intent(in) :: i
character(range(i)+2) :: tmp
write(tmp,'(i0)') i
res = trim(tmp)
end function
which you can use after without worrying about trimming and left-adjusting and without writing to a temporary variable:
OPEN(1, FILE = 'Output'//itoa(i)//'.TXT')
It requires Fortran 2003 because of the allocatable string.
For a shorten version.
If all the indices are smaller than 10, then use the following:
do i=0,9
fid=100+i
fname='OUTPUT'//NCHAR(i+48) //'.txt'
open(fid, file=fname)
!....
end do
For a general version:
character(len=5) :: charI
do i = 0,100
fid = 100 + i
write(charI,"(A)"), i
fname ='OUTPUT' // trim(charI) // '.txt'
open(fid, file=fname)
end do
That's all.
I've tried #Alejandro and #user2361779 already but it gives me an unsatisfied result such as file 1.txt or file1 .txt instead of file1.txt. However i find the better solution:
...
integer :: i
character(len=5) :: char_i ! use your maximum expected len
character(len=32) :: filename
write(char_i, '(I5)') i ! convert integer to char
write(filename, '("path/to/file/", A, ".dat")') trim(adjustl(char_i))
...
Explanation:
e.g. set i = 10 and write(char_i, '(I5)') i
char_i gives " 10" ! this is original value of char_i
adjustl(char_i) gives "10 " ! adjust char_i to the left
trim(adjustl(char_i)) gives "10" ! adjust char_i to the left then remove blank space on the right
I think this is a simplest solution that give you a dynamical length filename without any legacy blank spaces from integer to string conversion process.
Try the following:
....
character(len=30) :: filename ! length depends on expected names
integer :: inuit
....
do i=1,n
write(filename,'("output",i0,".txt")') i
open(newunit=iunit,file=filename,...)
....
close(iunit)
enddo
....
Where "..." means other appropriate code for your purpose.
To convert an integer to a string:
integer :: i
character* :: s
if (i.LE.9) then
s=char(48+i)
else if (i.GE.10) then
s=char(48+(i/10))// char(48-10*(i/10)+i)
endif
Here is my subroutine approach to this problem. it transforms an integer in the range 0 : 9999 as a character. For example, the INTEGER 123 is transformed into the character 0123. hope it helps.
P.S. - sorry for the comments; they make sense in Romanian :P
subroutine nume_fisier (i,filename_tot)
implicit none
integer :: i
integer :: integer_zeci,rest_zeci,integer_sute,rest_sute,integer_mii,rest_mii
character(1) :: filename1,filename2,filename3,filename4
character(4) :: filename_tot
! Subrutina ce transforma un INTEGER de la 0 la 9999 in o serie de CARACTERE cu acelasi numar
! pentru a fi folosite in numerotarea si denumirea fisierelor de rezultate.
if(i<=9) then
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+0)
filename4=char(48+i)
elseif(i>=10.and.i<=99) then
integer_zeci=int(i/10)
rest_zeci=mod(i,10)
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=100.and.i<=999) then
integer_sute=int(i/100)
rest_sute=mod(i,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+0)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=1000.and.i<=9999) then
integer_mii=int(i/1000)
rest_mii=mod(i,1000)
integer_sute=int(rest_mii/100)
rest_sute=mod(rest_mii,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+integer_mii)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
endif
filename_tot=''//filename1//''//filename2//''//filename3//''//filename4//''
return
end subroutine nume_fisier

passing allocatable character through two levels of procedure calls fails

I am experiencing an allocation failure when using allocatable character strings as optional arguments. The problem only occurs when I call through two levels of procedures. In my actual code call get_level1() (see below) represents a call to a list data structure and call get_level2() represents the list calling the same type of accessor function on one of its records. I have stripped down an example to the bare minimum that adequately reproduces the problem.
In the code below when I call get_level2 directly the expected character string is returned through the optional argument. When I call get_level1 which in turn calls get_level2 allocation of the optional dummy argument fails. Using gdb I find the allocation attempt to create a character*1635... when it gets back to the actual argument is obviously has an integer overflow because it thinks the allocation is character*-283635612...
My actual code has many optional arguments not just one. As a simple example I added an optional integer argument. This time instead of a segmentation fault I get a null string.
In the second example the integer argument works regardless of using the character argument. (I would expect this since no dynamic allocation is being performed) The integer's presence has no effect on the character. I have also tried changing the intent to (inout). This does not change the behavior, though I did not expect it to. [I believe that intent(out) causes the actual argument to deallocate first, and intent(inout) retains the actual argument's allocation state]
call get_level1( NUM=n ) ! works
call get_level1( NUM=n, TEXT=words ) ! fails
call get_level1( TEXT=words ) ! fails
my compile cmd is:
gfortran -Wall -g -std=f2008ts stest1.f08 -o stest
Environment
Linux 4.15.0-42-generic #45-Ubuntu SMP x86_64 GNU/Linux
GNU Fortran (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0
Example with one optional argument
module stest1
implicit none
character(:), allocatable :: data
contains
subroutine get_level2( TEXT )
implicit none
character(:), optional, allocatable, intent(out) :: TEXT
if ( PRESENT( TEXT ) ) then
TEXT = 'Prefix: ' // data // ' :postfix'
end if
end subroutine get_level2
subroutine get_level1( TEXT )
implicit none
character(:), optional, allocatable, intent(out) :: TEXT
call get_level2( TEXT )
end subroutine get_level1
end module stest1
program main
use stest1
implicit none
character(:), allocatable :: words
data = 'Hello Doctor'
call get_level1( words )
write(*,100) words
100 format( 'words = [',A,']' )
end program main
Example with two optional arguments
module stest2
implicit none
character(:), allocatable :: data
integer :: count
contains
subroutine get_level2( TEXT, NUM )
implicit none
character(:), optional, allocatable, intent(out) :: TEXT
integer, optional, intent(out) :: NUM
if ( PRESENT( TEXT ) ) then
TEXT = 'Prefix: ' // data // ' :postfix'
end if
if ( PRESENT( NUM ) ) then
NUM = count
end if
end subroutine get_level2
subroutine get_level1( TEXT, NUM )
implicit none
character(:), optional, allocatable, intent(out) :: TEXT
integer, optional, intent(out) :: NUM
call get_level2( NUM=NUM, TEXT=TEXT )
end subroutine get_level1
end module stest2
program main
use stest2
implicit none
character(:), allocatable :: words
integer :: n
count = 42
data = 'Hello Doctor'
call get_level1( TEXT=words )
write(*,100) words
write(*,110) n
100 format( 'words = [',A,']' )
110 format( 'N = [',I0,']' )
end program main
You seem to have hit a compiler bug. I can reproduce the issue on gfortran 8.2.1:
Operating system error: Cannot allocate memory
Memory allocation failure in xrealloc
Error termination. Backtrace:
#0 0x7f9c0314f107 in write_character
at ../../../libgfortran/io/write.c:1399
#1 0x7f9c03153e66 in list_formatted_write_scalar
at ../../../libgfortran/io/write.c:1872
#2 0x400c78 in MAIN__
at /tmp/test.F90:43
#3 0x400cbe in main
at /tmp/test.F90:34
but in 5.1.1 I see the correct output:
Prefix: Hello Doctor :postfix
With the following work-around, I got it to work:
subroutine get_level1( TEXT )
implicit none
character(:), optional, allocatable, intent(out) :: TEXT
character(:), allocatable :: tmp
if ( PRESENT( TEXT ) ) then
call get_level2( tmp )
TEXT = tmp
else
call get_level2( )
endif
end subroutine get_level1
It is a bug in the compiler, and still stands in gfortran v9.0.0 (experimental) on Windows. You should report it with the vendor.
I've done some tests and it seems that the failure only happens when: passing a present optional argument as an actual argument corresponding to a dummy argument that is character(:), allocatable, optional. Any variation in the previous sentence seems to avoid the bug and produce the correct result.
I reduced your example to a minimal test-case:
program main
implicit none
character(:), allocatable :: txt
call sub1(txt)
print *, "main ", len(txt), txt ! prints: main 0 (or throws segfault)
contains
subroutine sub1(txt)
character(:), allocatable, optional :: txt
call sub2(txt)
print *, "sub1 ", len(txt), txt ! prints: sub1 0 (or throws segfault)
end
subroutine sub2(txt)
character(:), allocatable, optional :: txt
if(present(txt)) txt = "message"
print *, "sub2 ", len(txt), txt ! prints: sub2 7 message
end
end
The inspection inside sub2 shows that the assignment actually works there. The problem seems to happen when associating that dummy to the actual argument inside sub1. Hmm...
Again, any variation of the pattern character(:), allocatable, optional dummies produces the correct result in my tests. So, I suggest you to flexibilize at least one of the previous conditions to circumvent the buggy thing. There are some suggestions:
1. non-allocatable optional character works, no matter if fixed or assumed length;
Here is an example with fixed-lenght variable and assumed-length arguments.
Advantage: Easy to refactor, less disruptive/intrusive.
Disadvantage: Must estimate the length of the variable beforehand, wastes storage.
program option1
implicit none
character(10) :: txt
call sub1(txt)
print *, "main ", len(txt), txt ! prints: main 10 message
contains
subroutine sub1(txt)
character(*), optional :: txt
call sub2(txt)
print *, "sub1 ", len(txt), txt ! prints: sub1 10 message
end
subroutine sub2(txt)
character(*), optional :: txt
if(present(txt)) txt = "message"
print *, "sub2 ", len(txt), txt ! prints: sub1 10 message
end
end
2. non-optional, on either the actual argument passed from sub1 or on the dummy argument in sub2, also makes it work;
Of course, if you can refactor your code to avoid this situation, that would be the better solution. You could use generic interfaces to achieve a similar result, for example. Or, as you said in the comment, "using local variables at level1 and passing all the optional arguments to the lower level".
Disadvantage: May need to change interfaces of the lower-level procedures.
Advantage: Wouldn't be a problem if they are private module procedures; It's an implementation detail.
Consider the following approach, that hacks the bug and avoid passing an optional argument, so doesn't change the procedure's signature:
program option2
implicit none
character(:), allocatable :: txt
call sub1(txt)
print *, "main ", len(txt), txt ! prints: main 7 message
contains
subroutine sub1(txt)
character(:), allocatable, optional :: txt
character(:), allocatable :: txt_
if(present(txt)) then
! txt_ isn't optional, so the bug doesn't fire
call sub2(txt_)
txt = txt_
end if
print *, "sub1 ", len(txt), txt ! prints: sub1 7 message
end
subroutine sub2(txt)
character(:), allocatable, optional :: txt
print *, present(txt)
if(present(txt)) txt = "message"
print *, "sub2 ", len(txt), txt ! prints: sub2 7 message
end
end
3. with any other type it works too, no matter the attributes (even a derived-type with allocatable character component). Although, changes on the rank or kind don't count.
I will show you two options involving derived types: one with allocatable character length component; the other with parameterized derived type.
Advantage: You can keep your code structure, and all the optional stuff. Storage overhead is low. You could even extend your DT with methods and tailor it to your problem.
Disadvantage: Maybe too much hassle for little. PDT is cool, but is a new (and buggy) feature in gfortran.
program option3a
! using a derived type with allocatable character length component.
implicit none
type :: string
character(:), allocatable :: chars
end type
type(string) :: txt
call sub1(txt)
print *, "main ", len(txt%chars), txt%chars ! prints: main 7 message
contains
subroutine sub1(txt)
type(string), optional :: txt
call sub2(txt)
print *, "sub1 ", len(txt%chars), txt%chars ! prints: sub1 7 message
end
subroutine sub2(txt)
type(string), optional :: txt
if(present(txt)) txt = string("message")
print *, "sub2 ", len(txt%chars), txt%chars ! prints: sub2 7 message
end
end
program option3b
! using a parameterized derived type, you can practically mimic the intrinsic
! character type behavior, with the possibility to add custom behavior.
! but its still raw in gfortran.
implicit none
type :: string(len)
integer, len :: len
character(len) :: chars
end type
type(string(:)), allocatable :: txt
call sub1(txt)
print *, "main ", txt%len, txt ! prints: main 7 7 message (a lil bug of gfortran)
contains
subroutine sub1(txt)
type(string(:)), allocatable, optional :: txt
call sub2(txt)
print *, "sub1 ", txt%len, txt ! prints: main 7 7 message
end
subroutine sub2(txt)
type(string(:)), allocatable, optional :: txt
! the following fails with gfortran, however it's valid syntax
! if(present(txt)) txt = string(7)("message")
allocate(string(7) :: txt)
if(present(txt)) txt%chars = "message"
print *, "sub2 ", txt%len, txt ! prints: main 7 7 message
end
end
Summing up: you can change your compiler or choose any of those (or other) way to circunvent this bug and keep working, until your compiler vendor address the issue.

Saving the results of a do loop in different files [duplicate]

I have a program in Fortran that saves the results to a file. At the moment I open the file using
OPEN (1, FILE = 'Output.TXT')
However, I now want to run a loop, and save the results of each iteration to the files 'Output1.TXT', 'Output2.TXT', 'Output3.TXT', and so on.
Is there an easy way in Fortran to constuct filenames from the loop counter i?
you can write to a unit, but you can also write to a string
program foo
character(len=1024) :: filename
write (filename, "(A5,I2)") "hello", 10
print *, trim(filename)
end program
Please note (this is the second trick I was talking about) that you can also build a format string programmatically.
program foo
character(len=1024) :: filename
character(len=1024) :: format_string
integer :: i
do i=1, 10
if (i < 10) then
format_string = "(A5,I1)"
else
format_string = "(A5,I2)"
endif
write (filename,format_string) "hello", i
print *, trim(filename)
enddo
end program
A much easier solution IMHO ...................
character(len=8) :: fmt ! format descriptor
fmt = '(I5.5)' ! an integer of width 5 with zeros at the left
i1= 59
write (x1,fmt) i1 ! converting integer to string using a 'internal file'
filename='output'//trim(x1)//'.dat'
! ====> filename: output00059.dat
Well here is a simple function which will return the left justified string version of an integer:
character(len=20) function str(k)
! "Convert an integer to string."
integer, intent(in) :: k
write (str, *) k
str = adjustl(str)
end function str
And here is a test code:
program x
integer :: i
do i=1, 100
open(11, file='Output'//trim(str(i))//'.txt')
write (11, *) i
close (11)
end do
end program x
I already showed this elsewhere on SO (How to use a variable in the format specifier statement? , not an exact duplicate IMHO), but I think it is worthwhile to place it here. It is possible to use the techniques from other answers for this question to make a simple function
function itoa(i) result(res)
character(:),allocatable :: res
integer,intent(in) :: i
character(range(i)+2) :: tmp
write(tmp,'(i0)') i
res = trim(tmp)
end function
which you can use after without worrying about trimming and left-adjusting and without writing to a temporary variable:
OPEN(1, FILE = 'Output'//itoa(i)//'.TXT')
It requires Fortran 2003 because of the allocatable string.
For a shorten version.
If all the indices are smaller than 10, then use the following:
do i=0,9
fid=100+i
fname='OUTPUT'//NCHAR(i+48) //'.txt'
open(fid, file=fname)
!....
end do
For a general version:
character(len=5) :: charI
do i = 0,100
fid = 100 + i
write(charI,"(A)"), i
fname ='OUTPUT' // trim(charI) // '.txt'
open(fid, file=fname)
end do
That's all.
I've tried #Alejandro and #user2361779 already but it gives me an unsatisfied result such as file 1.txt or file1 .txt instead of file1.txt. However i find the better solution:
...
integer :: i
character(len=5) :: char_i ! use your maximum expected len
character(len=32) :: filename
write(char_i, '(I5)') i ! convert integer to char
write(filename, '("path/to/file/", A, ".dat")') trim(adjustl(char_i))
...
Explanation:
e.g. set i = 10 and write(char_i, '(I5)') i
char_i gives " 10" ! this is original value of char_i
adjustl(char_i) gives "10 " ! adjust char_i to the left
trim(adjustl(char_i)) gives "10" ! adjust char_i to the left then remove blank space on the right
I think this is a simplest solution that give you a dynamical length filename without any legacy blank spaces from integer to string conversion process.
Try the following:
....
character(len=30) :: filename ! length depends on expected names
integer :: inuit
....
do i=1,n
write(filename,'("output",i0,".txt")') i
open(newunit=iunit,file=filename,...)
....
close(iunit)
enddo
....
Where "..." means other appropriate code for your purpose.
To convert an integer to a string:
integer :: i
character* :: s
if (i.LE.9) then
s=char(48+i)
else if (i.GE.10) then
s=char(48+(i/10))// char(48-10*(i/10)+i)
endif
Here is my subroutine approach to this problem. it transforms an integer in the range 0 : 9999 as a character. For example, the INTEGER 123 is transformed into the character 0123. hope it helps.
P.S. - sorry for the comments; they make sense in Romanian :P
subroutine nume_fisier (i,filename_tot)
implicit none
integer :: i
integer :: integer_zeci,rest_zeci,integer_sute,rest_sute,integer_mii,rest_mii
character(1) :: filename1,filename2,filename3,filename4
character(4) :: filename_tot
! Subrutina ce transforma un INTEGER de la 0 la 9999 in o serie de CARACTERE cu acelasi numar
! pentru a fi folosite in numerotarea si denumirea fisierelor de rezultate.
if(i<=9) then
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+0)
filename4=char(48+i)
elseif(i>=10.and.i<=99) then
integer_zeci=int(i/10)
rest_zeci=mod(i,10)
filename1=char(48+0)
filename2=char(48+0)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=100.and.i<=999) then
integer_sute=int(i/100)
rest_sute=mod(i,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+0)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
elseif(i>=1000.and.i<=9999) then
integer_mii=int(i/1000)
rest_mii=mod(i,1000)
integer_sute=int(rest_mii/100)
rest_sute=mod(rest_mii,100)
integer_zeci=int(rest_sute/10)
rest_zeci=mod(rest_sute,10)
filename1=char(48+integer_mii)
filename2=char(48+integer_sute)
filename3=char(48+integer_zeci)
filename4=char(48+rest_zeci)
endif
filename_tot=''//filename1//''//filename2//''//filename3//''//filename4//''
return
end subroutine nume_fisier