How do I nest an array of strings inside another array in Fortran

77 views Asked by At

In my computational methods class, our homework problems are given as Python scripts, however my instructor said as long as we can translate them into our language of choice, we can solve them in our language of choice. I am trying to learn Fortran, so that is what I chose.

In one of our problems, the setup contains a list of lists of strings, like such:

q = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', h']]

When trying to rewrite this, I ran into a problem. Ususally, I'd define a type like character, however the values stored in each spot aren't characters, they're whole other arrays. My first thought was doing something like:

character(1) :: letters(3), arr1(3), arr2(3), arr3(2)
arr1 = ['a', 'b', 'c']
arr2 = ['d', 'e', 'f']
arr3 = ['g', 'h']
letters(1) = [arr1, arr2, arr3]

and so on. This however does not work as letters does not expect three arrays, rather three characters.

How could I go about nesting arrays inside letters? I need to do this because the problem has us indexing the arrays inside.

3

There are 3 answers

2
Steve Lionel On

You would have to do it as an array of a derived type containing an allocatable array of characters. Fortran doesn't have the concept of an array of arrays.

Here's an example:

program test
    implicit none
    type ltype
        character, allocatable :: l(:)
    end type ltype
    type(ltype), allocatable :: letters(:)
    integer :: i
    
    letters = [ltype(['a','b','c']),ltype(['d','e','f']),ltype(['g'])]
    do i=1,size(letters)
        print *, letters(i)%l
    end do
    
    end program test
0
John Alexiou On

Fortran cannot do jagged arrays (arrays of arrays). But you can do an array of a structure (user type) with each structure containing one or more arrays.

The answer @SteveLionel is (of course) the correct one, I just wanted to add a different example here to keep in the back of your mind for future use.

The module below defines a vector as an array of real values, and a matrix as an array of vectors.

module mod_matrix
use, intrinsic :: iso_fortran_env
implicit none

type vector
    real, allocatable :: data(:)
end type

type matrix
    type(vector), allocatable :: data(:)
end type

contains

pure function create_lower_triangular(n) result(M)
! creates a new matrix object representing a lower triangular matrix.
! example below for n=4
!
! M = | 1.0                 |
!     | 2.0  3.0            |
!     | 4.0  5.0  6.0       |
!     | 7.0  8.0  9.0  10.0 |
!
! each row is stored as a vector object which wraps an array.
integer, intent(in) :: n
type(matrix) :: M
type(vector) :: row
integer :: i,j,k

allocate(M%data(n))
k = 1
do i=1, n
    row = vector([(real(k+j-1), j=1,i)])
    M%data(i) = row
    k = k + i
end do

end function

subroutine show_matrix(M)
! display the contents of the matrix row-by-row on the console
type(matrix), intent(in) :: M
integer :: i, n
n = size(M%data)
do i=1, n
    print '(*(g11.3,1x))', M%data(i)%data
end do
end subroutine

end module

The code above can be used as

type(matrix) :: trig
trig = create_lower_triangular(7)
call show_matrix(trig)

with output

   1.00
   2.00        3.00
   4.00        5.00        6.00
   7.00        8.00        9.00        10.0
   11.0        12.0        13.0        14.0        15.0
   16.0        17.0        18.0        19.0        20.0        21.0
   22.0        23.0        24.0        25.0        26.0        27.0        28.0

PS. I think it is amazing that you are trying to learn Fortran in numerical methods class.

0
Vladimir F Героям слава On

It is not clear how big the array will be and what will be done with them. The approach in the other answers is the generic one, but may be overkill for a very simple arrays of strings.

If it is a small array as you show, typically used for some auxiliary purpose and not a very large one for heavy duty numerics and if you know the maximum length of the string, you can just use a Fortran array of strings.

integer, parameter :: max_len = 3
character(max_len) :: letters(3) = [character(max_len) :: 'abc', 'def', 'gh']

do j = 1, size(letters)
  do i = 1, len(letters(j))
    print *, letters(i:i)(j)
  end do
end do

This can waste some memory (the strings are all of the same length and terminated with blanks) but is very easy to set up and the code is short.