【Go】標準入力 / bufio.Scanner


競プロとかをやっていると標準入力を受け取って処理をすることが多いと思います。
記事も多いので、あまり理解せず書いていたコードをドキュメントを参照しながらまとめようと思います。

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