20210123のGoに関する記事は11件です。

AtCoder Beginner Contest 189のメモ

前置き

Atcoderをやってみたので、自分用のメモです。
あとから加筆・修正する予定です。

問題

https://atcoder.jp/contests/abc189

A

Q_A.go
package main

import (
    "fmt"
    "strings"
)

func main() {
    var C string
    fmt.Scanf("%s", &C)
    s := strings.Split(C, "")

    var flag bool = true
    var c string = s[0]
    for i:=1; i<3; i++{
        if c != s[i]{
            flag = false
        }
    }

    if flag{
        fmt.Printf("Won\n")
    } else {
        fmt.Printf("Lost\n")
    }
}

B

Q_B.go
package main

import (
    "fmt"
)

func main() {
    var N int
    var X int64
    fmt.Scanf("%d %d", &N, &X)
    X = X*100
    var alcohole int64 = 0
    var v, p int64
    var ans int = -1
    for i:=0; i<N; i++{
        fmt.Scanf("%d %d", &v, &p)
        alcohole += v * p

        if alcohole > X {
            ans = i + 1
            break
        }
    }

    fmt.Printf("%d\n", ans)
}

C

Q_C.go
package main

import (
    "fmt"
)

func main() {
    var N int64
    fmt.Scanf("%d", &N)

    A := make([]int64, N)
    var i int64
    for i=0; i<N; i++{
        fmt.Scanf("%d", &A[i])
    }

    var mikan int64 = 0

    var max, min int64
    var l,r int64
    for l=0; l<N; l++{
        min = A[l]
        for r=l; r<N; r++{
            if A[r] < min {
                min = A[r]
            }
            max = min * (r - l + 1)
            if max > mikan {
                mikan = max
            }
        }
    }

    fmt.Printf("%d\n", mikan)

}

D

Q_D.go
package main

import (
    "fmt"
)

func dfs(S []string, N int, i int) int64{

    var count int64 = 0

    if i == 0{
        return 1
    }

    count += dfs(S, N, i-1)

    if S[i] == "OR"{

        var c int64 = 1
        for j:=0; j<i; j++{
            c = c * 2
        } 
        count += c
    }

    return count
}

func main() {
    var N int
    fmt.Scanf("%d", &N)

    S := make([]string, N+1) // AND/OR
    for i:=1; i<=N; i++{
        fmt.Scanf("%s", &S[i])
    }

    var count int64 = 0

    count += dfs(S, N, N)

    fmt.Printf("%d\n", count)
}

E

覚えてたら後で書きます。

F

覚えてたら後で書きます。

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

GolangでGoogle Calender APIを使って日本の祝日情報を取得する

祝日の一覧が欲しいなと思った時にGoogle Calendarから取得する方法を調べたので
まとめておきたいと思います。
GolangでGoogle Calender APIを使って取得します。

はじめに

google calender api ドキュメント
にgolangで利用する時のクイックスタートが乗っているので、参考に進めていきます。

1. 作業ディレクトリの作成

$ mkdir google_calender
$ cd google_calender/

2. Google APIを利用するためのcredentialsを取得

スクリーンショット 2021-01-22 21.18.58.png
「Enable the Google Calender API」をクリックして進むとキー情報が書いてあるcredenrial.jsonがダウンロードできるので、作業ディレクトリに置きます。

3. 必要なモジュールの追加

ドキュメントに従ってモジュールを追加していきます

$ go get -u google.golang.org/api/calendar/v3
$ go get -u golang.org/x/oauth2/google

4. 実行ファイル作成

$ touch quickstart.go

quickstart.go
githubにコードがあるので、コピペします。

mod初期コマンド

$ go mod init google_calender

ビルドします

$ go build

5. tokenの取得

この状態で実行するとURLが表示されるので、実行した状態でブラウザにコピペして利用を許可します。

$ go run quickstart.go
https://accounts.google.com/o/oauth2/auth...

利用許可を進めていくと最後に「このコードをコピーしてアプリケーションに貼り付けてください」と表示されるので、先ほどの実行した状態のコンソールにコピペします。
成功すると「token.json」というファイルが出来ていると思います。
ここまででquickstartを実行する準備が整いました。

6. 祝日を取得するようにコードを修正する

quickstartのままだと利用許可したアカウントのカレンダーに登録されている予定を取得するようになっています。
これを祝日が取得できるカレンダーから取得するようにします。

quickstartで使用されている

L.109
events, err := srv.Events.List("primary")

のドキュメントを見ると
golang google api doc
パラメータでcalender idを渡せるようです。
※primaryはアカウントのプライマリカレンダー

