20200210のGoに関する記事は7件です。

goのioインターフェースを使ってみよう

はじめに

こんにちわ、すえけん(@sueken5)です。

今回の記事ではgolangのioインターフェースを使うと効果的な処理がかけるようになるのでそれを紹介します。

io.Reader/io.Writer

io.Reader/io.Writerはioパッケージで提供されているインターフェースです。
どこで使われているかというとbyte列をいじる際によく採用されています。よくみるところだとencoding/jsonosパッケージのファイル周りで使われています。

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.224s

decoderにFileインスタンスを渡してる方が圧倒時に早くメモリ効率も良いです。

まとめ

通信のレスポンスなどをこのようにioインターフェースをうまく使うことができればよりパフォーマンスのでるプログラムが書けそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクリプト言語の比較しながらGoのお勉強 〜 標準入力編

前回からのおさらい

前回、Hello World編にて、標準出力に文字列を表示するという処理を確認しました。
今回はその対になります標準入力にてキーボードにて入力する文字列を受け取る処理を比較してみたいと思います。

標準入力

簡単にキーボードから1行入力された文字列を表示するプログラムを見ていくことにしましょう。

Goの標準入力

Goでの標準入力のプログラムです。

input.go
package 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パッケージはシステムのローレベルな処理から汎用的にレベルを上げたパッケージということですね。

bufioパッケージは、

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.py
text = input()
print(text)

実行結果

$ python input.py
Hello, Python
Hello, Python

Ruby

Rubyでは組み込み関数のgetsで実装可能です。
module Kernel

input.rb
text = gets
print text

実行結果

$ ruby input.rb
Hello, Ruby
Hello, Ruby

Perl

PerlではI/O演算子の<>で標準入力のファイルハンドルSTDINを指定して入力処理を実装します。
I/O 演算子

input.pl
$text = <STDIN>;
print $text;

実行結果

$ perl input.pl
Hello, Perl
Hello, Perl

Bash

Bashではreadコマンドで入力処理を実装します。
man read

input.sh
read text
echo $text

実行結果

$ bash input.sh
Hello, Bash
Hello, Bash

入力処理を比較してみて

入力処理を比較してみて感じたことはHello World編と同様で、本体は簡素に作られているというところです。
今回は多少パッケージのソースにも目を通してみたところ、低レベルの処理を駆使して実装はできるのでしょうが、
車輪の再発明は無駄で何も旨味がないためパッケージの使用を学ぶべきと感じました。
(但し、実装方法を確認することは多くの知見を得られますので、できる限りソースには目を通すべきだと思います)
では、次回以降は変数に関しての演算子や文字列処理について勉強したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

append関数で可変長引数を受け取る

書籍:Go言語によるWebアプリケーション開発より。

組み込みの 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",
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.go
package 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.go
package 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
}

比較的想像がつきやすかった
これでどうだろうか

おっ、クリア

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GolangのBigQueryパッケージで外部データソースとしてCSVを指定するサンプル

BigQuery上で複数の異なるデータソース間でJOINとかしたいことがある。それをする方法。

以下のようなCSVがGCS上に用意されているとする。

people.csv
name,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,
}

ExternalDataConfigAutoDetectというオプションがあり、これをtrueにすると自動でCSVのデータ構造からスキーマを推測してくれると書いてあったのだが、これをtrueにしてもselect文とかで指定できなかった。やはりSchemaに自前でスキーマ定義を渡してやるのが必要そうだ。

GCS以外にもJSONやGoogleスプレッドシートをデータソースとして使えるらしい。ええやん。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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でコントリビュートするプロジェクトを探していたら、是非!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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オプションの値が格納されることが分かります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む