読者です 読者をやめる 読者になる 読者になる

Go でプロコンに出てみた (後編)

前回の続き

入力を読もう

ググって出てきた [Go] ファイルや標準入力から一行ずつ読み込む - Qiita とか Go 言語で標準入力から読み込む競技プログラミングのアレ --- 改訂第二版 - Qiita とか見て入力をとってみる。

  • sc := bufio.NewScanner(os.Stdin): Scanner を生成する
  • sc.Split(bufio.ScanWords): デフォだと1行単位なのでトークン単位にする
  • s := sc.Text(): 1個読み込む
  • i, e := strconv.Atoi(s): int に変換する

という感じらしい。 標準入力から1個読んでくるのは失敗しないけど、テキストをパーズして整数に変換するのは失敗する可能性のある処理らしい。。(実は伏線) それにしても、エラーが直積で表されるとはなんぞ…。 非常に簡単に握りつぶせるから検査例外としての価値もないぞ。

書きかけプログラムをチェックしてみたかった…

とりあえず、入力の最初の方だけとってみて入力がとれているかチェックしてみることにした。

package main

import (
  "bufio"
  "fmt"
  "os"
  "strconv"
)

func main() {
  sc := bufio.NewScanner(os.Stdin)
  sc.Split(bufio.ScanWords)
  N, _ := strconv.Atoi(sc.Text())
  D, _ := strconv.Atoi(sc.Text())
  K, _ := strconv.Atoi(sc.Text())
}

さて走らせてみる。

$ go run hoge.go 
# command-line-arguments
./hoge.go:5: imported and not used: "fmt"

あれ?余計な import を入れるな、らしい。意外と細かい…。 fmt は出力に使うためのものなのでまだいらないのでとりあえず消してみる。

$ go run hoge.go 
# command-line-arguments
./hoge.go:12: N declared and not used
./hoge.go:13: D declared and not used
./hoge.go:14: K declared and not used

えー…ちょっと神経質すぎでは…。 これ warning ではなく compile error なので、この先は実行されない。 Go プログラマは作りかけどうやってチェックしているのだろう? 謎い。

逆に仮の出力を足すことにした。

fmt.Println(N + D + K)

これはコンパイルが通る。うーむ。

入力を与えてみた

では入力を与えてみよう。

$ echo 10 10 3 > in
$ go run hoge.go < in
0

あれ?0 ってなんぞ? 入力が取れていないようだ。 実は、sc.Text() を呼ぶ前に sc.Scan() を呼んで次の入力をフェッチしておかないと sc.Text() で渡される内容が更新されないようである。 なるほど、だから sc.Text() は例外投げないのか…。

というわけで、 sc.Scan() を頑張ってあちこちに挿入した。はい。

$ go run hoge.go < in
23

余計な配慮をしない分多少速度には貢献しそうだけど実際どうなのだろう?

補足

今度書くなら参考ページのように my scanner 作ることにする。。

配列を使おう

配列が使えるらしいので宣言してみる。

  var L [D]int
  var R [D]int

さて実行してみる。

$ go run piyo.go 
# command-line-arguments
./piyo.go:19: non-constant array bound D
./piyo.go:20: non-constant array bound D

サイズが静的でないからダメらしい。 そういえば C とかでもダメなんだっけ? 実行時にサイズが決まる配列は正しくはこう生成するらしい。

  var L = make([]int, D)
  var R = make([]int, D)

繰り返しを書こう

繰り返しは普通に C 系言語のような for が使えるらしい。 ただカッコが少なくてこのようになった。

    for d := 0; d < D; d++ {
      if L[d] <= S && S <= R[d] {
        if L[d] <= T && T <= R[d] {
          fmt.Println(d + 1)
          break
        } else {
          if S < T {
            S = R[d]
          } else {
            S = L[d]
          }
        }
      }
    }

ここまでと違って特にハマりどころもなくて (普通に break も使えたし) 悪くないんだけど、 1つ気になることは、視覚的なまとまりがちょっと寂しい気がする。 ぱっと見で for d := 0; d < D; d++ { がひとまとまりに見えるかというとよくわからない。 if でも同じことが言えると思う。

昔 Algebra of Programming という本で条件演算子(A→B,C) という表記で (A ? B : C の意味)、何度読みなおしても ((A→B),C) としか 構文解析できなくてつらい思いをした。 なんだかそれと似たような空気を感じた。

感想

最適化が効きやすいようにあちこち工夫していそうということは感じ取ることができた。 (本当に効いているかは知らないが) それに C を書くよりずっと安全そうな空気も良かった。 ただ少し攻めすぎている印象も受けたよ。。