匿名希望のおでんFortranツヴァイさん太郎

生き物、Fortran、川について書く

コマンド ライン引数の個数で処理を変えるプログラムを作る

本稿では、コマンド ライン引数の個数で処理を変えるプログラムを Fortran で作ります。

作成するプログラムは以下のように動作します。

  • コマンド ライン引数で自然数のみを与えた場合
  • コマンド ライン引数で自然数とファイル名を与えた場合
    • 指定のファイルに、与えた自然数以下の素数を出力します。

処理の流れは以下の通りです。

  1. コマンド ライン引数を解析する。
  2. 与えられた自然数以下の素数を取得する。
  3. 素数を出力する。

従って、概念上必要なものは、コマンド ライン引数を解析するクラス、素数を判定するクラス、素数を出力するクラスです。
今回はコマンド ライン引数の解析、素数の出力は大きな処理ではないので、クラスを作らずに対応します*1
素数を判定するために class_sieve_of_eratosthenes を作ります。
コマンド ライン引数の取得には以下の記事で作った class_command_getter を用います。
y-tis.hatenablog.com

自然数のみを与えて実行すると以下のように出力されます。

sieve_of_eratosthenes.exe 10
 Prime numbers are followings:
           2
           3
           5
           7
The number of primes is 4

以下のように、自然数とファイル名を与えて実行すると、テキスト ファイルに出力されます。

sieve_of_eratosthenes.exe 10 primes.txt

primes.txt には、以下のように出力されます。

           2
           3
           5
           7

メイン プログラムは以下の通りです。コマンド ライン引数の型は解析していないので、第一引数で文字列を指定してしまうとエラーになる上、使用例を表示せずに終了します。また、大きすぎる値を指定するとエラーになります。この辺りの処理は今後の課題です。

! 指定の自然数以下の素数を調べるプログラムです。
!
! コマンド ライン引数で自然数のみを指定すると、
! 指定した自然数以下の素数とその個数を標準出力に表示します。
! 
! コマンド ライン引数で自然数とファイル名を指定すると、
! 指定した自然数以下の素数を、指定のファイルに出力します。
program main_program
    use, intrinsic :: iso_fortran_env
    use class_command_getter
    use class_sieve_of_eratosthenes
    implicit none

    ! 走査する自然数の上限 
    integer upper_limit
    ! 出力ファイル名
    character(:), allocatable :: output_file_name
    ! コマンド ライン引数取得のためのクラス
    type(command_getter) args_getter
    ! エラトステネスのふるいを実行するクラス
    type(sieve_of_eratosthenes) sieve
    ! 素数を格納する配列
    integer(int32), allocatable :: prime_numbers(:)


    ! コマンド ライン引数が不正であれば終了します。
    if (args_getter%get_size() == 0 .or. args_getter%get_size() > 2) then
        write(error_unit, *) "ERROR:"
        write(error_unit, *) "The command line arguments are followings:"
        write(error_unit, *) " - positive_integer: integer"
        write(error_unit, *) " - output_file_name [optional]: character"
        write(error_unit, *) ""
        write(error_unit, *) "(e.g.1) sieve_of_eratosthenes.exe 100"
        write(error_unit, *) " => Prime Numbers are written in standard output."
        write(error_unit, *) ""
        write(error_unit, *) "(e.g.2) sieve_of_eratosthenes.exe 100 prime_numbers.txt"
        write(error_unit, *) " => Prime Numbers are written in 'prime_numbers.txt'."
        write(error_unit, *) ""
        write(error_unit, *) "Program ended."
        stop
    end if

    ! 指定された自然数が 0 以下であれば、エラーを表示して終了します。
    if (args_getter%to_int32(1) <= 0) then
        write(error_unit, *) "ERROR : The first argument must be a positive integer."
        write(error_unit, *) "Program ended."
        stop
    end if

    ! 出力ファイル名が指定されている場合
    if (args_getter%get_size() == 2) then
        ! 出力ファイル名を決定します。
        output_file_name = args_getter%get(2)
    else
        ! 出力ファイル名は空にします。
        output_file_name = ""
    end if

    ! コマンド ライン引数から走査する自然数の上限を決定します。
    upper_limit = args_getter%to_int32(1)

    ! エラトステネスのふるいを実行し、素数を格納した配列を取得します。
    prime_numbers = sieve%sieve(upper_limit)

    if (output_file_name == "") then
        ! 出力ファイル名の指定がない場合
        ! 素数の個数と素数の一覧を標準出力に表示します。
        call show_prime_numbers(prime_numbers)
    else
        ! 出力ファイル名が指定されている場合
        ! 素数を指定の名前の出力ファイルに出力します。
        call write_prime_numbers(prime_numbers, output_file_name)
    end if


    contains


    ! 与えられた整数配列の個数と要素を標準出力に表示します。
    !
    ! @param prime_numbers 整数配列
    subroutine show_prime_numbers(prime_numbers)
        ! 引数
        integer(int32), intent(in) :: prime_numbers(:)
        ! 素数の個数
        integer(int32) number_of_primes

        ! ループ カウンタ
        integer(int32) i

        ! 素数の個数を取得します。
        number_of_primes = size(prime_numbers)

        ! 素数の個数が 0 個の時は終了します。
        if (number_of_primes == 0) return

        ! 素数を標準出力に列挙します。
        print*, "Prime numbers are followings:"
        do i = 1, size(prime_numbers)
            print*, prime_numbers(i)
        end do

        ! 素数の個数を表示します。
        print'(A, I0)', "The number of primes is ", number_of_primes
    end subroutine


    ! 与えられた整数配列を指定のファイルに出力します。
    !
    ! @param prime_numbers 整数配列
    ! @param output_file_name 出力ファイル名
    subroutine write_prime_numbers(prime_numbers, output_file_name)
        ! 引数
        integer(int32), intent(in) :: prime_numbers(:)
        character(*), intent(in) :: output_file_name
        ! 装置番号
        integer(int32) output_file_unit
        ! ループ カウンタ
        integer(int32) i

        open(newunit=output_file_unit, file=output_file_name, status="replace")

        if (size(prime_numbers) == 0) then
            ! 素数の個数が 0 個の時は終了します。
            return
        else
            ! 素数を出力ファイルに書き出します。
            do i = 1, size(prime_numbers)
                write(output_file_unit, *) prime_numbers(i)
            end do
        end if

        close(output_file_unit)
    end subroutine
