概要
Fortran-langコミュニティが主導して開発しているFortraの標準ライブラリstdlibについて紹介します.紹介する内容は,stdlibの現行バージョン0.1.0に基づいています.
本記事では,stdlibに実装されているユーザ定義の文字列型や文字列型のリストについて説明します.
stdlib_string_type
stdlib_string_type
モジュールでは,可変長文字列を扱う派生型を定義しています.Fortranでもcharacter(:),allocatable
で文字列を取り扱えますが,文字型の拡張ではなく文字列という型が欲しいという要望が根強くありました.stdlib_string_type
で定義されている派生型string_type
は,そのような要望に応えるために作成されました.stdlib_string_type
では,文字列に対する組込手続と互換性のある手続も定義されています.
派生型
派生型string_type
を利用するには,stdlib_string_type
をuse
した後,他の派生型と同様に型をtype(string_type)
として変数を宣言します.
program main
use :: stdlib_string_type
type(string_type) :: str
end program main
これまでのcharacter(:), allocatable :: str
と比較するとずいぶんと宣言が簡略化されました.type(string_type)
だと,typeが2回現れていて冗長に感じるのですが,fortran-langではユーザ定義派生型の名前には_type
と付けることになっています.他の言語で_t
が組込型以外の型を表しているのと同じ理屈だと思います.
string_type
では代入演算子がオーバーロードされており,またコンストラクタもいくつか定義されています.派生型I/Oも定義されているのでprint
文やwrite
文で直接内容を出力できます.
type(string_type) :: str
! 空の文字列として初期化するコンストラクタ.
str = string_type()
print *, str
!
! 文字列に基づいて初期化するコンストラクタ.
str = string_type("string")
print *, str
! string
! 整数に基づいて初期化するコンストラクタ.
str = string_type(-huge(0))
print *, str
! -2147483647
! 論理値に基づいて初期化するコンストラクタ.
str = string_type(.true.)
print *, str
! T
! オーバーロードされた代入演算子を利用して,文字列から初期化する.
str = "Assignment of character sequence"
print *, str
! Assignment of character sequence
書式付きで表示することも可能です.Fortranでは,ユーザ定義型の書式指定子は,DT"型名"
とします.(それを指定した場合の挙動は,ユーザが実装する必要があります.)
! 文字列に基づいて初期化.
str = "string"
! 書式指定をして表示.
print '(DT"string_type")', str
! string
! 書式指定をせずに表示.文字間に空白が入っている.
print *, "|", str, "|"
! | string |
! 書式指定をして表示.文字間の空白がなくなっている.
print '(A,DT"string_type",A)', "|", str, "|"
! |string|
内部手続のオーバーロード
Fortranには,文字や文字列に対する豊富な内部手続が存在ます.詳しくは過去の記事Fortranにおける文字型変数の宣言方法と関連機能をご覧ください.string_type
はそのほとんどをオーバーロードしています.
文字列の長さ
関数 | 機能 |
---|---|
len(文字列) |
空白を含む文字列の長さを返す |
trim(文字列) |
文字列の末尾の空白を削除した文字列を返す |
len_trim(文字列) |
末尾の空白を削除した文字列の長さを返す |
adjustl(文字列) |
文字列の長さを変えず,左に寄せた文字列を返す(先頭の空白を末尾に詰める) |
adjustr(文字列) |
文字列の長さを変えず,右に寄せた文字列を返す(末尾の空白を先頭に詰める) |
type(string_type) :: str
!123456789012345678901234567890123456
str = "user-defined string type for Fortran"
! 文字列の長さを表示.
print *, len(str)
! 36
! 前後に3文字の空白を追加.
!123456789012345678901234567890123456789012
str = " user-defined string type for Fortran "
! 文字列の長さと,末尾の空白を削除した文字列の長さを表示.
print *, len(str), len_trim(str)
! 42 39
! 末尾の空白を削除した文字列と,その長さを表示.
print '(A,DT"string_type",A,I0)', "|", trim(str), "|", len(trim(str))
! | user-defined string type for Fortran|39
! 左に寄せた文字列(先頭の空白を末尾に詰めた文字列)を表示.
print '(A,DT"string_type",A)', "|", adjustl(str), "|"
! |user-defined string type for Fortran |
! 右に寄せた文字列(末尾の空白を先頭に詰めた文字列)を表示.
print '(A,DT"string_type",A)', "|", adjustr(str), "|"
! | user-defined string type for Fortran|
文字列の繰り返し
関数 | 機能 |
---|---|
repeat(文字列, 繰返し回数) |
文字列を繰り返し回数だけ繰り返した文字列を返す |
str = "Fortran"
print *, repeat(str, 3)
! FortranFortranFortran
文字列の検索
関数 | 機能 |
---|---|
index(被検索文字列, 部分文字列 [,back={.false. or .true.}]) |
被検索文字列から部分文字列を検索し,その文字の位置を整数で返すback=.true. とすると,後方から検索する |
scan(被検索文字列, 検索文字集合 [,back={.false. or .true.}]) |
被検索文字列から検索文字集合に含まれるいずれかの文字を検索し,その文字の位置を整数で返す |
verify(被検索文字列, 検索文字集合 [,back={.false. or .true.}]) |
被検索文字列から検索文字集合に含まれない文字を検索し,その文字の位置を整数で返す |
type(string_type) :: str
!1234567890123456789012345
str = " functions for character"
!1234567890123456789012345
! 文字列を前から検索し,"ct"の最初の位置を表示.
print *, index(str, "ct")
! 6 ! functionに含まれるctのcの位置
! 文字列を後ろから検索し,"ct"の最初の位置を表示.
print *, index(str, "ct", back=.true.)
! 22 ! characterに含まれるctのcの位置
! 文字列を前から検索し,文字の集合"f","o","r"のいずれかと一致する最初の文字の位置を表示.
print *, scan(str, "for")
! 3 ! fがfunctionのfと一致したので,その位置
! 文字列を後ろから検索し,文字の集合"f","o","r"のいずれかと一致する最初の文字の位置を表示.
print *, scan(str, "for", back=.true.)
! 25 ! rがcharacterのrと一致したので,その位置
! 文字列を後ろから検索し,文字の集合に含まれない文字が現れる最初の位置
print *, verify(str, "acefhiorstu", back=.true.)
! 16 ! 文字の集合に含まれていない空白が,forとcharacterの間に存在するので,その位置
! 文字列を前から検索し,文字の集合に含まれない文字が現れる最初の位置
print *, verify(adjustl(trim(str)), "acefhiorstu")
! 3 ! 文字の集合に含まれていないnの位置
文字コード関連
Fortranの文字に対する組込関数の内,文字コード関連の組込関数は挙動が少し異なります.
関数 | 機能 |
---|---|
char(文字列) |
文字列をFortranの文字列に変換して返す |
char(文字列, 位置) |
文字列の中から位置で指定した位置の文字を,Fortranの文字に変換して返す 位置には配列を渡すことができ,その場合戻り値は文字の配列になる |
char(文字列, 開始位置,終了位置) |
文字列の中から開始位置~終了位置の範囲の文字列を,Fortranの文字列に変換して返す |
ichar(文字列) |
文字列の最初の文字をシステム既定の文字セットに沿って変換した数字を返す |
iachar(文字列) |
文字列の最初の文字のASCIIコードを返す |
type(string_type) :: str
character(:), allocatable :: fstr
character, allocatable :: char_array(:)
!123456789012345678901234567890123
str = "functions for user-defined string"
! Fortranの文字列を返す.
fstr = char(str)
print *, fstr
! functions for user-defined string
! 指定した位置の文字を返す.
fstr = char(str, 4)
print *, fstr
! c
! 配列で指定した位置の文字を,文字型配列で返す.
char_array = char(str, [3, 5, 7, 11, 13, 17, 19, 23, 29])
print *, char_array
! ntofre-it
! 指定した範囲をFortranの文字列で返す.
fstr = char(str, 10, 20)
print *, fstr
! for user-d
! 文字列の最初の文字の文字コードを返す.実行しているシステム既定の文字セットがASCIIなので同じ結果になる.
str = "Fortran"
print *, ichar(str), iachar(str)
! 70 70
文字コードを基準に文字列の大小を比較する関数もオーバーロードされています.ここで,関数名に付いているl
はlexical
の略です.
引数の文字列は,string_type
型の文字列とFortranの文字列(character(:),allocatable
)の両方を取れます.文字列の長さが異なる場合は,短い文字列の末尾に空白が追加されます.
関数 | 機能 |
---|---|
lgt(文字列1, 文字列2) |
文字列1と文字列2を先頭から比較していき,文字列1のASCIIコードが文字列2より大きければ.true. を返す |
lge(文字列1, 文字列2) |
文字列1のASCIIコードが文字列2より大きいか,文字列1と文字列2が等しければ.true. を返す |
llt(文字列1, 文字列2) |
文字列1のASCIIコードが文字列2より小さければ.true. を返す |
lle(文字列1, 文字列2) |
文字列1のASCIIコードが文字列2より小さいか,文字列1と文字列2が等しければ.true. を返 |
type(string_type) :: str
str = "bcd"
! 先頭から比較していき,文字列1のASCIIコードが文字列2より大きければT.
! string_typeとcharacter(:),allocatableを比較.
print *, lgt(str, "abc"), lgt(str, "bcd"), lgt(str, "cde")
! T F F
! string_typeとstring_typeを比較.
print *, lgt(str, string_type("abc")), lgt(str, string_type("bcd")), lgt(str, string_type("cde"))
! T F F
! 長さが異なるstring_typeとstring_typeを比較.
print *, lgt(str, string_type("abc ")), lgt(str, string_type("bcd ")), lgt(str, string_type("cde "))
! T F F
! character(:),allocatableとstring_typeを比較.
print *, lgt("abc", str), lgt("bcd", str), lgt("cde", str)
! F F T
str = "bcd"
! 先頭から比較していき,文字列1のASCIIコードが文字列2より大きいか,文字列1と文字列2が等しければT.
! string_typeとcharacter(:),allocatableを比較.
print *, lge(str, "abc"), lge(str, "bcd"), lge(str, "cde")
! T T F
! string_typeとstring_typeを比較.
print *, lge(str, string_type("abc")), lge(str, string_type("bcd")), lge(str, string_type("cde"))
! T T F
! 長さが異なるstring_typeとstring_typeを比較.
print *, lge(str, string_type("abc ")), lge(str, string_type("bcd ")), lge(str, string_type("cde "))
! T T F
! character(:),allocatableとstring_typeを比較.
print *, lge("abc", str), lge("bcd", str), lge("cde", str)
! F T T
str = "bcd"
! 先頭から比較していき,文字列1のASCIIコードが文字列2より小さければT.
! string_typeとcharacter(:),allocatableを比較.
print *, llt(str, "abc"), llt(str, "bcd"), llt(str, "cde")
! F F T
! string_typeとstring_typeを比較.
print *, llt(str, string_type("abc")), llt(str, string_type("bcd")), llt(str, string_type("cde"))
! F F T
! 長さが異なるstring_typeとstring_typeを比較.
print *, llt(str, string_type("abc ")), llt(str, string_type("bcd ")), llt(str, string_type("cde "))
! F F T
! character(:),allocatableとstring_typeを比較.
print *, llt("abc", str), llt("bcd", str), llt("cde", str)
! T F F
str = "bcd"
! 先頭から比較していき,文字列1のASCIIコードが文字列2より小さいか,文字列1と文字列2が等しければT.
! string_typeとcharacter(:),allocatableを比較.
print *, lle(str, "abc"), lle(str, "bcd"), lle(str, "cde")
! F T T
! string_typeとstring_typeを比較.
print *, lle(str, string_type("abc")), lle(str, string_type("bcd")), lle(str, string_type("cde"))
! F T T
! 長さが異なるstring_typeとstring_typeを比較.
print *, lle(str, string_type("abc ")), lle(str, string_type("bcd ")), lle(str, string_type("cde "))
! F T T
! character(:),allocatableとstring_typeを比較.
print *, lle("abc", str), lle("bcd", str), lle("cde", str)
! T T F
演算子
文字に対する代入演算子=
,比較演算子>
, >=
, <
, <=
, .gt.
, .ge.
, .lt.
, .le.
,連結演算子//
もオーバーロードされています.比較演算子は,先ほどのlgt
等と同じです.
代入(単純なコピー)以外に,二つの文字列間で文字列を移動する手続も存在します.
type(string_type) :: str1, str2
str1 = "string"
print *, str1, str2
! string
! string_type間の代入
str2 = str1
print *, str1, str2
! string string
! 二つのstring_typeの比較.
print *, str1 == str2, str1 /= str2, str1 >= str2, str1 <= str2
! T F T T
! 二つのstring_typeの連結.
print *, str1//str2
! stringstring
str2 = ""
! string_type間でデータを移動.
call move(from=str1, to=str2)
print *, str1, str2, len(str1), len(str2)
! string 0 6
stdlib独自の関数
stdlib_ascii
やstdlib_string
で定義されている関数も,オーバーロードされています.
use :: stdlib_ascii
! 大文字アルファベットを小文字に変換する.
use :: stdlib_ascii
type(string_type) :: str
str = uppercase
! 大文字アルファベットを小文字に変換する.
print *, str
print *, to_lower(str)
! ABCDEFGHIJKLMNOPQRSTUVWXYZ
! abcdefghijklmnopqrstuvwxyz
str = lowercase
! 小文字アルファベットを大文字に変換する.
print *, str
print *, to_upper(lowercase)
! abcdefghijklmnopqrstuvwxyz
! ABCDEFGHIJKLMNOPQRSTUVWXYZ
use :: stdlib_ascii
type(string_type) :: str
str = "hello there. this is 'enquated string'. 19th century writer"
! 各英単語の最初の一文字を大文字にする.
print *, to_title(str)
! Hello There. This Is 'Enquated String'. 19th Century Writer
! 文字列の最初の一文字を大文字にする.
print *, to_sentence(str)
! Hello there. this is 'enquated string'. 19th century writer
! 文字列を反転する.
print *, reverse(str)
! retirw yrutnec ht91 .'gnirts detauqne' si siht .ereht olleh
派生型IO
string_type
は派生型IOの機能が実装されており,それを用いて標準出力に内容を出力していました.派生型IOは,標準出力だけでなく,ファイルへの出力やファイルからの入力も可能です.しかし,現状の実装では,コンパイラによっては動かない,書式付き入力はできないなど,かなり挙動が怪しい状態です.
use :: stdlib_io
integer(int32) :: unit
type(string_type) :: strw, strr
strw = "string"
unit = open("string_type.txt", "wt")
! 書式付き派生型IOでstring_typeの内容をファイルに出力.
write (unit, '(DT"string_type")') strw ! string_typeの内容("string")を出力して改行.
write (unit, *) "" ! 現状では,もう1行改行がないと,string_typeとして読み込めない.
close (unit)
! ---string_type.txtの内容---
! string
! ↵
! ↵
unit = open ("string_type.txt", "rt")
! 派生型IOでのファイル入力.
read (unit, *) strr ! 現状では,書式付き派生型入力は利用できない.
close (unit)
! stringstring
print '(2DT"string_type")', strw, strr
ファイルから読み込む際に問題が生じます.上の例では,ファイルに1行だけ文字列が書かれており,それを読み込んでいます.
gfortranでは,読み込む文字列の下にもう1行改行がないと,エラーコード-1
,End of file
エラーが生じます.そのため,上の例ではstring_type
型変数の出力後にもう1行改行を出力しています.
Intel Fortranでは,改行があろうがなかろうが,エラーコード255
が生じます.エラーメッセージ if a UDIO child sets IOMSG, it must set IOSTAT non-zero, ...
も出力されており,恐らく派生型入力の実装に問題があります.
iostat
を取得するようにすればプログラムの実行は継続できます.その際,strr
には値が読み込まれています.
stdlib_stringlist_type
stdlib_stringlist_type
モジュールでは,可変長文字列stringlist_type
の1次元リストを表す派生型stringlist_type
と,そのリストを走査するための派生型stringlist_index_type
を定義しています.
派生型stringlist_type
は,stringlist_type
の1次元リストを取り扱う為の型です.stringlistは,string_type
のリストを意味する言葉ですが,内部的に何を用いてリストを実装しているかは,意識する必要はありません.開発中であるため,今のところ要素の追加はできますが,要素の削除はできません.
派生型stringlist_index_type
は,stringlistを走査するためのインデックスです.走査の方向は順方向もしくは逆方向のどちらかです.stringlist_index_type
はstringlistの走査に用いますが,strlinglist_type
とは独立であるため,複数のstlinglistに対して同じインデックスを使用できます.
派生型stringlist_index_type
に対して,順方向インデックスを表すstringlist_index_type
型変数を返す関数fidx
および逆方向インデックスを表すstringlist_index_type
型変数を返す関数bidx
が定義されています.また,stlinglistの先頭および末尾を表すstringlist_index_type
型変数list_head
およびlist_tail
が定義されています.
stringlist_type
およびstringlist_index_type
はstringlistを使用する上で不可分であるため,まとめて使用例を見ていきます.
stringlist_type
にはコンストラクタが用意されており,string_type
の配列を渡すことでstringlist_type
型変数を構築できます.
type(stringlist_type) :: strlist
! コンストラクタを用いて,可変長文字列"one", "two", "three"からなるstrlinglistを作成する.
strlist = stringlist_type([string_type("one"), string_type("two"), string_type("three")])
strlinglistの要素を取得するには,型束縛手続きget
を持ちます.get
にはインデックスを渡します.要素の指定には,順方向インデックスを返す関数fidx(先頭からの要素番号)
もしくは逆方向インデックスを返す関数bidx(末尾からの要素番号)
の戻り値を利用します.順方向および逆方向の意味は,引数の数字が先頭からの要素番号を意味しているか,末尾からかの違いだけです.
! 順方向インデックスを利用してリストの要素を取得する.
print *, strlist%get(fidx(1)), strlist%get(fidx(2)), strlist%get(fidx(3))
! one two three
先頭要素はfidx(1)
に代わってlist_head
を利用して参照できます.同様に,末尾の要素はbidx(1)
に代わってlist_tail
が利用できます.
! 先頭要素
print *, strlist%get(list_head)
! one
! 末尾要素
print *, strlist%get(list_tail)
! three
リストの長さ(=要素数)の取得には,型束縛手続きlen
を用います.
! リストの長さ(要素数)を返す.
print *, strlist%len()
! 3
リストに要素を追加するには,型束縛手続きinsert_at
を用います.
! リストの先頭に要素を追加.
call strlist%insert_at(list_head, string_type("four"))
print *, strlist%get(fidx(1)), strlist%get(fidx(2)), strlist%get(fidx(3)), strlist%get(fidx(4))
print *, strlist%len()
! four one two three
! 4
! リストの末尾に要素を追加.
call strlist%insert_at(list_tail, string_type("five"))
print *, strlist%get(fidx(1)), strlist%get(fidx(2)), strlist%get(fidx(3)), strlist%get(fidx(4)), strlist%get(fidx(5))
print *, strlist%len()
! four one two three five
! 5
stringlist_index_type
型の変数も利用できますが,今のところ変数は決まった要素番号を指す以外の機能を持っていません.使用用途としては,ある決まった要素番号の変化を確認するくらいでしょうか.
type(stringlist_type) :: strlist
type(stringlist_index_type) :: idx
! リストを[one, two, three]で初期化
strlist = stringlist_type([string_type("one"), string_type("two"), string_type("three")])
! 前から2番目の要素を指すインデックス
idx = fidx(2)
print *, strlist%get(idx)
! two
! リストの先頭にfourをついかしたので,リストの内容が [four, one, two, three]に変化
call strlist%insert_at(list_head, string_type("four"))
! 2番目の要素がtwoからoneに変化
print *, strlist%get(idx)
! one
リストの全要素を削除するには,型束縛手続きclear
を用います.
print *, strlist%len()
! 4
! stringlist_typeを初期化
call strlist%clear()
print *, strlist%len()
! 0
演算子
string_list
には,比較と連結を行う演算子が定義されています.
type(stringlist_type) :: strlist1, strlist2, strlist
strlist1 = stringlist_type([string_type("one"), string_type("two"), string_type("three")])
strlist2 = stringlist_type([string_type("four"), string_type("five"), string_type("six")])
! stringlist_type同士の比較.
print *, strlist1 == strlist2, strlist1 /= strlist2
! F T
! stringlist_typeの連結.
strlist = strlist1//strlist2
print *, strlist%len()
print *, strlist%get(fidx(3)), strlist%get(fidx(4))
! 6
! three four