3

How does one extract a substring of a Fortran string array? For example

program testcharindex
    implicit none
    character(len=10), dimension(5) :: s
    character(len=10), allocatable :: comp(:)
    integer, allocatable  :: i(:), n(:)
    s = (/ '1_E ', '2_S ', '3_E ', '14_E', '25_S' /)
    i = index(s,'_')
    print *,' i = ', i
    n = s(1:i-1) ! or n = s(:i-1)
    comp = s(i+1:)
    print *,' n = ', n
    print *,' comp = ', comp
end program

Compiling with gfortran yields the error:

testcharindex.f90:11:10:

n = s(1:i-1) 1 Error: Array index at (1) must be scalar

Is there any way of avoiding a do loop here? If one can extract an index of a string array, I would expect that one should be able to extract a dynamically-defined substring of a string array (without looping over the array elements). Am I too optimistic?

3 Answers 3

3

You have a couple of problems here. One of which is easily addressed (and has been in other questions: you can find these for more detail).

The line1

n = s(1:i-1)

about which the compiler is complaining is an attempt to reference a section of the array s, not an array of substrings of elements of array s. To access the substrings of the array you will need

n = s(:)(1:i-1)

However, this is related to your second problem. As the compiler complains for accessing the array section, the i must be a scalar. This is also true for the case of accessing substrings of an array. The above line will still not work.

Essentially, if you wish to access substrings of an array, each substring has to have exactly the same structure. That is, in s(:)(i:j) both i and j must be scalar integer expressions. This is motived by the desire to have every element of the returned array being the same length.

You will, then, need to use a loop.


1 As High Performance Mark once commented, there's also a problem with the assignment itself. I considered simply the expression on the right-hand side. Even corrected for a valid array substring, the expression is still a character array, which cannot be assigned to the integer scalar n as desired.

If you want the literal answer about selecting substrings then read as above. If you simply care about "converting part of a character array to an integer array" then another answer covers things well.

Sign up to request clarification or add additional context in comments.

Comments

3

In case loop is to be avoided and there is no other (simple) methods, it might be useful to define an elemental substring function and apply it to an array of strings. For example,

module str_mod
    implicit none
contains
    elemental function substr( s, a, b ) result( res )
        character(*), intent(in) :: s
        integer,      intent(in) :: a, b
        character(len(s)) :: res

        res = s( a : b )
    endfunction
endmodule

program main
    use str_mod
    implicit none
    character(10) :: s( 5 )
    integer, allocatable :: ind(:)
    character(len(s)), allocatable :: comp(:)

    s = [ '1_E ', '2_S ', '3_E ', '14_E', '25_S' ]
    ! s = [ character(len(s)) :: '1_E', '2_S', '3_E', '14_E', '25_S' ]

    print *, "test(scalar) : ", substr( s(1), 1, 2 )
    print *, "test(array ) : ", substr( s,    1, 2 )

    ind = index( s, '_' )
    comp = substr( s, 1, ind-1 )

    print *
    print *, "string (all)    : ", s
    print *, "string before _ : ", comp
    print *, "string after _  : ", substr( s, ind+1, len(s) )
endprogram

which gives (with gfortran-7.3)

 test(scalar) : 1_        
 test(array ) : 1_        2_        3_        14        25        

 string (all)    : 1_E       2_S       3_E       14_E      25_S      
 string before _ : 1         2         3         14        25        
 string after _  : E         S         E         E         S     

2 Comments

This works precisely because the function substr returns a character of length consistent and independent of the individual element being considered.
Yeah, I think the constraint that the string length is the same for all elements in a string array helps here. (Btw, my another concern is that this may fail for not-so-new compilers... So I guess using a simple loop is more "robust" :-)
2

@francescalus has already explained the error, here's my contribution towards the question OP seems really to be tackling, ie how to read the integers from a string array such as

s = (/ '1_E ', '2_S ', '3_E ', '14_E', '25_S' /)

OP wants to do this without loops, and @roygvib points us towards using an elemental function. Here's my version of such a function to read the integer from a string. This ignores any leading spaces, so should cope with strings such as 12_e. It then stops scanning at the first non-digit character (so reads 12 from a string such as 12_3).

ELEMENTAL INTEGER FUNCTION read_int(str)
  CHARACTER(*), INTENT(in) :: str
  CHARACTER(:), ALLOCATABLE :: instr

  instr = adjustl(str)
  instr = instr(1:VERIFY(instr,'0123456789')-1)
  ! if the string doesn't have a leading digit instr will be empty, return a guard value
  IF(instr=='') instr = '-999'
  READ(instr,*) read_int
END FUNCTION read_int

I trust that this is clear enough. OP could then write

n = read_int(s)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.