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

Golangでtomlを読み込む

概要

dockerコンテナでgolangを動かしtomlを読み込んで標準フォーマットで出力する。

ファイル構成

今回はdockerを使ってコンテナ上で動かしますので以下のファイル構成で行うこととします。

goTest
  ├docker-compose.yml
  ├Dockerfile
  └src
   ├main.go
   ├config
   │  └testConfig.toml
   └tomlexec
      ├tomlexec.go
      └tomlexec_test.go

登場ファイル

主な登場ファイルはdockerファイルを除けば難しい事はなく、以下の4ファイルになります。
・main.go
・testConfig.toml
・tomlexec.go
・tomlexec_test.go

各ファイルの設定を見ていきましょう。

docker-compose.yml

docker-compose.yml
version: '3.7'
services:
  #golangの設定
  app:
    build: ./
    container_name: goTest
    tty: true
    volumes:
      - ../:/go/src/charts_server/src
    working_dir: /go/src/charts_server/src/goTest/src
    ports:
      - "8080:8080"
    command: go run main.go
    environment:
          - "GOPATH=/go/src/charts_server"
解説

細かい説明は省きますが、以下の通りとなります。
volumes:localのディレクトリとdockerコンテナのディレクトリの同期をここで行っています。
working_dir:ここで基準になるディレクトリを設定しています。
ports:アクセスする際のポートナンバーです。
environment:ここではdockerコンテナ内でのGOPATHを指定しています。今回は「/go/src/charts_server」

Dockerfile

FROM golang:latest
RUN mkdir /go/src/charts_server
WORKDIR /go/src/charts_server/src/goTest/src
ADD . /go/src/charts_server

ここに関してはdockerの設定を行っていますが、シンプルなものとなっていますので説明は不要かと思います。

main.go

main.go
package main

import (
    "goTest/src/tomlexec"
)

func main() {
    tomlexec.Exec()
}
解説

とてもシンプルです。
1階層下のパッケージ、tomlexecをインポートして、tomlexecパッケージのExec関数を呼び出しているだけです。

testConfig.toml

testConfig.toml
[TestToml]
No    = 1
Name  = "testName"

解説

今回はgoの構造体に合わせて上記のような設定をしています。
Noはint、Nameはstringとなっています。
TestTomlに関してはmapで言うkeyのようなものと思えば良いと思います。
TestTomlと言うキーの中に、NoNameと言うmapがあると思えば想像しやすいかもしれません。

tomlExec.go

tomlExec.go
package tomlexec

import (
    "fmt"

    "github.com/BurntSushi/toml"
)

// TConfig ...
type TConfig struct {
    TestToml *TomlConfig
}

// TomlConfig ...
type TomlConfig struct {
    No   int
    Name string
}

// Exec ...
func Exec() {
    var cfg TConfig
    _, err := toml.DecodeFile("./config/testConfig.toml", &cfg)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(cfg.TestToml.Name)
}
解説

TConfigTomlConfigはそれぞれ、構造体の定義を行っています。
toml.DecodeFileで読み込むtomlファイルと読み込ませる構造体を指定しています。
今回は読み込んだ後にNameだけ標準出力するようにしています。

tomlexec_test.go

tomlexec_test.go
package tomlexec

import (
    "os"
    "path/filepath"
    "testing"
)

func TestExec(t *testing.T) {
    apath, _ := filepath.Abs("../")
    os.Chdir(apath)
    Exec()
}
解説

tomlExecパッケージのExec関数を呼んでいるだけの簡単なtestです。
ポイントはfilepath.Abs("../")os.Chdir
これをやらないとエラーとなってしまいます。
理由はmain.goの実行場所とこのテストの実行場所が違うと言うことです。
最初に挙げたファイル構成を見て頂けるとわかりますが、main.goよりも1階層深い場所にこのテストがあります。
このまま実行すると./config/testConfig.tomlがどこにあるのかプログラム上では分からなくなり、エラーとなりますが、filepath.Abs("../")で1階層下の絶対パスを取得し、それを引数にos.Chdirでワーキングディレクトリを指定することで同じように読むことが可能となります。

実行

goTest> docker-compose up
Starting goTest ... done
Attaching to goTest
goTest | testName