調べた所、日本の祝日用のcalender idが

ja.japanese#holiday@group.v.calendar.google.com

こちらになるようです。

先ほどのL.109のcalender idを書き換えます。

L.109
events, err := srv.Events.List("ja.japanese#holiday@group.v.calendar.google.com")

7. 実行して祝日のリストを取得する

実行します

$ go run quickstart.go
Upcoming events:
建国記念の日 (2021-02-11)
天皇誕生日 (2021-02-23)
春分の日 (2021-03-20)
昭和の日 (2021-04-29)
憲法記念日 (2021-05-03)
みどりの日 (2021-05-04)
こどもの日 (2021-05-05)
海の日 (2021-07-22)
体育の日 (2021-07-23)
山の日 (2021-08-08)

取れました!!!!
quickstartでは現在時刻から10件取得するようになっているので、L.110のパラメータを修正すれば期間や件数を変更できます。

おわりに

簡単に祝日の一覧を取得する事ができました!!!!!

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

go言語学習雑記2

go言語の勉強雑記その2
今回は文法

for文

goの言語は他の言語と比べて非常に柔軟性があります。
まずは普通のfor文

    for i := 0; i < 10; i++ {
        sum += i
    }

この条件式をいじるとwhile文のような挙動を実現できます

  sum1 := 1
  for sum < 1000 {
    sum1 += sum
  }

これで条件式を満たさなくなるまで処理を続けます。

さらにいじると無限ループができます。

  for {}

if文

 if x>0 {}

シンプルにifを書くだけ、 但し評価を()でくくる必要はないです

また、その場で変数を代入して比較を行うこともできます

if i:=3; i<4 {
  // 処理 
}

switch文

switch文は評価される変数を省略できる。

    t := 11
    switch {
    case t < 12:
        fmt.Println("Good morning!")
    case t < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }

なおbreakは自動で挿入されるため書く必要はない

defer文

関数の呼び出し順序を操作する文法。

defer文に実行したい関数を渡すと、呼び出し元の処理が終わった段階で実行される。

func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}

main関数が終了した時点で defer文の関数が実行されるため出力される文字列は

hello
world

になる。

deferはstackで実行する関数の順番を操作するので、

    fmt.Println("counting")

    for i := 0; i < 10; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("done")

上記の場合は

counting 9 8 7 6 5 4 3 2 1 0 done

となります。
stack は LIFO になるので、入れた順番とは逆の順番で出力されるので、上記のような結果になります。

最後に

defer文の使いどきがわからない。下手に使うと可読性が落ちそうなのでチーム開発でのルール決めの難しいそう。

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

AWS Translateを使ってGo言語で翻訳するサンプル

タイトルの通りですが、公式ドキュメントにサンプルが含まれていなかったので記事にしました。

サンプルソースはGithubにも置いてあります。
https://github.com/yuukimiyo/go-aws-translate-sample

簡単なコードですが、73言語(2021年1月現在)の双方向翻訳が可能な簡易ツールとして使えます。
Public Domainですので、コピペなどご自由に。

package main

import (
    "flag"
    "fmt"

    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/translate"
)

func main() {
    sourceText := flag.String("text", "これは翻訳のテストです", "source text")
    sourceLC := flag.String("slc", "ja", "source language code [en|ja|fr]...")
    targetLC := flag.String("tlc", "en", "target language code [en|ja|fr]...")
    flag.Parse()

    sess := session.Must(session.NewSession())
    trs := translate.New(sess)

    result, err := trs.Text(&translate.TextInput{
        SourceLanguageCode: aws.String(*sourceLC),
        TargetLanguageCode: aws.String(*targetLC),
        Text:               aws.String(*sourceText),
    })
    if err != nil {
        panic(err)
    }

    fmt.Printf(*result.TranslatedText)
}

上記ドキュメントを読み、Text()関数で翻訳を実施するんだ、という事さえわかれば難しいことは何もないです。

手元で試したい人向け

前提

AWSアカウントと、次のポリシーをアタッチしたユーザが必要です。

  • TranslateFullAccess

無料期間もありますがAWS Translateは有料サービスです、公式サイトの料金表は必ず確認してください。
AWS Translate

Go言語の実行環境が必要です。
厳密にどのバージョンからかは把握していませんが、開発/確認には次の環境を利用しています。

Ubuntu20.04(WSL2)
go1.15.7.linux-amd64

環境変数の設定

他言語のAWS SDKでも同じですが、次の環境変数をセットしておきます。

  • AWS_REGION
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

