LoginSignup
6
5

More than 5 years have passed since last update.

夏休みFortran祭り 小ネタ集

Last updated at Posted at 2017-08-13

配列要素取り出し接尾演算子

配列を返す関数を呼んで、その中の特定の要素だけが欲しいことがありますが、Fortran ではいったんテンポラリな配列にいれて、行を変えて添え字で抜き出す必要があります。

ここでは配列の後ろにつけて、特定の要素を取り出すユーザー定義演算子を定義してみます。

ソース・プログラム

python に習って、負の引数の時には配列のお尻から数えた要素を返すことにします。(しかし Fortran は添え字が 1 から始まるので 0 の扱いに困ります。)

    module sub_m
      implicit none
      interface operator (.o.)
        module procedure :: take
      end interface
    contains
      pure real function take(d, i)
        real   , intent(in) :: d(:)
        integer, intent(in) :: i
        if (i > 0) then
          take = d(i)
        else if (i < 0) then
          take = d(size(d) + i + 1)
        else
          take = 0.0  
        end if    
      end function take
    end module sub_m

    program oOo
      use sub_m
      implicit none
      real :: a(10)
      integer :: i
      a = [(i * 1.0, i = 1, 10)]
      print *, a .o.  2
      print *, a .o. -1

      print *, test(22.0/7.0) .o.  1  
      print *, test(22.0/7.0) .o.  2  
      print *, test(22.0/7.0) .o. -1
    contains
      function test(x) 
        real, intent(in) ::x 
        real, allocatable :: test(:)
        test = [sin(x), cos(x), tan(x)]
      end function test
    end program oOo

実行結果

   2.000000
   10.00000
 -1.2644208E-03
 -0.9999992
  1.2644219E-03
続行するには何かキーを押してください . . .

一応、望みの結果が得られましたが、部分配列を取り出すことなどは出来ないので、改良の余地はあると思います。

文字列と文字配列の変換

Fortran では、文字列と文字の配列は別物ですが、メモリー上の並びは同じになっていて、transfer 関数で相互に変換できます。

文字列の方が様々な点で使い勝手がいいのですが、配列の場合 elemental な関数を使って、文字全体を一括で処理することが出来る利点があります。

以下の例では、文字列を文字配列に変換して、英小文字だけを大文字に変化して、また文字列に戻します。また Fortran 2003 から、文字列長を再割り付けで自動的に調節できるので、その機能も使っています。

ソース・プログラム

Fortran の組み込み関数 iachar/achar はどちらも要素型の関数なので、配列を引数に入れると、MAP 演算のように各要素に作用した結果を配列で返してくれます。

なお Fortran には achar/iachar と char/ichar という、極めて似通った関数のセットがありますが、これはかつては IBM の EBCDIC 文字コード系をはじめとして各処理系ごとに独自の文字コードを用いていたため、ASCII 文字コード用の関数と、各処理系独自の文字コード用の関数の二つが用意されたことによると思われます。


    program arai_choo
      implicit none
      character(*), parameter :: text  = 'This is a pen.'
      character, allocatable  :: chars(:)
      character(:), allocatable  :: string

      chars = transfer(text, chars) ! transfer(text, ' ', size = len(text))
      print '(a)', text, chars

      where (chars >= 'a' .and. chars <= 'z') chars = achar(iachar(chars) + iachar('A') - iachar('a'))
      string = transfer(chars, repeat(' ', size(chars)))
      print '(a)', string
    end program arai_choo

実行結果

This is a pen.
T
h
i
s

i
s

a

p
e
n
.
THIS IS A PEN.
続行するには何かキーを押してください . . .

1 から N までの番号付け

Fortran では配列は 1 から番号付けが始まります。これは普通の行列などのテキストと一致するので至極自然で、自然数が 1 から始まるという定義も、もっともだなと感じられます。

一方で計算機上では、自然数は 0 から始まるほうが自ずから然りという事情もあって、Fortran でたまに困ることがあります。

私の場合、ある正整数 i を、1 から n の自然数に類別したいことが時々あります。(グラフの色を選ぶとか色々)この時、n の余りを取ればいいですが、その場合 1 から n ではなく、0 から n-1 の値が得られてしまします。+1 で誤魔化す場合もありますが、余りに意味がある場合もあって、できれば 0 だけが n になって欲しいことがあります。

これを簡単に実現する方法として、modulo 関数を使う方法があります。modulo 関数は mod 関数とは異なるもので、より数学的に性質がいいものとして Fortran 90 で導入されました。 mod 関数は引数が正負をまたぐとき、原点 0 に関して正負に対称になっていますが、modulo 関数は原点に関わらず並進的に結果を与えます。

ソース・プログラム

    program numbering
      implicit none
      integer :: i, n = 7
      do i = 0, 10
        print *, mod(i, n), modulo(i, -n) + n
      end do  
    end program numbering

実行結果

以下のように、望みの結果が得られます。ただし引数は正を仮定しています。

           0           7 
           1           1 
           2           2 
           3           3 
           4           4 
           5           5 
           6           6 
           0           7 
           1           1 
           2           2 
           3           3 
続行するには何かキーを押してください . . .

配列から重複要素を抜いて一覧表を作る (ダブりを削る)

データ読み込みなどをしたとき、重複したラベルを持つ要素を処理しなければならない時があります。また集合のように、二つの配列を合体させて、重複要素は1個と見なしたい場合もあります。

この時、クイックソートの流用で、ソートしつつダブりを消すことが出来ます。

以下では、文字列配列から重複要素を削って、出現文字列の一覧表を出力します。

ソース・プログラム

三種類の文字列が重複している大きさ 10 の文字配列から、重複要素を除いたものを出力しています。

Fortran 90 から、ASCII コード順での文字列の順序の比較が <,> 演算子で出来るようになりました。FORTRAN77 にも固有文字コード[訂正] ASCII コードでの比較関数がありましたが。

    program dup
      implicit none
      character(len = 5) :: buff(10)
      character(:), allocatable :: list(:) 
      buff( 1) = 'ABCDE' 
      buff( 2) = '12345' 
      buff( 3) = '@#$%%' 
      buff( 4) = '@#$%%' 
      buff( 5) = '12345' 
      buff( 6) = '12345' 
      buff( 7) = '@#$%%' 
      buff( 8) = 'ABCDE' 
      buff( 9) = '12345' 
      buff(10) = 'ABCDE' 
      !
      list = deduplicate(buff)
      !
      print '(a)', list
    contains
      recursive function deduplicate(texts) result(res) ! skew-quicksort
        character(*), intent(in) :: texts(:)
        character(:), allocatable :: res(:)
        integer :: m
        m = size(texts) / 2
        if (m < 1) then 
          res = texts  
        else
          res = [ deduplicate(pack(texts, texts < texts(m))), &
                  texts(m), &
                  deduplicate(pack(texts, texts > texts(m)))  ]
        end if  
      end function deduplicate
    end program dup

実行結果

12345
@#$%%
ABCDE
続行するには何かキーを押してください . . .
6
5
2

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
6
5