4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FortranAdvent Calendar 2021

Day 19

Fortranの標準ライブラリstdlibの紹介(ユーザ定義の文字列型)

Last updated at Posted at 2021-12-18

概要

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_typeuseした後,他の派生型と同様に型を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

文字コードを基準に文字列の大小を比較する関数もオーバーロードされています.ここで,関数名に付いているllexicalの略です.

引数の文字列は,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_asciistdlib_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
4
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?