testNameが表示されているのでしっかり読めていますね。

テスト

手順は
1.起動しているコンテナを検索
2.コンテナの中に入る
3.テストのあるディレクトリまで移動してテストを実行
となります。

1.起動しているコンテナを検索
goTest> docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
c38c50f20df5        gotest_app          "go run main.go"    24 hours ago        Up 27 minutes       0.0.0.0:8080->8080/tcp   goTest 
2.コンテナの中に入る
goTest> docker exec -ti c38c50f20df5 bash
root@c38c50f20df5:/go/src/charts_server/src/goTest/src# 
3.テストのあるディレクトリまで移動してテストを実行
goTest/src# ls 
charts_server  config  main.exe  main.go  tmp  tomlexec
goTest/src# cd tomlexec/
goTest/src/tomlexec# go test -v
=== RUN   TestExec
testName
--- PASS: TestExec (0.01s)
PASS
ok      goTest/src/tomlexec     0.011s

ここまでで終了です。
テストも通りました。

一応上記のコードはgitで公開していますので、見たい方は参考までにどうぞ。

git

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

docker、Golangでtomlを読み込む

概要

dockerコンテナでgolangを動かしtomlを読み込んで標準フォーマットで出力する。

ファイル構成

今回はdockerを使ってコンテナ上で動かしますので以下のファイル構成で行うこととします。

goTest
  ├docker-compose.yml
  ├Dockerfile
  └src
   ├main.go
   ├config
   │  └testConfig.toml
   └tomlexec
      ├tomlexec.go
      └tomlexec_test.go

登場ファイル

主な登場ファイルはdockerファイルを除けば難しい事はなく、以下の4ファイルになります。
・main.go
・testConfig.toml
・tomlexec.go
・tomlexec_test.go

各ファイルの設定を見ていきましょう。

docker-compose.yml

docker-compose.yml
version: '3.7'
services:
  #golangの設定
  app:
    build: ./
    container_name: goTest
    tty: true
    volumes:
      - ../:/go/src/charts_server/src
    working_dir: /go/src/charts_server/src/goTest/src
    ports:
      - "8080:8080"
    command: go run main.go
    environment:
          - "GOPATH=/go/src/charts_server"
解説

細かい説明は省きますが、以下の通りとなります。
volumes:localのディレクトリとdockerコンテナのディレクトリの同期をここで行っています。
working_dir:ここで基準になるディレクトリを設定しています。
ports:アクセスする際のポートナンバーです。
environment:ここではdockerコンテナ内でのGOPATHを指定しています。今回は「/go/src/charts_server」

Dockerfile

FROM golang:latest
RUN mkdir /go/src/charts_server
WORKDIR /go/src/charts_server/src/goTest/src
ADD . /go/src/charts_server

ここに関してはdockerの設定を行っていますが、シンプルなものとなっていますので説明は不要かと思います。

main.go

main.go
package main

import (
    "goTest/src/tomlexec"
)

func main() {
    tomlexec.Exec()
}
解説

とてもシンプルです。
1階層下のパッケージ、tomlexecをインポートして、tomlexecパッケージのExec関数を呼び出しているだけです。

testConfig.toml

testConfig.toml
[TestToml]
No    = 1
Name  = "testName"

解説

今回はgoの構造体に合わせて上記のような設定をしています。
Noはint、Nameはstringとなっています。
TestTomlに関してはmapで言うkeyのようなものと思えば良いと思います。
TestTomlと言うキーの中に、NoNameと言うmapがあると思えば想像しやすいかもしれません。

tomlExec.go

tomlExec.go
package tomlexec

import (
    "fmt"

    "github.com/BurntSushi/toml"
)

// TConfig ...
type TConfig struct {
    TestToml *TomlConfig
}

// TomlConfig ...
type TomlConfig struct {
    No   int
    Name string
}

// Exec ...
func Exec() {
    var cfg TConfig
    _, err := toml.DecodeFile("./config/testConfig.toml", &cfg)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println(cfg.TestToml.Name)
}
解説

