コマンド ライン引数の個数で処理を変えるプログラムを作る
本稿では、コマンド ライン引数の個数で処理を変えるプログラムを Fortran で作ります。
作成するプログラムは以下のように動作します。
処理の流れは以下の通りです。
従って、概念上必要なものは、コマンド ライン引数を解析するクラス、素数を判定するクラス、素数を出力するクラスです。
今回はコマンド ライン引数の解析、素数の出力は大きな処理ではないので、クラスを作らずに対応します*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:処理の大きさに関わらず、クラスを作っておく方が後々プログラムを変更しやすいで、本当は作っておくほうが良いです。