end program

class_command_getter は以下の通りです。

! コマンド ライン引数を取得するクラスです。
module class_command_getter
    use, intrinsic :: iso_fortran_env
    implicit none
    
    private
    
    ! コマンド ライン引数を表す構造体です。
    type, private :: command_line_argument
        character(:), private, allocatable :: value
    end type

    
    ! コマンド ライン引数を取得するクラスです。
    type, public :: command_getter
        ! コマンド ライン引数を取得していれば true、そうでなければ false
        logical, private :: has_values = .false.
        ! コマンド ライン引数
        type(command_line_argument), private, allocatable :: value(:)

        contains

        procedure, private, pass :: get_all_command_line_argument
        procedure, public, pass :: get
        procedure, public, pass :: get_size
        procedure, public, pass :: to_int32
        procedure, public, pass :: to_real64
    end type


    contains


    ! i 番目のコマンド ライン引数を返します。戻り値の型は character です。
    !
    ! @param i コマンド ライン引数の順番 (1 始まり、int32)
    ! @return i 番目のコマンド ライン引数
    function get(this, i) result(arg)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        character(:), allocatable :: arg

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        arg = this%value(i)%value
    end function


    ! コマンド ライン引数の個数を返します。
    !
    ! @return コマンド ライン引数の個数
    function get_size(this) result(number_of_args)
        ! 引数
        class(command_getter), intent(inout) :: this
        ! 戻り値
        integer(int32) number_of_args

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        number_of_args = size(this%value)
    end function


    ! 指定されているコマンド ライン引数を全て取得します。
    subroutine get_all_command_line_argument(this)
        ! 引数
        class(command_getter), intent(inout) :: this
        ! コマンド ライン引数
        character(:), allocatable :: arg
        
        ! コマンド ライン引数の個数
        integer(int32) number_of_args
        ! コマンド ライン引数の文字列長を格納する配列
        integer(int32) length
        ! コマンド ライン引数取得時の状態
        integer(int32) status
        ! ループ カウンタ
        integer(int32) i
        
        ! コマンド ライン引数の個数を取得します。
        number_of_args = command_argument_count()
        
        ! コマンド ライン引数を格納する配列を生成します。
        allocate(this%value(number_of_args))
        
        do i = 1, number_of_args
            ! i 番目のコマンド ライン引数の長さを取得します。
            call get_command_argument(i, length=length, status=status)
            
            ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。
            if (status /= 0) then
                write(error_unit, *) "Command line argument retrieval failed."
                write(error_unit, *) "Program ended."
                stop
            end if
            
            ! i 番目のコマンド ライン引数を取得します。
            allocate(character(length) :: arg)
            call get_command_argument(i, arg, status=status)
            
            ! コマンド ライン引数の取得に失敗すれば、プログラムを終了します。
            if (status /= 0) then
                write(error_unit, *) "Command line argument retrieval failed."
                write(error_unit, *) "Program ended."
                stop
            end if

            ! コマンド ライン引数を格納します。
            this%value(i)%value = arg
            
            deallocate(arg)
        end do

        this%has_values = .true.
    end subroutine


    ! i 番目のコマンド ライン引数を int32 に変換します。
    !
    ! @param i int32 に変換したいコマンド ライン引数のインデックス
    ! @return int32 の整数
    function to_int32(this, i) result(output_integer)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        integer(int32) output_integer
        ! iostat 指定子
        integer(int32) iostat

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        ! コマンド ライン引数を int32 に変換します。
        read(this%value(i)%value, *, iostat=iostat) output_integer

        ! 変換にエラーがあればプログラムを終了します。
        if(iostat > 0) then
            write(error_unit, *) "An error occurred in to_int32 in class_command_getter."
            write(error_unit, *) "Program ended."
            stop
        end if
    end function


    ! i 番目のコマンド ライン引数を real64 に変換します。
    !
    ! @param i real64 に変換したいコマンド ライン引数のインデックス
    ! @return real64 の実数
    function to_real64(this, i) result(output_real_value)
        ! 引数
        class(command_getter), intent(inout) :: this
        integer(int32), intent(in) :: i
        ! 戻り値
        real(real64) output_real_value
        ! iostat 指定子
        integer(int32) iostat

        ! コマンド ライン引数を取得していなければ、コマンド ライン引数を全て取得します。
        if(.not. this%has_values) then
            call this%get_all_command_line_argument()
        end if

        ! コマンド ライン引数を real64 に変換します。
        read(this%value(i)%value, *, iostat=iostat) output_real_value

        ! 変換にエラーがあればプログラムを終了します。
        if(iostat > 0) then
            write(error_unit, *) "An error occurred in to_real64 in class_command_getter."
            write(error_unit, *) "Program ended."
            stop
        end if
    end function