TConfigTomlConfigはそれぞれ、構造体の定義を行っています。
toml.DecodeFileで読み込むtomlファイルと読み込ませる構造体を指定しています。
今回は読み込んだ後にNameだけ標準出力するようにしています。

tomlexec_test.go

tomlexec_test.go
package tomlexec

import (
    "os"
    "path/filepath"
    "testing"
)

func TestExec(t *testing.T) {
    apath, _ := filepath.Abs("../")
    os.Chdir(apath)
    Exec()
}
解説

tomlExecパッケージのExec関数を呼んでいるだけの簡単なtestです。
ポイントはfilepath.Abs("../")os.Chdir
これをやらないとエラーとなってしまいます。
理由はmain.goの実行場所とこのテストの実行場所が違うと言うことです。
最初に挙げたファイル構成を見て頂けるとわかりますが、main.goよりも1階層深い場所にこのテストがあります。
このまま実行すると./config/testConfig.tomlがどこにあるのかプログラム上では分からなくなり、エラーとなりますが、filepath.Abs("../")で1階層下の絶対パスを取得し、それを引数にos.Chdirでワーキングディレクトリを指定することで同じように読むことが可能となります。
※pathが崩れてtestが通らない所を、通すのが目的なのでassertなどは記載していません。

実行

goTest> docker-compose up
Starting goTest ... done
Attaching to goTest
goTest | testName

testNameが表示されているのでしっかり読めていますね。

テスト

手順は
1.起動しているコンテナを検索
2.コンテナの中に入る
3.テストのあるディレクトリまで移動してテストを実行
となります。

1.起動しているコンテナを検索
goTest> docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
c38c50f20df5        gotest_app          "go run main.go"    24 hours ago        Up 27 minutes       0.0.0.0:8080->8080/tcp   goTest 
2.コンテナの中に入る
goTest> docker exec -ti c38c50f20df5 bash
root@c38c50f20df5:/go/src/charts_server/src/goTest/src# 
3.テストのあるディレクトリまで移動してテストを実行
goTest/src# ls 
charts_server  config  main.exe  main.go  tmp  tomlexec
goTest/src# cd tomlexec/
goTest/src/tomlexec# go test -v
=== RUN   TestExec
testName
--- PASS: TestExec (0.01s)
PASS
ok      goTest/src/tomlexec     0.011s

ここまでで終了です。
テストも通りました。

一応上記のコードはgitで公開していますので、見たい方は参考までにどうぞ。

git

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

GoのEchoでオリジン間リソース共有の問題を解決

やりたいこと

フロント側の http://localhost:8080/ からバックエンド側の http://localhost:8080/ を呼び出したい
しかし以下のエラーがフロント側で出ていたので、これを解決したい

Access to XMLHttpRequest at 'http://localhost:8000' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

オリジン管理ソース共有に関してはこの記事では解説しませんが、 こちらの記事 がわかりやすかったです

環境情報

  • vue
    • 2.6
  • go
    • 1.15
    • echoをサーバーとして使用

解決方法

以下の設定を追加

e := echo.New()

// 以下を追加
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"http://localhost:8080"},
    AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPost, http.MethodDelete},
}))

無事フロント側からアクセスできる様になりました!

参考

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

[競プロ]Goの標準入力をまとめる

はじめに

ある日Paizaにハマったので、Goの標準入力についてまとめておく。

1つのデータの入力

package main

import "fmt"

func main() {
    var s string
    fmt.Scan(&s)
    fmt.Println(s)
}

1行のデータの入力

package main

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

func main()  {
    reader := bufio.NewReader(os.Stdin)
    s, _ := reader.ReadString('\n')
    fmt.Println(s)
}

n行のデータの入力(1行目にデータ数)

package main

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

func main()  {
    reader := bufio.NewReader(os.Stdin)
    s, _ := reader.ReadString('\n')
    s = strings.TrimSpace(s)
    n, _ := strconv.Atoi(s)
    for i:=0; i<n; i++ {
        s, _ = reader.ReadString('\n')
        s = strings.TrimSpace(s)
        fmt.Println(s)
    }
}

複数のデータ(今回は3つ)の入力(スペース区切り)

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    s, _ := reader.ReadString('\n')
    s = strings.TrimSpace(s)
    t := strings.Split(s, " ")
    fmt.Println(t[0])
    fmt.Println(t[1])
    fmt.Println(t[2])
}