bashの場合

export AWS_REGION=ap-northeast-1
export AWS_ACCESS_KEY_ID=<access key id>
export AWS_SECRET_ACCESS_KEY=<Secret access key>

fish shellの場合

set -x AWS_REGION ap-northeast-1
set -x AWS_ACCESS_KEY_ID <access key id>
set -x AWS_SECRET_ACCESS_KEY <Secret access key>

Download

git clone git@github.com:yuukimiyo/go-aws-translate-example.git

動かしてみる

デフォルトでは、日本語ー>英語の翻訳を行います。

go run main.go --text="翻訳のテストです"

> Testing the translation

言語を指定したい場合は次のように言語コードを指定します。
(例えば英語ー>日本語の場合)

go run main.go --text="This is test of AWS Translate" --slc="en" --tlc="ja"

> これは AWS 翻訳のテストです

利用可能な言語コードの一覧は次のURLにあります。
Supported Languages and Language Codes

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

go module のバージョニング

はじめに

自身で作った go package を公開し、別のプロジェクトで利用されるというケースで、バージョンがどのように扱われるのか調べました。

バージョン指定なし

githubにhelloというリポジトリを作り、そこにパッケージを作成します。

go.mod
module github.com/username/hello

go 1.15
hello.go
package hello

import "fmt"

func Hello() {
    fmt.Println("this is version v0.0.0")
}

パッケージを呼び出す

パッケージを呼び出すためのクライアントを用意します。

$ go mod init hello_client
$ go get -u github.com/username/hello

以下のようなgo.modができます。バージョン(v0.0.0)と参照しているコミット(40fe1c06b3a8)がわかります。

go.mod
module hello_client

go 1.15

require github.com/username/hello v0.0.0-20210123030850-40fe1c06b3a8 // indirect
main.go
package main

import (
    m "github.com/username/hello"
)

func main() {
    m.Hello()
}
$ go run main.go 
this is version v0.0.0

意図通りに実行できました。

特にバージョンについて考慮しないと、masterブランチを参照することがわかりました。例えばこのあとmasterに更新がありその最新版を取得したい場合は、再びgo get -u github.com/username/helloを実行することで最新のコミットを参照するようにgo.modが更新されます。

補足: masterブランチの参照

masterブランチを参照する」というのは多少の語弊があって、正しくは「デフォルトブランチを参照」します。例えばmasterブランチを起点にmainブランチを作り、mainブランチをデフォルトブランチにするように設定が変更された場合、go get -u github.com/username/hellomainブランチを参照します。

v1

バージョンを指定しない場合の問題点は、go get -uを実行したタイミングによって参照される資材が異なってしまう点です。そこで常に同じ資材を参照できるようにバージョン管理をします。

hello.goに変更を加えてプッシュしたら、タグを作成します。

hello.go
package hello

import "fmt"

func Hello() {
    fmt.Println("this is version v1.0.0")
}
$ git tag -a v1.0.0 -m "Version 1.0.0"
$ git push --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 167 bytes | 167.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To https://github.com/username/hello.git
 * [new tag]         v1.0.0 -> v1.0.0

パッケージを呼び出す

先ほど作成したクライアントで、パッケージをアップデートします。

$ go get -u github.com/username/hello

go.modは以下のようになります。

go.mod
module hello_client

go 1.15

require github.com/username/hello v1.0.0

実行してみます。

$ go run main.go 
this is version v1.0.0

タグを打つことで、masterブランチではなくv1.0.0タグを参照するようになりました。
これでmasterブランチで開発を続けても、利用者は常にv1.0.0を参照することができるようになります。

v1.1

マイナーバージョンを一つ上げてタグを作成してみます。手順は、v1の時と同様ですので省きます。

パッケージを呼び出す

クライアントも同じようにパッケージをアップデートして実行してみます。

$ go get -u github.com/username/hello
$ go run main.go 
this is version v1.1.0

参照先がv1.1.0に切り替わったことがわかります。

このようにマイナーアップデートやパッチアップデートが行われたら、それらが最新版として認識されることがわかりました。

// プレフィックスとしてのvは必須のようです。例えば1.2.0のようなタグの切り方はv1の最新版として認識されませんでした。

v1.0.0を参照したいときは明示的にバージョンを指定します。

go get github.com/username/hello@v1.0.0

v2

セマンティックバージョニングに従うと、メジャーバージョンのアップデートは互換性のない変更が行われた時に発生します。つまり、v1とv2は自動的にアップデートされるべきでもないし、別物として扱うべきです。

