- 投稿日:2020-02-10T22:18:23+09:00
goのioインターフェースを使ってみよう
はじめに
こんにちわ、すえけん(@sueken5)です。
今回の記事ではgolangのioインターフェースを使うと効果的な処理がかけるようになるのでそれを紹介します。
io.Reader/io.Writer
io.Reader/io.Writerはioパッケージで提供されているインターフェースです。
どこで使われているかというとbyte列をいじる際によく採用されています。よくみるところだとencoding/json
やos
パッケージのファイル周りで使われています。Benchmark
次の二つの関数のベンチマークを測ってみます。一つはreaderからbyteを取り出した後にmarshalし、もう一つはdecoderにFileインスタンスを渡しています。
package io_bench import ( "encoding/json" "fmt" "io/ioutil" "os" ) type test struct { Message string `json:"message"` } //not use io interface. func Ex1() error { f, err := os.Open("test.json") if err != nil { return fmt.Errorf("ex1 err: %w", err) } defer f.Close() data, err := ioutil.ReadAll(f) if err != nil { return fmt.Errorf("ex1 err: %w", err) } t := &test{} if err := json.Unmarshal(data, t); err != nil { return fmt.Errorf("ex1 err: %w", err) } return nil } //use io interface. func Ex2() error { f, err := os.Open("test.json") if err != nil { return fmt.Errorf("ex2 err: %w", err) } defer f.Close() t := &test{} if err := json.NewDecoder(f).Decode(t); err != nil { return fmt.Errorf("ex1 err: %w", err) } return nil }結果は次の通りです。(ベンチマーク用のテストコードはこちら)
> go test -bench . -benchmem goos: darwin goarch: amd64 pkg: github.com/sueken5/io-bench BenchmarkEx1-8 88365 12195 ns/op 2392 B/op 10 allocs/op BenchmarkEx2-8 1000000000 0.000065 ns/op 0 B/op 0 allocs/op PASS ok github.com/sueken5/io-bench 1.224sdecoderにFileインスタンスを渡してる方が圧倒時に早くメモリ効率も良いです。
まとめ
通信のレスポンスなどをこのようにioインターフェースをうまく使うことができればよりパフォーマンスのでるプログラムが書けそうです。
- 投稿日:2020-02-10T22:17:44+09:00
スクリプト言語の比較しながらGoのお勉強 〜 標準入力編
前回からのおさらい
前回、Hello World編にて、標準出力に文字列を表示するという処理を確認しました。
今回はその対になります標準入力にてキーボードにて入力する文字列を受け取る処理を比較してみたいと思います。標準入力
簡単にキーボードから1行入力された文字列を表示するプログラムを見ていくことにしましょう。
Goの標準入力
Goでの標準入力のプログラムです。
input.gopackage main import ( "fmt" "os" "bufio" ) func main() { stdin := bufio.NewScanner(os.Stdin) stdin.Scan() text := stdin.Text() fmt.Printf(text + "\n") }実行してみましょう。
$ go run input.go Hello, Go Hello, Goただ単に鸚鵡返しするだけですが、思い通りの処理はできています。
プログラムの中身ですが、
importで3つのパッケージを読み込んでいます。fmt
パッケージは標準出力でも使用したものですね。os
パッケージとbufio
パッケージは今回初めて使用するライブラリです。
os
パッケージは公式サイトによると、オペレーティングシステムの機能へのプラットフォームに依存しないインタフェースを提供します。
Unixライクな設計です。ドキュメントを読んでると
syscall
パッケージをラップしていることがわかりました。os
パッケージはシステムのローレベルな処理から汎用的にレベルを上げたパッケージということですね。I/Oのバッファリング機能を提供します。io.Reader・io.Writerをラップし、別のオブジェクト(ReaderまたはWriter)を作成します。インタフェースは同じままでバッファリングやその他便利な機能を追加します。
とのことで
io
パッケージをラップして機能拡張しているものの様です。処理に関してですが、10行目のNewScannerはドキュメントによると1行ごと読み込む処理です。
またos.Stdinの処理はsyscall.Stdin
で/dev/stdin
を開いたファイルディスクリプたということで、標準入力を1行ごと読み込む処理の様です。
11行目で1行ごとバッファし、12行目で変数に文字列を格納、そして15行目で出力という流れになっています。簡単な標準入力の処理ですが、なかなか理解しなければならないことの多いGoです。
では、他の言語との比較です。
Python
Pythonでは組み込み関数の
input
関数で入力処理を実装できます。
組み込み関数input.pytext = input() print(text)実行結果
$ python input.py Hello, Python Hello, PythonRuby
Rubyでは組み込み関数の
gets
で実装可能です。
module Kernelinput.rbtext = gets print text実行結果
$ ruby input.rb Hello, Ruby Hello, RubyPerl
PerlではI/O演算子の
<>
で標準入力のファイルハンドルSTDIN
を指定して入力処理を実装します。
I/O 演算子input.pl$text = <STDIN>; print $text;実行結果
$ perl input.pl Hello, Perl Hello, PerlBash
Bashでは
read
コマンドで入力処理を実装します。
man readinput.shread text echo $text実行結果
$ bash input.sh Hello, Bash Hello, Bash入力処理を比較してみて
入力処理を比較してみて感じたことはHello World編と同様で、本体は簡素に作られているというところです。
今回は多少パッケージのソースにも目を通してみたところ、低レベルの処理を駆使して実装はできるのでしょうが、
車輪の再発明は無駄で何も旨味がないためパッケージの使用を学ぶべきと感じました。
(但し、実装方法を確認することは多くの知見を得られますので、できる限りソースには目を通すべきだと思います)
では、次回以降は変数に関しての演算子や文字列処理について勉強したいと思います。
- 投稿日:2020-02-10T17:36:27+09:00
append関数で可変長引数を受け取る
組み込みの append 関数は可変長引数を受け取れるので、複数の要素を一度に追加できる。適切な型のスライスを持っているなら、その名前に続けて「...」と記述するとスライス中のそれぞれの項目を個別の引数として渡せる。
// 格納先のスライス初期化 gore> var options []string // 格納要素の初期化 gore> arr := []string{"a","b","c"} []string{ "a", "b", "c", } // 可変長引数でまとめて追加 gore> options = append(options, arr...) []string{ "a", "b", "c", } // スライスの宣言と可変長引数による受け渡しを同時に実施 gore> options = append(options, []string{"d","e","f"}...) []string{ "a", "b", "c", "d", "e", "f", }
- 投稿日:2020-02-10T17:19:15+09:00
Go で競技プログラミング攻略2 (AtCoder Beginners Selection 7~)
はじめに
この記事は自分のためにメモとして書いてあるので書き方はテキトー
間違っていたりこうしたらいいよっていうのは教えていただけるとありがたいです
競プロはマジの初心者スタートでやっていきたいと思いますABC088B - Card Game for Two
人とも自分の得点を最大化するように最適な戦略を取った時, Alice は Bob より何点多く取るか求めてください.
つまりは、順番に最大の数を取っていくということか。
sortパッケージを使うと一発でソートしてくれる最高
たくさん読み込めるようにするため以前作ったこれを使用
//文字列を1列入力 func scanStringLine() string { var strLine string scanner := bufio.NewScanner(os.Stdin) scanner.Scan() strLine = scanner.Text() return strLine } //string型の配列からint型の配列に変換 func StrToInt(strList []string) []int { var numList []int var toInt int for _, v := range strList { toInt, _ = strconv.Atoi(v) numList = append(numList, toInt) } return numList }
int型の配列のソート
こんな感じにすると昇順で並び替えてくれる。
sort.Ints(numList)gameの処理を書く
func game(numList []int) int { alliceSum := 0 bobSum := 0 ans := 0 //alliceのturnは0,bobは1 turn := 0 for i := len(numList) - 1; 0 <= i; i-- { if turn == 0 { alliceSum += numList[i] turn = 1 } else { bobSum += numList[i] turn = 0 } } //得点差 ans = alliceSum - bobSum return ans }turnのところすごく気に食わない
改善案あったら教えてくださいとりあえず
こんな感じになったよmain.gopackage main import ( "bufio" "fmt" "os" "sort" "strconv" "strings" ) func main() { var N int var strNumbers string var numList []int var arr []string //Nの入力 fmt.Scanf("%d", &N) //文字列を空白で区切って文字列に入れてint型に変換 strNumbers = scanStringLine() arr = strings.Split(strNumbers, " ") numList = StrToInt(arr) //numListを昇順にソート sort.Ints(numList) //gameの処理をして出力 fmt.Println(game(numList)) } //文字列を1列入力 func scanStringLine() string { var strLine string scanner := bufio.NewScanner(os.Stdin) scanner.Scan() strLine = scanner.Text() return strLine } //string型の配列からint型の配列に変換 func StrToInt(strList []string) []int { var numList []int var toInt int for _, v := range strList { toInt, _ = strconv.Atoi(v) numList = append(numList, toInt) } return numList } func game(numList []int) int { alliceSum := 0 bobSum := 0 ans := 0 //alliceのturnは0,bobは1 turn := 0 for i := len(numList) - 1; 0 <= i; i-- { if turn == 0 { alliceSum += numList[i] turn = 1 } else { bobSum += numList[i] turn = 0 } } //得点差 ans = alliceSum - bobSum return ans }おk、提出
クリア
ABC085B - Kagami Mochi
前回もそうなんだが最初の一文字は適当に読み込ませるだけで何も処理していない。
今回は入力回数が指定されているから、考えなきゃいけないんだけど予想では、入力回数分forを回してその処理に配列に追加していくことにすればいいのかな?
//N回分入力しint配列にいれる func inputNumList(N int)[]int{ var tmp int var numList []int for i := 0; i < N; i++ { fmt.Scanf("%d", &tmp) numList = append(numList, tmp) } return numList }tmpの変数定義したくないなぁ...
ScanFでそのままappend出来たらいいのに、、、、まあ、とりあえず作っていこう
numListをsort.Ints()で昇順に直しておく
次の配列のvalueが大きいときのみcounter++するって感じの処理を拡張for文して回す。書いていこう
func layerCounter(numList []int) int { layerSize := 0 layerCount := 0 for _, v := range numList { if layerSize < v { layerCount++ layerSize = v } } return layerCount }結果こんな感じになりました
main.gopackage main import ( "fmt" "sort" ) func main() { var N int var numList []int fmt.Scanf("%d", &N) numList = inputNumList(N) //numListのソート sort.Ints(numList) fmt.Println(layerCounter(numList)) } //N回分入力しint配列にいれる func inputNumList(N int) []int { var tmp int var numList []int for i := 0; i < N; i++ { fmt.Scanf("%d", &tmp) numList = append(numList, tmp) } return numList } func layerCounter(numList []int) int { layerSize := 0 layerCount := 0 for _, v := range numList { if layerSize < v { layerCount++ layerSize = v } } return layerCount }比較的想像がつきやすかった
これでどうだろうかおっ、クリア
- 投稿日:2020-02-10T16:47:48+09:00
GolangのBigQueryパッケージで外部データソースとしてCSVを指定するサンプル
BigQuery上で複数の異なるデータソース間でJOINとかしたいことがある。それをする方法。
以下のようなCSVがGCS上に用意されているとする。
people.csvname,age jessy,25 thomas,30 brian,34 tommy,33このCSVのデータをBigQuery上でテーブルっぽく使うには以下のような実装をする。
// スキーマフィールドを定義 nameSchema := bigquery.FieldSchema{ Name: "name", Required: true, Type: bigquery.StringFieldType, } ageSchema := bigquery.FieldSchema{ Name: "age", Required: true, Type: bigquery.IntegerFieldType, } // GCSをBigQuery上で外部データソースとして使うための設定を定義 gcsSrcConfig := bigquery.ExternalDataConfig{ SourceFormat: bigquery.CSV, SourceURIs: []string{"gs://your-gcp-project/somewhere/somedirectory/people.csv"}, Schema: []*bigquery.FieldSchema{ &nameSchema, &ageSchema, }, } // CSV上のデータをdataOnGcsとして指定しSQL文で使う q := client.Query(`select * from dataOnGcs`) q.TableDefinitions = map[string]bigquery.ExternalData{ "dataOnGcs": &gcsSrcConfig, }
ExternalDataConfig
にAutoDetect
というオプションがあり、これをtrueにすると自動でCSVのデータ構造からスキーマを推測してくれると書いてあったのだが、これをtrueにしてもselect文とかで指定できなかった。やはりSchema
に自前でスキーマ定義を渡してやるのが必要そうだ。GCS以外にもJSONやGoogleスプレッドシートをデータソースとして使えるらしい。ええやん。
- 投稿日:2020-02-10T02:32:27+09:00
RedisベースのGo Background Jobライブラリー
Asynq: RedisベースのGo Background Jobライブラリー
Ruby,Railsのサークルの中ではResqueやSidekiqがBackground-jobのライブラリーで人気ですが、Goのコミュニティーの中でこれといったライブラリーがあまり見つからなかったので自分でSidekiqのデザインを基にしてBackground Jobライブラリーを書いてみました(github.com/hibiken/asynq)。
GoやRedisに興味があってGithubでコントリビュートするプロジェクトを探していたら、是非!
- 投稿日:2020-02-10T00:17:42+09:00
flagパッケージを利用しているときに、オプションの値をスライスで受け取りたい / 同じオプションを複数回指定し、その値をすべてスライスに詰めたい
Go言語の
flag
パッケージを利用して、コマンドラインツールを作成していると、オプションの値をSliceで受け取りたい場合があります。たとえば「同じオプションを複数回指定し、その値をすべてスライスに詰めたい」という場合、どのように実装すればよいでしょうか?答えだけ先に述べておくと、
flag.Value
インターフェースを実装し、パース時にはflag.Var
を呼び出します。今回は一例を示すにあたって、
div
コマンドを作成してみます。div
コマンドは標準入力から数値が与えられると、-d
オプションで与えられた数値で割り切れるかどうかを確認し、割り切れる場合は標準入力の値をそのまま標準出力に出力するというものです。ここでは-d
オプションは複数回指定でき、複数回指定した場合は、1つでも割り切ることができればOKということにします。動作イメージは次の通りです。ここでは1から15までの数値のうち、3または5で割り切れるものを出力しています。
$ seq 15 | ./div -d 3 -d 5 3 5 6 9 10 12 15
div
コマンドの実装例は次の通りになります。package main import ( "bufio" "flag" "fmt" "os" "strconv" ) type Divisors []int func (ds *Divisors) String() string { return fmt.Sprintf("%v", *ds) } func (ds *Divisors) Set(s string) error { d, err := strconv.Atoi(s) if err != nil { return err } *ds = append(*ds, d) return nil } func main() { var divisors Divisors flag.Var(&divisors, "d", "除数を指定してください") flag.Parse() stdin := bufio.NewScanner(os.Stdin) for stdin.Scan() { // 標準入力から1行読み込み、整数に変換。 // 変換できない場合は理由を標準エラー出力に出力し、その行はスキップ line := stdin.Text() dividend, err := strconv.Atoi(line) if err != nil { fmt.Fprintln(os.Stderr, err) continue } // 標準入力から読み込んだ整数が、dオプションから取得した整数で割り切れるかどうかを検査。 // 割り切れた場合は標準入力から読み込んだ値を標準出力に出力する。 for _, divisor := range divisors { if dividend%divisor == 0 { fmt.Fprintln(os.Stdout, line) break } } } }ここでは
Divisors
型(実体はint
スライス)を作成、flag.Value
インターフェースの関数String()
とSet()
をそれぞれ実装しています。パース時にflag.Var
関数を呼び出すと、Divisors
型(実体はint
スライス)に-d
オプションの値が格納されることが分かります。