競プロとかをやっていると標準入力を受け取って処理をすることが多いと思います。
記事も多いので、あまり理解せず書いていたコードをドキュメントを参照しながらまとめようと思います。
Go勉強し始めて日が浅いので色々ご容赦ください...mm
標準入力を受け取るよくある書き方。
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
sc := bufio.NewScanner(os.Stdin)
sc.Scan()
if err := sc.Err(); err != nil {
log.Fatal(err)
}
fmt.Println(sc.Text())
}
一つずつみていく
まず、bufio.Scanner
をみてみる。今回使ってるメソッドが大体載ってますね。
// ファクトリ関数
func NewScanner(r io.Reader) *Scanner
func (s *Scanner) Scan() bool
func (s *Scanner) Text() string
func (s *Scanner) Err() error
NewScanner
はファクトリ関数で、Scanner型のポインタを返すので、返り値をレシーバーにメソッドが呼べるんですね。os.Stdin
は一旦置いておいて、進めます。
それ以下は3つともScannerをレシーバに持つメソッドです。
Scan
は入力をトークンとして保存します。このトークンを取り出すには、Text()
かBytes()
を呼べと書かれています。戻り値はboolで、falseを返したときは、Err()
でエラー詳細を参照できるみたいです。
ところで、NewScanner
の引数はio.Reader
型なので、os.Stdin
を調べてみます。
var (
Stdin = NewFile(uintptr(syscall.Stdin), "/dev/stdin")
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")
Stderr = NewFile(uintptr(syscall.Stderr), "/dev/stderr")
)
func NewFile(fd uintptr, name string) *File
NewFile
の第一引数はfile descriptor(ファイルの識別子)、第二引数がファイル名です。返り値はFile型。
fdはsyscall.Stdin
の返り値を型キャストしています。
つまり、os.Stdin
はFile型、NewScanner
の引数はio.Reader
型なので一致しません。??
こちらの記事で述べられていますが、それぞれドキュメントを再び確認してみます。os.Stdin / io.Reader
package io
type Reader interface {
Read(p []byte) (n int, err error)
}
// ===================
package os
func (f *File) Read(b []byte) (n int, err error)
つまり、io.Reader
はReadメソッドをラップしたinterfaceであり、os.File
は全く同じReadメソッドを持っているということになります。従って、File型をReader型として引数で渡せる訳ですね。
最後にBufferについて
Bufferとは情報を一時的に保存しておく領域のことです。
bufio.Scanner
のBufferのデフォルト値を確認してみます。
const (
MaxScanTokenSize = 64 * 1024
)
64KiBつまり、65536byteです。入力サイズが大きい場合は不足する可能性があります。私は問題を解いていてこれでハマりました。
const (
init = 10 * 1024
max = math.MaxInt64
)
sc := bufio.NewScanner(os.Stdin)
buf := make([]byte, init)
sc.Buffer(buf, max)
sc.Scan()
Scanを呼ぶ前にBufferを指定すれば良さそうです。上記の例はかなり乱暴な例ですが、注意点として第一引数は[]byte
で、第二引数はint
です。
以上、読んでいただきありがとうございました!
参考:
https://qiita.com/takayukioda/items/edb30c7ba9ca28dba624
https://mickey24.hatenablog.com/entry/bufio_scanner_line_length