そのため、v1の時と同じようなバージョンのアップデートの仕方をしてもうまくいきません。
(v0 / v1v2 ~ で扱いが変わります。詳しくは、公式ブログを参照:https://blog.golang.org/v2-go-modules)

パッケージ側

hello.go
package hello

import "fmt"

func Hello() {
    fmt.Println("this is version v2.0.0")
}
$ git tag -a v2.0.0 -m "Version 2.0.0"

クライアント側

$ go get -u github.com/username/hello
$ go run main.go 
this is version v1.1.0

ここで -u オプションについて再確認します。メジャーアップデートはされないことがわかります。

The -u flag instructs get to update modules providing dependencies of packages named on the command line to use newer minor or patch releases when available.

また、@2.0.0とサフィックスを指定してもエラーになります。

$ go get github.com/username/hello@v2.0.0
go get github.com/username/hello@v2.0.0: github.com/username/hello@v2.0.0: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2

ということで、v2以降のバージョンを認識させるためにパッケージのgo.modを修正して、v2.0.1を作成してみます。

go.mod
module github.com/username/hello/v2

go 1.15
$ git tag -a v2.0.1 -m "Version 2.0.1"

パッケージを呼び出す

パスが変わりましたので、クライアントも呼び出し方を合わせて変更します。

go get -u github.com/username/hello/v2

go.modをみてみると別々のパッケージのように扱われていることがわかります。

go.mod
module hello_client

go 1.15

require (
    github.com/username/hello v1.1.0
    github.com/username/hello/v2 v2.0.1
)
main.go
package main

import (
    m1 "github.com/username/hello"
    m2 "github.com/username/hello/v2"
)

func main() {
    m1.Hello()
    m2.Hello()
}

実行してみると以下のようになります。

$ go run main.go 
this is version v1.1.0
this is version v2.0.1

このようにメジャーバージョンが上がるとパスが別々になり、それぞれのバージョンごとにメンテナンスができるようになります。

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

Gormの1.9と1.20で失敗したお話

最初に

2021年1月23日時点で、GORMの最新版はv1.20.11ですが、Qiitaや巷の記事はv1.9.xの物が多く(特に日本語)
公式ドキュメントは、基本v1.20がヒットするのでGorm初心者の私は混乱したというお話です。

バージョンはよく確認しよう

当たり前のことですが、パッケージのバージョンと参考記事のバージョンはよく確認しましょう。

GORMのバージョン

v1.9.x系
import (
  "github.com/jinzhu/gorm"
   _ "github.com/jinzhu/gorm/dialects/mysql"

)
v1.20.11
import (
  "gorm.io/gorm"
  "gorm.io/driver/mysql"
)

1.9系と1.20系でインポートするパッケージが異なります。
ネットの記事をそのままに、「go get github.com/jinzhu/gorm」すると1.9系がインストールされます。

変更点

ほかに記事を上げてくださっている方がいらっしゃるので、すべては書きませんが
私が影響を受けたのは、
 ・「RecordNotFound」がなくなったこと
 ・DB接続の書き方が変わったこと
 ・バッチインサートができるようになったこと!(めっちゃうれしい)
がまず影響を受けました。

まとめ

Goのパッケージは、現在も開発が活発で進化していますが、
そのぶん技術系記事も更新前の情報がひっかかりやすいです。
基本的なことでお恥ずかしい限りですが、
久しぶりに引っかかったので初心を忘れないために記事にしました。

ありがとうございました。

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

GinでBodyを取得する際の最大サイズについて

概要

ginで不確定なjosnをinterfaceに変換する際、一定サイズを超えると変換できない状態になった。

コード

func SAmple(c *gin.Context) {
    ~~~
    //bodyを取得
    buf := make([]byte, 1028)
    n, _ := c.Request.Body.Read(buf)
    body := string(buf[0:n])
    //bodyをmapに変換
    var json_parse map[string]interface{}
    err := json.Unmarshal([]byte(b), &json_parse)
    ~~~
}

c.Request.Body.Readでバイトスライスにボディを格納してstring形に変換していますが、
jsonのサイズが一定値を超えるとパースできなくなりました。

私の環境ではnが2575で上限になりそれ以降のリクエストボディがbodyに格納されずjson.Unmarssamlでエラーになっていました。

修正前に

Webサーバがbodyを制限なく受け入れるとbodyサイズだけサーバのメモリが消費されるため、
大きなbodyを送信しサーバをダウンさせるセキュリティホールになります。
そのため、API側で受け取れるbodyに上限を設けることは、Webに公開するAPIの場合必須になります。

その点は注意したうえで実装してください。

修正後

func SAmple(c *gin.Context) {
    ~~~
    //bodyを取得
    var bodyBytes []byte
    if c.Request.Body != nil {
      bodyBytes, _ = ioutil.ReadAll(c.Request.Body)
    }
    c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
    bodyString := string(bodyBytes)
    //変換
    var json_parse map[string]interface{}
    err := json.Unmarshal([]byte(b), &json_parse)
    ~~~
}

ioutil.ReadAllですべてのbodyを読み込めるので問題なくjsonをinterfaceへ変換できます。

まとめ

c.Request.Body.Read(buf)にはサイズに上限がある。
全部読み込みたい場合は、ioutil.ReadAll(c.Request.Body)で読み込みましょう

ありがとうございました。

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

自宅内の見守りカメラをアップデートした:④実装編:Go言語の基本を駆使してラズパイ制御&HTTPサーバー

はじめに

本記事はこちらのパート④にあたります。
もし内容に興味を持たれましたら、他のパートもご覧頂けると幸いです。

前回の記事までで、作成するシステムの構成まで固まりましたので、いよいよゴリゴリ実装していきます…!

成果物(クライアント側)

image.png

コード

こちらに全て掲載しました。
https://github.com/tenkoh/go-home-camera

補足説明など

自動調整の実行

UIにある自動調整実行ボタンに対して、jQueryのGETメソッドを送信する関数を設定しました。
リクエスト先はapi/calibrationとしてあり、サーバ側ではそのURIにリクエストが送られると、一旦静止画撮影を停止し、キャリブレーションを実行するようにしています。
(URIにリクエストが届くと、静止画撮影のgoroutineとのチャネルに値を送信するようにし、goroutine側ではチャネルに値が入ったら静止画撮影を一旦やめて自動キャリブレーションを行うようにして実現しています)

UI

自分が使うデバイスがiPhoneかiPadなので、それに合わせたレスポンシブデザインを簡単に作っておしまいにしてあります。
(あんまりcssの編集が得意ではないので、必要最低限で…)

おわりに

最初に作ったシステムよりも段違いにサーバーのレスポンスも早く、また静止画撮影設定の固定化+自動調整機能実装によって見やすい画像が取得できるようになりました!
また自分でシステム構成を考えることで、学習教材を使った勉強の中ではいまいちピンと来ていなかったAPIの作成やその利用についても深く理解できたような気がします。よい勉強になりました。

後日談

しばらく使い込んでいたら、ある日カメラモジュールのフラットケーブルがお亡くなりになりました…。
せっかくここまでシステムを作ったので、ケーブルを交換して再稼働させようと思います :cry:

出典など

Go言語の学習にあたってはUdemyさんの講座にお世話になりました。
https://www.udemy.com/course/go-fintech/

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

自宅内の見守りカメラをアップデートした:③構成を変更しよう編:ラズパイカメラモジュールのマニュアル撮影を極める…!

はじめに

本記事はこちらのパート③にあたります。
もし内容に興味を持たれましたら、他のパートもご覧頂けると幸いです。(順次執筆中…)

システム構成の変更

ハードウェアは据え置きで、ソフトウェアのみ構成を変更します。

パフォーマンス改善:画像取得方法の変更

カメラモジュールを使って単純に静止画を取得したいだけなので、標準コマンドを自作プログラムの中から呼び出して使うようにします。

Before After
Opencv-python ラズパイ標準コマンドraspistill

取得画像の明るさ適正化:画像取得設定の固定化とキャリブレーションモードの導入

画像取得設定を固定化するには、raspistillのオプションで各種自動調整を切りつつ、必要なパラメータを全て指定する必要があります。
ここで言うところの自動調整とは、オートホワイトバランス(awb)と自動ゲインコントロール(agc)です。
それらをoffにすると、下記の項目を指定する必要があります。公式ドキュメントを見ても端的にまとまっていなかったため、どのオプションを与えれば良いかを明確にするまでが大変でした…。

オプション 内容
-ss シャッター開放時間
-br 画像の明るさ
-awbg ホワイトバランス
-ag アナログゲイン
-dg デジタルゲイン

これをいちいちトライアンドエラーするのは辛いです。
そこで同じくraspistillのオプションで実現できる、自動調整結果の標準出力-setオプションを使います。

-setオプションを使うと以下のようなカメラ設定値が標準出力されます。何行も出力されてきますが、基本的には一番最後の行が調整完了済み結果となります。
そこから次の対応付けをして各オプションに値を与えればOKです。

input
raspistill -set
output
Camera control callback  cmd=0x48435045mmal: Exposure now 499982, analog gain 1075/256, digital gain 256/256
mmal: AWB R=314/256, B=708/256
Camera control callback  cmd=0x48435045mmal: Exposure now 499982, analog gain 1075/256, digital gain 258/256
mmal: AWB R=317/256, B=698/256
Camera control callback  cmd=0x48435045mmal: Exposure now 499982, analog gain 1130/256, digital gain 256/256
mmal: AWB R=309/256, B=729/256

オプション 標準出力結果との対応
-ss Exposure now の値をそのまま与える
-awbg AWB R, Bをそれぞれ小数に直して与える。例: -awbg 1.3, 1.7
-ag analog gainを小数に直して与える
-dg digital gainを小数に直して与える

これにより、一度自動調整を実行して取得したカメラ設定を使って連続して静止画を撮影することができます。
awb, agcを行う必要がなくなるため、撮影のインターバルを狭めることもできるようになります!

最終的な撮影コマンドを以下に示しておきます。xxxは適宜設定する値です。

raspistill -n -awb off -ex off -ss xxx -md xxx -o xxx.jpg -br 50 -awbg xxx,xxx -ag xxx -dg xxx -t xxx  

また自動調整時の実行コマンドも同様に示しておきます。

raspistill -set -br 0 -ex xxx

ここで、-exは露出設定です。autonightなど、シーンごとの設定が用意されているため、ここは使用する時間帯等に応じて変更できるようにしたほうが良さそうです。

システム構成図の落書き

以上を踏まえたシステム構成図の落書きを示します。
前回記事で明言したようにGo言語決め打ち!としたので、UIを配信するHTTPサーバと並行して動く撮影用goroutineを用意し、ユーザ操作によって撮影設定の自動調整(calibration)が指示されたときのみ、撮影設定を上書きするような構成とします。
このあたりの仕組はちょうど学習していたGo言語を使ったapi構築を応用してみました。

78537.jpg

おわりに

raspistillのマニュアル撮影設定方法を調べるのが一番大変でした…。
なんとかシステム構成が決まりましたので、いよいよ実装→稼働です…!!

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

[Go] データをファイルに書き込み方法をまとめる

色んな書き込み方があり、何かとごちゃまぜになったので、一旦まとめてみました。
パターンがあり、いつ何を使えば良いか見えてきたので、同じく混乱している方の参考になればと思います。

ざっくりと

こんな感じになる。

  1. 書き込み先を決める (例: file, buffer)。ファイルの場合 defer Close() を忘れずに
  2. データを変換する (例: []byte, Marshal), または 書き込むやつを取得する (例: Writer, Encoder)
  3. 書き込む (例: Write, Encode)

データ格納先へのデータの渡し方が多少違うので、覚えてなければ、都度マニュアルを確認しましょ。

単純に文字列をファイルに書き込む

なんでもいいからファイルに書き込みたいときに使います。

全体の流れ

  1. 書き込み先のファイル作成 (f := os.Create)
  2. バイト文字列に変換 (d := []byte{"文字列"})
  3. 書き込み (f.Write(d))

コード①

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    // 1. 書き込み先のファイル作成
    f, err := os.Create("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    // 2. バイト文字列に変換
    d := []byte("バイト文字列に変換")

    // 3. 書き込み
    n, err := f.Write(d)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%d bytes 書き込んだよ!", n)
}

コード②: ioutil.WriteFile を使えばファイル作成ステップ(①)を省略できる

package main

import (
    "io/ioutil"
    "log"
)

func main() {

    // 2. バイト文字列に変換
    d := []byte("バイト文字列に変換")

    // 3. 書き込み
    err := ioutil.WriteFile("test.txt", d, 0644)
    if err != nil {
        log.Fatal(err)
    }
}

ライター (Writer) を使って書き込む

bufio や CSV で使います。
参考: NewWriter

全体の流れ

  1. 書き込み先のファイル生成 (f := os.Create)
  2. 生成したファイルに書き込む、ライターを取得 (w := NewWriter(f))
    1. パッケージによっては、defer w.Flush() を忘れずに (例: bufio & CSV)
  3. 書き込み (w.Write())
    1. CSV の場合、引数は []string を渡す。
    2. Bufio の場合、引数は []byte を渡す。ただ WriteString[]string を渡せる

コード

package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {

    data := [][]string{
        {"name", "age"},
        {"田中", "25"},
        {"加藤", "30"},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.csv")
    if err != nil {
        log.Fatalln("failed to open file", err)
    }
    defer f.Close()

    // 2. ライターを取得
    w := csv.NewWriter(f)
    defer w.Flush()

    // 3. 書き込み
    for _, record := range data {
        err := w.Write(record)
        if err != nil {
            log.Fatal(err)
        }
    }
}

Encoder を使って書き込む

JSON や XML で使います。
参考: NewEncoder

全体の流れ

  1. 書き込み先のファイル生成 (f := os.Create("test.json"))
  2. ファイルに書き込む、エンコーダを取得 (encoder := json.newEncoder(f))
  3. 構造体を JSON データにエンコードする (encoder.Encode(&data))

コード ①

package main

import (
    "encoding/json"
    "log"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.json")
    if err != nil {
        log.Fatal(err)
    }

    // 2. ファイルに書き込む、エンコーダを取得
    encoder := json.NewEncoder(f)

    // 3. data をファイルに書き込み
    err = encoder.Encode(&data)
    if err != nil {
        log.Fatal(err)
    }

}

コード ②: エンコーダーの取得と書き込み、一連の流れで書ける

package main

import (
    "encoding/json"
    "log"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.json")
    if err != nil {
        log.Fatal(err)
    }

    // 2. ファイルに書き込む、エンコーダを取得 & 3 書き込み
    err = json.NewEncoder(f).Encode(&data)
    if err != nil {
        log.Fatal(err)
    }
}

Marshal を使って書き込む

JSON や XML で使います。

参考: Marshal

全体の流れ

  1. 書き込み先のファイル生成 (f := os.Create("test.json))
  2. 構造体を JSON データに変換する ( output, _ := json.Marshal(&data))
  3. データを書き込む (f.Write(output))

コード

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.json")
    if err != nil {
        log.Fatal(err)
    }

    // 2. バイト文字列の JSON データに変換
    output, err := json.Marshal(&data)
    if err != nil {
        log.Fatal(err)
    }

    // 3. data を書き込み
    n, err := f.Write(output)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%d bytes 書き込んだよ!", n)
}

番外編: 書き込み先がバッファの場合

もちろん、ファイルだけでなくバッファ (buf := new(bytes.Buffer)) にも書き込めます

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のバッファを生成
    buf := new(bytes.Buffer)

    // 2. バッファに書き込む、エンコーダを取得
    encoder := json.NewEncoder(buf)

    // 3. data をバッファに書き込む
    err := encoder.Encode(&data)
    if err != nil {
        log.Fatal(err)
    }
}

番外編: Encode vs Marshal どっちを使うべきか

JSON や XML は共に、Encode と Marshal が使え、同じ結果が得られます。
では違いはなんなのでしょうか?

Stack Overflow( 参考: in Golang, what is the difference between json encoding and marshalling ) によると、違いはこのようです。

  • Marshal => String
  • Encode => Stream

使い分けとして、文字列やバイト文字としてして扱いたい場合は Marshal、それ以外では Encode を使うようです。

また、JSON と XML はどちらの方法でも使えますが、ベンチマークでは Encode の方が早いようです。(TODO: 後で確認する)

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

[Go] データをファイルに書き込む方法をまとめる

色んな書き込み方があり、何かとごちゃまぜになったので、一旦まとめてみました。
パターンがあり、いつ何を使えば良いか見えてきたので、同じく混乱している方の参考になればと思います。

ざっくりと

こんな感じになる。

  1. 書き込み先を決める (例: file, buffer)。ファイルの場合 defer Close() を忘れずに
  2. データを変換する (例: []byte, Marshal), または 書き込むやつを取得する (例: Writer, Encoder)
  3. 書き込む (例: Write, Encode)

データ格納先へのデータの渡し方が多少違うので、覚えてなければ、都度マニュアルを確認しましょ。

単純に文字列をファイルに書き込む

なんでもいいからファイルに書き込みたいときに使います。

全体の流れ

  1. 書き込み先のファイル作成 (f := os.Create)
  2. バイト文字列に変換 (d := []byte{"文字列"})
  3. 書き込み (f.Write(d))

コード①

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {

    // 1. 書き込み先のファイル作成
    f, err := os.Create("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()

    // 2. バイト文字列に変換
    d := []byte("バイト文字列に変換")

    // 3. 書き込み
    n, err := f.Write(d)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%d bytes 書き込んだよ!", n)
}

コード②: ioutil.WriteFile を使えばファイル作成ステップ(①)を省略できる

package main

import (
    "io/ioutil"
    "log"
)

func main() {

    // 2. バイト文字列に変換
    d := []byte("バイト文字列に変換")

    // 3. 書き込み
    err := ioutil.WriteFile("test.txt", d, 0644)
    if err != nil {
        log.Fatal(err)
    }
}

ライター (Writer) を使って書き込む

bufio や CSV で使います。
参考: NewWriter

全体の流れ

  1. 書き込み先のファイル生成 (f := os.Create)
  2. 生成したファイルに書き込む、ライターを取得 (w := NewWriter(f))
    1. パッケージによっては、defer w.Flush() を忘れずに (例: bufio & CSV)
  3. 書き込み (w.Write())
    1. CSV の場合、引数は []string を渡す。
    2. Bufio の場合、引数は []byte を渡す。ただ WriteString[]string を渡せる

コード

package main

import (
    "encoding/csv"
    "log"
    "os"
)

func main() {

    data := [][]string{
        {"name", "age"},
        {"田中", "25"},
        {"加藤", "30"},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.csv")
    if err != nil {
        log.Fatalln("failed to open file", err)
    }
    defer f.Close()

    // 2. ライターを取得
    w := csv.NewWriter(f)
    defer w.Flush()

    // 3. 書き込み
    for _, record := range data {
        err := w.Write(record)
        if err != nil {
            log.Fatal(err)
        }
    }
}

Encoder を使って書き込む

JSON や XML で使います。
参考: NewEncoder

全体の流れ

  1. 書き込み先のファイル生成 (f := os.Create("test.json"))
  2. ファイルに書き込む、エンコーダを取得 (encoder := json.newEncoder(f))
  3. 構造体を JSON データにエンコードする (encoder.Encode(&data))

コード ①

package main

import (
    "encoding/json"
    "log"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.json")
    if err != nil {
        log.Fatal(err)
    }

    // 2. ファイルに書き込む、エンコーダを取得
    encoder := json.NewEncoder(f)

    // 3. data をファイルに書き込み
    err = encoder.Encode(&data)
    if err != nil {
        log.Fatal(err)
    }

}

コード ②: エンコーダーの取得と書き込み、一連の流れで書ける

package main

import (
    "encoding/json"
    "log"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.json")
    if err != nil {
        log.Fatal(err)
    }

    // 2. ファイルに書き込む、エンコーダを取得 & 3 書き込み
    err = json.NewEncoder(f).Encode(&data)
    if err != nil {
        log.Fatal(err)
    }
}

Marshal を使って書き込む

JSON や XML で使います。

参考: Marshal

全体の流れ

  1. 書き込み先のファイル生成 (f := os.Create("test.json))
  2. 構造体を JSON データに変換する ( output, _ := json.Marshal(&data))
  3. データを書き込む (f.Write(output))

コード

import (
    "encoding/json"
    "fmt"
    "log"
    "os"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のファイル生成
    f, err := os.Create("test.json")
    if err != nil {
        log.Fatal(err)
    }

    // 2. バイト文字列の JSON データに変換
    output, err := json.Marshal(&data)
    if err != nil {
        log.Fatal(err)
    }

    // 3. data を書き込み
    n, err := f.Write(output)
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("%d bytes 書き込んだよ!", n)
}

番外編: 書き込み先がバッファの場合

もちろん、ファイルだけでなくバッファ (buf := new(bytes.Buffer)) にも書き込めます

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "log"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {

    data := []User{
        User{Name: "田中", Age: 25},
        User{Name: "加藤", Age: 30},
    }

    // 1. 書き込み先のバッファを生成
    buf := new(bytes.Buffer)

    // 2. バッファに書き込む、エンコーダを取得
    encoder := json.NewEncoder(buf)

    // 3. data をバッファに書き込む
    err := encoder.Encode(&data)
    if err != nil {
        log.Fatal(err)
    }
}

番外編: Encode vs Marshal どっちを使うべきか?

JSON や XML は共に、Encode と Marshal が使え、同じ結果が得られます。
では違いはなんなのでしょうか?

Stack Overflow( 参考: in Golang, what is the difference between json encoding and marshalling ) によると、違いはこのようです。

  • Marshal => String
  • Encode => Stream

使い分けとして、文字列やバイト文字としてして扱いたい場合は Marshal、それ以外では Encode を使うようです。

また、GitHub の Q&A でも、

  • (メモリ上の) バイト文字列を取り扱う場合は、Marshal
  • Reader/Writer (ファイルなどの stream) を取り扱う場合は、Encode

と回答されてますね。

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