背景
Fortranで競技プログラミングをする際に...
競技プログラミングでは入力を読み込む必要があります.
面倒な入力形式が稀によくあります.
例えば...
(略)
4
1 1 a
2 1 2
2 2 30
1 2 z
のように, 3列目の型が文字か数値です.
これらを Q回読み込む必要があります.
do
ループで回したいのですが, 数値でも文字でも読み込める型は文字列しかないです.
そのため, 文字列で読み込む → 数値に変換するという手間が掛かります.
Fortran の read
文はトークンを読み込むことは(おそらく)できないため, このような入力の処理は面倒です.
なので, Java 言語の Scanner
クラスみたいなものを定義して, トークン毎に文字を読み込みたいです.
JavaのScannerクラス
Java の Scanner
クラスを使うと, 上の入力は
// (略)
// 大体こんな感じ(コンパイルできるか試していません)
Scanner sc = new Scanner(System.in);
int q = sc.nextInt();
for (int i = 0; i < q; ++i) {
int t = sc.nextInt();
if (t == 1) {
int iq = sc.nextInt();
String s = sc.next();
// 処理
} else { // t = 2
int lq = sc.nextInt();
int rq = sc.nextInt();
// 処理
}
}
のように, System.in
から空白区切りで数字や文字列を読み込むことができます.
これを Fortran で実現したいです.
数値計算的な用途は...ない?
例えば, 数値計算で入力を用意する場合は, 入力の方を自分で決められるため, Fortranで扱い易い形式にすればよいです.
なので, 数値計算においては scanner
クラスの需要はないと思います.
作ってみよう
マイ競プロライブラリにも src/scanner_m.f90
を追加しました.
ソースコード全体
ソースコードは以下の折りたたみの中身の通りです.
scanner_m.f90 (全体)
module scanner_m
use, intrinsic :: iso_fortran_env
implicit none
private
public scanner
integer(int32), parameter, public :: max_bufsize = ishft(1_int32, 20) ! ~ 10**6
!> type(scanner): For reading token like Scanner class in Java.
!> The default delimiter is space (" ").
!> If delimiter is space (" "), regard `iostat_eor` as space.
type :: scanner
private
integer(int32) :: pos_ = 0, unit_ = input_unit
character :: delim_ = " "
logical :: is_eof_ = .false.
character(len=max_bufsize) :: buffer_
contains
!> setting function
procedure, pass :: use_delim => use_delim_scanner
!> read function
procedure, pass, private :: read_until_delim => read_until_delim_scanner
!> next functions
procedure, pass :: next => next_scanner
generic :: next_int => next_int8, &
& next_int16, &
& next_int32, &
& next_int64
procedure, pass :: next_int8 => next_int8_scanner
procedure, pass :: next_int16 => next_int16_scanner
procedure, pass :: next_int32 => next_int32_scanner
procedure, pass :: next_int64 => next_int64_scanner
generic :: next_real => next_real32, &
& next_real64
procedure, pass :: next_real32 => next_real32_scanner
procedure, pass :: next_real64 => next_real64_scanner
!> state functions
procedure, pass :: is_eof => is_eof_scanner
end type scanner
interface scanner
module procedure construct_scanner
end interface scanner
contains
!> construct_scanner: Constructor for `type(scanner)`.
!> @param unit An unit of input device.
!> @return A object of scanner type.
pure type(scanner) function construct_scanner(unit) result(res)
integer(int32), intent(in) :: unit
if (unit == output_unit) &
& error stop "Error: Scanner does not use `output_unit`."
if (unit == error_unit) &
& error stop "Error: Scanner does not use `error_unit`."
res%unit_ = unit
end function construct_scanner
!> use_delim_scanner: Modify delimiter.
!> @param this `this%delim_` changes.
!> @param delimiter A character of delimiter.
pure subroutine use_delim_scanner(this, delimiter)
class(scanner), intent(inout) :: this
character, intent(in) :: delimiter
this%delim_ = delimiter
end subroutine use_delim_scanner
!> read_until_delim_scanner: Read from `this%unit_` until reaching `this%delim_`.
!> @param this `this%buffer_(1:this%pos_)` represents token splited by `this%delim_`.
impure subroutine read_until_delim_scanner(this)
class(scanner), intent(inout) :: this
character :: c
integer(int32) :: iostat
if (this%pos_ /= 0) return
read_until_delim: do
read (this%unit_, '(a1)', advance="no", iostat=iostat) c
select case (iostat)
case (iostat_eor)
if (this%delim_ == " ") exit
this%pos_ = this%pos_ + 1
this%buffer_(this%pos_:this%pos_) = achar(10) !> newline code.
cycle
case (iostat_end)
this%is_eof_ = .true.
exit
case (0)
!> do nothing.
case default
write (error_unit, *) iostat
error stop "Error: Unknown iostat"
end select
if (c == this%delim_) then
if (this%pos_ /= 0) exit
cycle !> The contiguous `this%delim_`.
end if
! write(error_unit, *) c, this%pos_, iostat, iostat_eor, iostat_end
this%pos_ = this%pos_ + 1
this%buffer_(this%pos_:this%pos_) = c
end do read_until_delim
end subroutine read_until_delim_scanner
!> next_scanner: Return token as `character(len=:)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res A character of token splited by `this%delim_`.
impure subroutine next_scanner(this, res)
class(scanner), intent(inout) :: this
character(len=:), intent(inout), allocatable :: res
character(len=:), allocatable :: tmp
call this%read_until_delim()
allocate (tmp, source=this%buffer_(1:this%pos_))
call move_alloc(from=tmp, to=res)
this%pos_ = 0
end subroutine next_scanner
!> next_int8_scanner: Return token as `integer(int8)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res An `integer(int8)` of token splited by `this%delim_`.
impure subroutine next_int8_scanner(this, res)
class(scanner), intent(inout) :: this
integer(int8), intent(inout) :: res
call this%read_until_delim()
read (this%buffer_(1:this%pos_), *) res
this%pos_ = 0
end subroutine next_int8_scanner
!> next_int16_scanner: Return token as `integer(int16)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res An `integer(int16)` of token splited by `this%delim_`.
impure subroutine next_int16_scanner(this, res)
class(scanner), intent(inout) :: this
integer(int16), intent(inout) :: res
call this%read_until_delim()
read (this%buffer_(1:this%pos_), *) res
this%pos_ = 0
end subroutine next_int16_scanner
!> next_int32_scanner: Return token as `integer(int32)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res An `integer(int32)` of token splited by `this%delim_`.
impure subroutine next_int32_scanner(this, res)
class(scanner), intent(inout) :: this
integer(int32), intent(inout) :: res
call this%read_until_delim()
read (this%buffer_(1:this%pos_), *) res
this%pos_ = 0
end subroutine next_int32_scanner
!> next_int64_scanner: Return token as `integer(int64)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res An `integer(int64)` of token splited by `this%delim_`.
impure subroutine next_int64_scanner(this, res)
class(scanner), intent(inout) :: this
integer(int64), intent(inout) :: res
call this%read_until_delim()
read (this%buffer_(1:this%pos_), *) res
this%pos_ = 0
end subroutine next_int64_scanner
!> next_real32_scanner: Return token as `real(real32)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res An `integer(real32)` of token splited by `this%delim_`.
impure subroutine next_real32_scanner(this, res)
class(scanner), intent(inout) :: this
real(real32), intent(inout) :: res
call this%read_until_delim()
read (this%buffer_(1:this%pos_), *) res
this%pos_ = 0
end subroutine next_real32_scanner
!> next_real64_scanner: Return token as `real(real64)` through `res`.
!> @param this this%pos_ reset 0.
!> @param res An `integer(real64)` of token splited by `this%delim_`.
impure subroutine next_real64_scanner(this, res)
class(scanner), intent(inout) :: this
real(real64), intent(inout) :: res
call this%read_until_delim()
read (this%buffer_(1:this%pos_), *) res
this%pos_ = 0
end subroutine next_real64_scanner
!> is_eof_scanner: Return is reaching EOF of input file or not.
!> @param this Read one token.
!> @return A logical value if reaching EOF or not.
impure logical function is_eof_scanner(this) result(res)
class(scanner), intent(inout) :: this
call this%read_until_delim()
res = this%is_eof_
end function is_eof_scanner
end module scanner_m
型を定義する
scanner_m.f90 (型定義)
integer(int32), parameter, public :: max_bufsize = ishft(1_int32, 20) ! ~ 10**6
type :: scanner
private
integer(int32) :: pos_ = 0, unit_ = input_unit
character :: delim_ = " "
logical :: is_eof_ = .false.
character(len=max_bufsize) :: buffer_
contains
!> setting function
procedure, pass :: use_delim => use_delim_scanner
!> read function
procedure, pass, private :: read_until_delim => read_until_delim_scanner
!> next functions
procedure, pass :: next => next_scanner
generic :: next_int => next_int8, &
& next_int16, &
& next_int32, &
& next_int64
procedure, pass :: next_int8 => next_int8_scanner
procedure, pass :: next_int16 => next_int16_scanner
procedure, pass :: next_int32 => next_int32_scanner
procedure, pass :: next_int64 => next_int64_scanner
! (略)
end type scanner
- 型の中の
character(len=max_bufsize) :: buffer_
配列にトークンを保持します. - $10^6$ 程度読み込めれば大丈夫でしょう.
- あとは色々定義します.
-
read_until_delim_scanner
はトークンを読み込むルーチンです. - ちなみに
next_int
たちはfypp
を使って楽しています.
#:set SCANNER_INTEGER_KINDS = ["int8", "int16", "int32", "int64"]
generic :: next_int => ${", &\n& ".join(map(lambda k: f"next_{k}", SCANNER_INTEGER_KINDS))}$
#:for kind in SCANNER_INTEGER_KINDS
procedure, pass :: next_${kind}$ => next_${kind}$_scanner
#:endfor
トークンを読み込む
scanner_m.f90 (read_until_delimiter)
impure subroutine read_until_delim_scanner(this)
class(scanner), intent(inout) :: this
character :: c
integer(int32) :: iostat
if (this%pos_ /= 0) return
read_until_delim: do
read (this%unit_, '(a1)', advance="no", iostat=iostat) c
select case (iostat)
case (iostat_eor)
if (this%delim_ == " ") exit
this%pos_ = this%pos_ + 1
this%buffer_(this%pos_:this%pos_) = achar(10) !> newline code.
cycle
case (iostat_end)
this%is_eof_ = .true.
exit
case (0)
!> do nothing.
case default
write (error_unit, *) iostat
error stop "Error: Unknown iostat"
end select
if (c == this%delim_) then
if (this%pos_ /= 0) exit
cycle !> The contiguous `this%delim_`.
end if
! write(error_unit, *) c, this%pos_, iostat, iostat_eor, iostat_end
this%pos_ = this%pos_ + 1
this%buffer_(this%pos_:this%pos_) = c
end do read_until_delim
end subroutine read_until_delim_scanner
-
read (this%unit_, '(a1)', advance="no", iostat=iostat) c
で1文字だけ読み込めます. -
iostat
で改行(iostat_eor
)と読み込み終わり(iostat_end
)を検知できます. - 区切りが
" "
の場合は改行も区切りとしています.
1文字を this%delim_
を読み込むまで繰り返すだけです.
やってみる
test_scanner_m.f90
program part_of_ABC157e
use, intrinsic :: iso_fortran_env
use scanner_m
implicit none
integer(int32) :: q
type(scanner) :: sc
integer(int32) :: i
read (input_unit, *) q
sc = scanner(input_unit)
do i = 1, q
block
integer(int32) :: types
call sc%next_int(types)
select case (types)
case (1)
block
integer(int32) :: i
character(len=:), allocatable :: c
call sc%next_int(i)
call sc%next(c)
write (output_unit, '(*(g0, 1x))') types, i, c
end block
case (2)
block
integer(int32) :: l, r
call sc%next_int(l)
call sc%next_int(r)
write (output_unit, '(*(g0, 1x))') types, l, r
end block
end select
end block
end do
end program Part_Of_ABC157e
$ gfortran scanner_m.f90 test_scanner.f90 && ./a.out <<< "4
1 1 a
2 1 2
2 2 30
1 2 z"
1 1 a
2 1 2
2 2 30
1 2 z
ちゃんと読み込めています.
-
sc = scanner(input_unit)
で初期化します. -
read
した後にscanner
クラスで読み込んでも平気そうです.- 逆は,
sc
のbuffer_
に文字列が読み込まれている可能性があるため危険です.
- 逆は,
まとめ
Fortran はトークン毎に文字列を読み込める可能性を秘めています.
ユーザはインターフェースだけ知っていれば良いので楽そうです.
fypp
を使うと楽できます.
面倒なことはクラスに処理させましょう.