end module

class_sieve_of_eratosthenes は以下の通りです。

! エラトステネスのふるいを実行するクラスです。
module class_sieve_of_eratosthenes
    use, intrinsic :: iso_fortran_env
    implicit none

    private

    ! エラトステネスのふるいを実行するクラスです。
    type, public :: sieve_of_eratosthenes

        contains

        procedure, public, pass :: sieve
    end type


    contains


    ! 指定の自然数以下の素数を格納した int32 配列を返します。
    ! 
    ! @param upper_limit 走査する自然数の上限
    ! @return 指定の数以下の素数を格納した int32 配列
    function sieve(this, upper_limit) result(prime_number_array)
        ! 引数
        class(sieve_of_eratosthenes), intent(in) :: this
        integer(int32), intent(in) :: upper_limit
        ! 戻り値
        integer(int32), allocatable :: prime_number_array(:)
        ! ループ カウンタ
        integer(int32) i

        ! upper_limit が 0 以下であればエラーを表示し、大きさ 0 の配列を返します。
        if (upper_limit <= 0) then
            write(error_unit, *) "ERROR : upper_limit must be positive."
            write(error_unit, *) "An empty int32 array is returned."
            allocate(prime_number_array(0))
            return
        end if

        ! 素数を配列に格納します。
        prime_number_array = pack([(i, i = 1, upper_limit)], mask = is_prime_number(upper_limit))
    end function


    ! インデックス i が素数なら要素が true である論理型配列を初期化します。
    !
    ! @param array_size 論理型配列の大きさ
    ! @return インデックス i が素数なら true である論理型配列
    function initialize_prime_number_flag(array_size) result(is_prime_number)
        ! 引数
        integer(int32), intent(in) :: array_size
        ! 戻り値
        logical, allocatable :: is_prime_number(:)

        ! input_number 以下の整数が素数であるかを判定する論理型配列を生成します。
        allocate(is_prime_number(array_size), source = .true.)
        ! 1 を素数から外します。
        is_prime_number(1) = .false.

        ! array_size が 1 であれば終了します。
        if (array_size == 1) return

        ! 2 を素数と判定します。
        is_prime_number(2) = .true.

        ! array_size が 2 であれば終了します。
        if (array_size == 2) return

        ! array_size が 4 以上の場合
        if (array_size >= 4) then
            ! 2 より大きい偶数を素数でないと判定します。
            is_prime_number(4:array_size:2) = .false.
        end if
    end function


    ! インデックス i が素数なら要素が true である、指定の大きさの論理型配列を返します。
    !
    ! @param array_size 走査する自然数の上限
    ! @return インデックス i が素数なら要素 true である、大きさ array_size の論理型配列
    function is_prime_number(array_size) result(is_prime)
        ! 引数
        integer(int32), intent(in) :: array_size
        ! インデックス i が素数なら true、そうでないなら false を格納する logical 配列
        logical, allocatable :: is_prime(:)
        ! 実際に走査する自然数の最大値
        integer(int32) max_iteration
        ! ループ カウンタ
        integer(int32) i

        ! インデックス i が素数なら要素が true である論理型配列を初期化します。
        ! 2 を素数、2 より大きい偶数を非素数と判定します。
        is_prime = initialize_prime_number_flag(array_size)

        ! 3 から sqrt(array_size) までの奇数が素数であるか走査します。
        ! 素数 p の倍数を消去する時、p * 2 から p * (p - 1) は
        ! すでに消去されているので p * p から消去します。
        ! 従って、 sqrt(array_size) までループを回せば十分です。
        max_iteration = int(sqrt(dble(array_size)))
        do i = 3, max_iteration, 2
            ! すでに判定した整数はとばします。
            if (.not. is_prime(i)) cycle

            ! i^2 から array_size までの i の倍数を false にします。
            is_prime(i * i:array_size:i) = .false.
        end do
    end function
end module

*1:処理の大きさに関わらず、クラスを作っておく方が後々プログラムを変更しやすいで、本当は作っておくほうが良いです。