n個のデータの入力(1行目にデータ数, 2行目にn個のデータ(スペース区切り))

package main

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

func main()  {
    reader := bufio.NewReader(os.Stdin)
    s, _ := reader.ReadString('\n')
    s = strings.TrimSpace(s)
    n, _ := strconv.Atoi(s)

    s, _ = reader.ReadString('\n')
    s = strings.TrimSpace(s)
    t := strings.Split(s, " ")

    for i:=0; i<n; i++ {
        fmt.Println(t[i])
    }
}

行列の作成

package main

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

func main() {
    var matrix[][] string
    reader := bufio.NewReader(os.Stdin)
    s, _ := reader.ReadString('\n')
    s = strings.TrimSpace(s)
    t := strings.Split(s, " ")
    n, _ := strconv.Atoi(t[0])

    for i:=0; i<n; i++ {
        s, _ = reader.ReadString('\n')
        s = strings.TrimSpace(s)
        x := strings.Split(s, "")
        matrix = append(matrix, x)
    }
    fmt.Println(matrix)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

golangで先週の〇〇曜日をtime.Timeで取得するコード

type Weekday struct {
    Now time.Time
    Loc *time.Location
}

func (w Weekday) LastWeekOf(weekday time.Weekday) time.Time {
    levellingOffset := int(w.Now.Weekday())
    weekDiff := 7-(int(weekday))

    return time.
        Date(w.Now.Year(), w.Now.Month(), w.Now.Day(), 0, 0, 0, 0, w.Loc).
        AddDate(0, 0, -levellingOffset).
        AddDate(0, 0, -weekDiff)
}

こう使う

package main

import (
    "fmt"
    "time"
)

func main() {   
    now := time.Date(2020, 11, 27, 0, 0, 0, 0, time.UTC)    
    fmt.Println(now.Year(), now.Month(), now.Day())

    w := Weekday{
        Now: now, 
        Loc: time.UTC,
    }   
    r := w.LastWeekOf(time.Monday) // 先週の月曜日を取得する
    fmt.Println(r.Year(), r.Month(), r.Day())
}

結果

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

細かいことは置いといて、とりあえずProtocolBuffersを触ってみたい時の覚書

  • タイトルの通り、自分が試した方法をまとめておく
  • ProtocolBuffersとはなんぞや?に関しては公式とか色んな方がネットに情報上げてくれてるので割愛

環境構築

  • ubuntu18.04
  • Go1.14
  • ディレクトリ構成

/home/user/
└── go/
   ├── bin/
   ├── pkg/
   └── src/
      ├── go.mod
      ├── go.sum
      ├── example.go
      ├── example.proto
      └── pb/
         └── (protoファイルのコンパイル後のファイルをここに置く)


まずprotocのインストール(ダウンロード先

/home/user/
# protocのzipファイルをwget
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip
# zipファイル解凍
$ unzip protoc-3.14.0-linux-x86_64.zip
# protocのバイナリをbin配下に移動
$ sudo mv bin/protoc /usr/local/bin/
# includeも移動(しておかないといけないらしい、参照するから)
$ sudo mv include/ /usr/local/

これでインストールされたはず

/home/user
# 確認
$ protoc --version
libprotoc 3.14.0

次にprotoc-gen-goのインストール

# インストール
$ go get -u google.golang.org/protobuf/cmd/protoc-gen-go

これで完了

# 確認
$ protoc-gen-go --version
protoc-gen-go v1.25.0

Command 'protoc-gen-go' not found... なんて表示されたら(僕の環境の場合)

/home/user/
# 下記をvim等で.profileに追加、PATHを通す
export PATH="$HOME/go/bin:$PATH"
# 反映
$ source .profile

protoファイル作成

  • message → フィールドというか値をまとめたもの
  • enum → define定義のようなもの
  • repeated → 配列
  • = 番号 → タグのようなもの。連番で付ける(messageは1から。enumは0から)

※ option go_packageはなくても問題なさそうですが、WARNINGが出るので、こちら参考にさせていただきました
  → Protocol Buffersのgo_packageの;の仕様

go/src/example.proto
syntax = "proto3";
package example;

option go_package = ".;pb";

message User {
    int32 id = 1;
    string name = 2;

    enum AddressType {
        HOKKAIDO = 0;
        TOHOKU = 1;
        KANTO = 2;
        CHUBU = 3;
        KINKI = 4;
        CHUGOKU = 5;
        SHIKOKU = 6;
        KYUSYU = 7;
        OKINAWA = 8;
    }

    message Address {
        string area = 1;
        AddressType type = 2;
    }

    repeated Address addresses = 3;
}

protoファイルのコンパイル

  • 「-I」→ アプリケーションのソースコードが存在する場所
  • 「--go_out」→ コンパイルで自動生成されるpb.goファイルの配置先
go/src/
# protocコマンドでコンパイル
$ protoc -I=. --go_out=./pb example.proto

シリアライズとデシリアライズ

Goでシリアライズとデシリアライズを行う
※ コードはこちら参考にさせていただきました
  → Protocol Buffers 導入メモ Mac/Win

go/src/example.go
package main

import (
    "fmt"
    pb "src/pb"

    "github.com/golang/protobuf/proto"
)

func main() {
    // シリアライズ
    obj := &pb.User{
        Id:   123,
        Name: "yuuzin217",
        Addresses: []*pb.User_Address{
            {
                Area: "Tokyo",
                Type: pb.User_KANTO,
            },
            {
                Area: "Kyoto",
                Type: pb.User_KINKI,
            },
        },
    }
    buff, err := proto.Marshal(obj)
    if err != nil {
        fmt.Println("シリアライズ失敗", err)
        return
    }
    fmt.Println("シリアライズ結果:", buff)

    // デシリアライズ
    parseResult := &pb.User{}
    err = proto.Unmarshal(buff, parseResult)
    if err != nil {
        fmt.Println("デシリアライズ失敗: ", err)
        return
    }
    fmt.Println("デシリアライズ結果 id: ", parseResult.Id)
    fmt.Println("デシリアライズ結果 name: ", parseResult.Name)
    fmt.Println("デシリアライズ結果 addresses: ", parseResult.Addresses[1].Area)
}

保存して実行

go/src/
# 実行
$ go run example.go
シリアライズ結果: [8 123 18 9 121 117 117 122 105 110 50 49 55 26 9 10 5 84 111 107 121 111 16 2 26 9 10 5 75 121 111 116 111 16 4]
デシリアライズ結果 id:  123
デシリアライズ結果 name:  yuuzin217
デシリアライズ結果 addresses:  Kyoto

参考

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

ProtocolBuffersの覚書

転職してProtocolBuffersの勉強をする機会があったので、最低限のところだけ忘れないようにまとめる。

環境構築

  • ubuntu18.04
  • Go1.14
  • ディレクトリ構成

/home/user/
└── go/
   ├── bin/
   ├── pkg/
   └── src/
      ├── go.mod
      ├── go.sum
      ├── example.go
      ├── example.proto
      └── pb/
         └── (protoファイルのコンパイル後のファイルをここに置く)


まずprotocのインストール(ダウンロード先

/home/user/
# protocのzipファイルをwget
$ wget https://github.com/protocolbuffers/protobuf/releases/download/v3.14.0/protoc-3.14.0-linux-x86_64.zip
# zipファイル解凍
$ unzip protoc-3.14.0-linux-x86_64.zip
# protocのバイナリをbin配下に移動
$ sudo mv bin/protoc /usr/local/bin/
# includeも移動(しておかないといけないらしい、参照するから)
$ sudo mv include/ /usr/local/

これでインストールされたはず

/home/user
# 確認
$ protoc --version
libprotoc 3.14.0

次にprotoc-gen-goのインストール

# インストール
$ go get -u google.golang.org/protobuf/cmd/protoc-gen-go

これで完了

# 確認
$ protoc-gen-go --version
protoc-gen-go v1.25.0

Command 'protoc-gen-go' not found... なんて表示されたら(僕の環境の場合)

/home/user/
# 下記をvim等で.profileに追加、PATHを通す
export PATH="$HOME/go/bin:$PATH"
# 反映
$ source .profile

protoファイル作成

  • message → フィールドというか値をまとめたもの
  • enum → define定義のようなもの
  • repeated → 配列

※ option go_packageはなくても問題なさそうですが、WARNINGが出るので、こちら参考にさせていただきました
  → Protocol Buffersのgo_packageの;の仕様

go/src/example.proto
syntax = "proto3";
package example;

option go_package = ".;pb";

message User {
    int32 id = 1;
    string name = 2;

    enum AddressType {
        HOKKAIDO = 0;
        TOHOKU = 1;
        KANTO = 2;
        CHUBU = 3;
        KINKI = 4;
        CHUGOKU = 5;
        SHIKOKU = 6;
        KYUSYU = 7;
        OKINAWA = 8;
    }

    message Address {
        string area = 1;
        AddressType type = 2;
    }

    repeated Address addresses = 3;
}

protoファイルのコンパイル

  • 「-I」→ アプリケーションのソースコードが存在する場所
  • 「--go_out」→ コンパイルで自動生成されるpb.goファイルの配置先
go/src/
# protocコマンドでコンパイル
$ protoc -I=. --go_out=./pb example.proto

シリアライズとデシリアライズ

Goでシリアライズとデシリアライズを行う
※ コードはこちら参考にさせていただきました
  → Protocol Buffers 導入メモ Mac/Win

go/src/example.go
package main

import (
    "fmt"
    pb "src/pb"

    "github.com/golang/protobuf/proto"
)

func main() {
    // シリアライズ
    obj := &pb.User{
        Id:   123,
        Name: "yuuzin217",
        Addresses: []*pb.User_Address{
            {
                Area: "Tokyo",
                Type: pb.User_KANTO,
            },
            {
                Area: "Kyoto",
                Type: pb.User_KINKI,
            },
        },
    }
    buff, err := proto.Marshal(obj)
    if err != nil {
        fmt.Println("シリアライズ失敗", err)
        return
    }
    fmt.Println("シリアライズ結果:", buff)

    // デシリアライズ
    parseResult := &pb.User{}
    err = proto.Unmarshal(buff, parseResult)
    if err != nil {
        fmt.Println("デシリアライズ失敗: ", err)
        return
    }
    fmt.Println("デシリアライズ結果 id: ", parseResult.Id)
    fmt.Println("デシリアライズ結果 name: ", parseResult.Name)
    fmt.Println("デシリアライズ結果 addresses: ", parseResult.Addresses[1].Area)
}

保存して実行

go/src/
# 実行
$ go run example.go
シリアライズ結果: [8 123 18 9 121 117 117 122 105 110 50 49 55 26 9 10 5 84 111 107 121 111 16 2 26 9 10 5 75 121 111 116 111 16 4]
デシリアライズ結果 id:  123
デシリアライズ結果 name:  yuuzin217
デシリアライズ結果 addresses:  Kyoto

参考

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

とにかくGolangでAvroを使わせろ

概要

Apache Avroにはデータ・スキーマを用いて永続化や通信を行うデータを圧縮する為の仕様「A compact, fast, binary data format.(コンパクトで高速なバイナリ形式データ)」があります。
本記事ではAvroの詳細まで言及しませんが、この仕様をGolangで実装して圧縮されたデータを実感してみたいと思います。

データ圧縮の準備

Avroスキーマ

Avroバイナリデータに圧縮するには、予めデータ構造のスキーマ定義を作成する必要があります。
まずは2つの項目を持つ簡単なスキーマを定義します。
(後ほどGolangのソースコード内でstringデータとして使用します)

{
  "namespace": "TestSchema",
  "type": "record",
  "name": "TestData",
  "fields" : [
    { "name" : "id", "type" : "int" }, { "name" : "value", "type" : "string" }
  ]
}

圧縮前のデータ(JSON)

スキーマにはint型の"id"と言う名前の項目とstring型の"value"と言う2つの項目が定義されています。
この定義に沿った以下のJSONデータを作成します。

{"id":10001,"value":"test_value"}

データ圧縮ロジック

Goライブラリ

以下のコマンドを実行してライブラリを取得します。

go get "github.com/linkedin/goavro"

シリアライズ処理手順

以下の手順で実行します。

// 1.JSON文字列をGolangのプリミティブ型のデータへ変換
// 1-1.スキーマを設定したCodecを生成
codec, _ := goavro.NewCodec(`
  {
    "namespace": "TestSchema",
    "type": "record",
    "name": "TestData",
    "fields" : [
      { "name" : "id", "type" : "int" }, { "name" : "value", "type" : "string" }
    ]
  }`)

// 1-2.JSON文字列をGoデータへ変換
textual := []byte(`{"id":10001,"value":"test_value"}`)
native, _, err := codec.NativeFromTextual(textual) // nativeはmap[string]interface{}型
if err != nil {
  panic(err)
}
log.Printf("textual length:[%d]\n", len(textual))

// 2.GoデータをAvroバイナリへシリアライズ
binary, err := codec.BinaryFromNative(nil, native) // binaryは[]byte型
if err != nil {
  panic(err)
}
log.Printf("binary length:[%d]\n", len(binary))

処理結果

元のJSON文字列33バイトから、バイナリデータ変換後は14バイトまで圧縮されています。

textual length:[33]
binary length:[14]

デシリアライズ処理手順

以下の手順で実行します。
Codecはシリアライズと同じものを使用できます。

// 3.AvroバイナリデータをGoデータへデシリアライズ
native, _, err := codec.NativeFromBinary(binary)
if err != nil {
  panic(err)
}
log.Printf("Deserialized:%v\n", native)

// 4.JSON文字列に戻すことも可能
textual, err := codec.TextualFromNative(nil, native)
if err != nil {
  panic(err)
}
log.Printf("JSON string :%v\n", string(textual))

処理結果

バイナリから復元されたデータはmap[string]interface{}型に変換されます。

Deserialized:map[id:10001 value:test_value]
JSON string :{"value":"test_value","id":10001}

どの辺がポイント?

・データ構造の情報を持たない
 シリアライズにもデシリアライズにもスキーマ定義が必要で、その管理も必要になりますが、スキーマに書かれている情報を持つ必要が無いのでデータは小さくなります。
 永続化にも通信にも有用です。


取り急ぎGolangでAvroのデータを扱いたい時は上記ソースコードをコピペして、スキーマ定義とJSON文字列を適切な値へ置換してください。(エラー処理の実装も忘れずに)

Appendix

*Apache Avro公式ページ
*linkedin/goavro(Github)

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

WindowsでのGo開発環境構築

社内で、A Tour of Goを使ってGoの勉強会をしているので、学んだことをメモしておく。
まとめ記事はこちら

初回はWindowsでGoの開発環境を整える。
本当はA Tour of Go上で開発をしたかったが、Web上では補完効かなかったため、急遽vscodeでの開発に切り替えた。

前提

  • vscodeインストール済み

手順

  1. こちらから、Goをインストールする
  2. vscodeを起動
    • Goをインストールする前にvscodeを起動していた場合、vscodeのterminalに環境変数が反映されないので、vscodeを再起動する必要がある
  3. vscodeにGoの拡張をインストール
  4. 適当にGoファイルを作成し、A Tour of Go のコードを書き写す
  5. importしているfmtをクリックして定義元に飛ぼうとするが、必要なパッケージがインストールされていないので飛べない
    • そうするとvscodeが察して、右下に Godefをインストールしますか? と表示される
    • 同じ要領でguru、Delve等もインストールできた
  6. この記事を参考し、デバッグもできた

所感

今日はここまで。

他の環境構築記事では、環境変数(GOPATH)の指定や各種ツールのインストールも自分で行っていた。
しかし、Goをインストールしたり、vscodeの案内に従っているだけでそれらの設定も自動で行ってくれるので、自分でやることは驚くほど少ない。感動。

今日は環境構築で終わったので、明日から本格的にA Tour of Goを進めていく。

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

社内Go勉強会まとめ

はじめに

社内の有志で、Go言語をキャッチアップするため、1日30分位使ってA Tour of Goを進めてみた。
その時の内容を残しておく。

A Tour of Go

#1

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

LeetCodeに毎日挑戦してみた26. Remove Duplicates from Sorted Array (Python、Go)

はじめに

無料英単語サイトE-tanを運営中の@ishishowです。

プログラマとしての能力を上げるために毎日leetcodeに取り組み、自分なりの解き方を挙げていきたいと思います。

Leetcodeとは

leetcode.com
ソフトウェア開発職のコーディング面接の練習といえばこれらしいです。
合計1500問以上のコーデイング問題が投稿されていて、実際の面接でも同じ問題が出されることは多いらしいとのことです。

golang入門+アルゴリズム脳の強化のためにgoとPythonで解いていこうと思います。(Pythonは弱弱だが経験あり)

8問目(問題26)

26. Remove Duplicates from Sorted Array

  • 問題内容(日本語訳)

並べ替えられた配列numsを指定して、各要素が1*回*だけ表示され、新しい長さを返すように、重複をインプレースで削除します。

別の配列に余分なスペースを割り当てないでください。O(1)の余分なメモリを使用して入力配列をインプレース変更する必要があります。

明確化:

戻り値が整数であるのに、答えが配列である理由がわかりませんか?

入力配列は参照によって渡されることに注意してください。これは、入力配列への変更が呼び出し元にも認識されることを意味します。

内部的には、これについて考えることができます。

// numsは参照によって渡されます。(つまり、コピーを作成せずに)
int len = removeDuplicates(nums);

//関数内のnumsへの変更は、呼び出し元に認識されます。
//関数から返された長さを使用して、最初のlen要素を出力します。
for(int i = 0; i <len; i ++){
    print(nums [i]);
}

Example 1:

  Input: nums = [1,1,2]
  Output: 2, nums = [1,2]
  Explanation: Your function should return length = 2, with the first two elements of nums being 1 and 2 respectively. It doesn't matter what you leave beyond the returned length.

Example 2:

  Input: nums = [0,0,1,1,1,2,2,3,3,4]
  Output: 5, nums = [0,1,2,3,4]
  Explanation: Your function should return length = 5, with the first five elements of nums being modified to 0, 1, 2, 3, and 4 respectively. It doesn't matter what values are set beyond the returned length.

ヒント1

この問題で焦点を当てる重要なポイントは、ソートされる入力配列です。重複する要素に関する限り、特定の配列が並べ替えられたときの配列内でのそれらの位置は何ですか?答えは上の画像を見てください。要素の1つの位置がわかっている場合、重複するすべての要素の位置もわかっていますか?
img

ヒント2

配列をインプレースで変更する必要があり、最終的な配列のサイズは入力配列のサイズよりも小さくなる可能性があります。したがって、ここでは2ポインターアプローチを使用する必要があります。1つは、元の配列の現在の要素を追跡し、もう1つは一意の要素のみを追跡します。

ヒント3

基本的に、要素が検出されたら、その重複をバイパスして、次の一意の要素に進む必要があります。

考え方

  1. 新規インデックスを作成 (nw_index)

  2. len(nums)回ループを回して重複していたらそのまま進める

  3. 重複していなかったら新規インデックスの番号にその値を代入する

  4. 戻り値は長さを返すのでプラス1します

  • 解答コード
  class Solution(object):
      def removeDuplicates(self, nums):
          nw_index = 0
          for i in range(len(nums)):
              if nums[nw_index] != nums[i]:
                  nw_index +=1
                  nums[nw_index] = nums[i]
          return (nw_index + 1)
  • Goでも書いてみます!
func removeDuplicates(nums []int) int {
    nw_index := 0
    for _, num := range nums {
        if nums[nw_index] != num {
            nw_index++
            nums[nw_index] = num
        }
    }

    return (nw_index + 1)
}

別解

def removeDuplicates(self, nums):
    nums[:] = sorted(set(nums))
    return len(nums)
手順説明

set() でset型(集合型)に変換します

set型とは?

set型は重複しない要素(同じ値ではない要素、ユニークな要素)のコレクションで、和集合、積集合、差集合などの集合演算を行うことができる。

setは集合なので順番がぐちゃぐちゃになりますのでsorted関数で昇順に戻します

nums[:]に参照を代入します。ここでnums=にしないのは新たにオブジェクトを生成してメモリを消費しないためです

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