いっきのblog

技術とか色々

GoroutineとChannelについて学ぶ

前回Goのインタフェースについて書いた。

kzkohashi.hatenablog.com (アイキャッチが更新されてない・・)

今回はGoの真髄とも言える、ゴルーチンとチャネルによる並行処理(Concurrent)について学んでいく。

並行(Concurrent)処理と並列(Parallel)処理

間違えやすいのだが、並行処理は並列処理と概念的に異なる。

こちらの記事がわかりやすかったので、引用させていただく。

Concurrent(並行)は「複数の動作が、論理的に、順不同もしくは同時に起こりうる」こと Parallel(並列)は、「複数の動作が、物理的に、同時に起こること」
引用: freak-da.hatenablog.com

並行は1人の人が複数の仕事をし、並列は複数の人が複数の仕事をしている状態だ。
とはいっても、並行処理は擬似並列ともいえるので、日本語的には「並列処理」を使っても問題ないと思う。

この辺りも面白かった。

興味がある人は以下を読むと良さそう。
自分は近々届くので、また別途まとめたい。

www.oreilly.co.jp

Goroutineで並行処理を行う。

Goroutineの実装は簡単で、関数の前にgoをつけるだけだ。
これだけで並行処理がとりあえずは実装できる。

func say(s string) {
    for i := 0; i < 5; i++ {
        time.Sleep(100 * time.Millisecond)
        fmt.Println(s)
    }
}


func main() {
    go say("world")
    say("hello")
}

引用: https://go-tour-jp.appspot.com/concurrency/1

並行処理のプログラミングをする際は、mainでの処理とは別に処理が走ってしまうので、Channelをうまく使って制御したり、Sleepで待つという方法もある。
Sleepは処理が終わる終わらないに限らず、その時間しかまたないのでよくない。

Channelを使う

A Tour Goをやってもあまり理解しなかったが、以下の記事がよかったのでこちらを見た方がいい。
channelは値をやり取りをするためのキューみたいなもんだ。しかも、goroutine(スレッド)間で値の受け渡しを安全にできるので、とても簡単に扱える。

qiita.com

func main() {
  // 容量2のchannel型で作成
  c := make(chan string, 2)
 // データを入れる
  c <- "hello"
  // データを入れる
  c <- "hello2"
  // c <- "hello3" 容量2のため3個目をいれたらエラー
  // 最初に入れたデータをchannelから取り出す
  fmt.Println(<-c)
  fmt.Println(<-c)
  // fmt.Println(<-c) 3つ目はない

  c = make(chan string, 1)
  c <- "world"
  fmt.Println(<-c)
}

Exercise: Web Crawler を解く

A Tour Goの最後に、goroutineの問題があったので解いて見た。
go-tour-jp.appspot.com

package main

import (
    "fmt"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string, resultUrl ResultUrl) (body string, urls []string, err error)
}

type ResultUrl struct {
    v   map[string]int
}

// Value returns the current value of the counter for the given key.
func checkUrl(c *ResultUrl, key string) bool {
    return c.v[key] == 1
}


// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, c ResultUrl, quit chan bool) {
    // TODO: Fetch URLs in parallel.
    // TODO: Don't fetch the same URL twice.
    // This implementation doesn't do either:
    if depth <= 0 {
        quit <- true
        return
    }

    if checkUrl(&c, url) {
        quit <- true
        return
    }

    body, urls, err := fetcher.Fetch(url, c)
    if err != nil {
        fmt.Println(err)
        quit <- true
        return
    }
    fmt.Printf("found: %s %q\n", url, body)

    quit2 := make(chan bool)
    for _, u := range urls {
        go Crawl(u, depth-1, fetcher, c, quit2)
        <-quit2
    }

    quit <- true
    return
}

func main() {
    c := ResultUrl{v: make(map[string]int)}
    quit := make(chan bool)
    go Crawl("https://golang.org/", 4, fetcher, c, quit)
    <-quit
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {
    body string
    urls []string
}

func (f fakeFetcher) Fetch(url string, c ResultUrl) (string, []string, error) {
    c.v[url] = 1

    if res, ok := f[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
    "https://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "https://golang.org/pkg/",
            "https://golang.org/cmd/",
        },
    },
    "https://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "https://golang.org/",
            "https://golang.org/cmd/",
            "https://golang.org/pkg/fmt/",
            "https://golang.org/pkg/os/",
        },
    },
    "https://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
    "https://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "https://golang.org/",
            "https://golang.org/pkg/",
        },
    },
}

すでにクロールしたURLを判定する処理に、sync.Mutexを最初入れてたが、入れなくてもできたのでちょっと怪しい。
キモとしては、channel型のquitに値を入れ、<-quitgoroutineの処理を待つことができる。

終わりに

Goの勉強を駆け足でやってみた。
最初は意味がわからなかったが、毎日少しずつ見ていくと慣れていくもんだな。
次からはGoAPIサーバー作ることをしようかな。

Goのインタフェースについて学ぶ

以前初めてGoに触った感想をまとめたが、インタフェースについては書いてなかったのでまとめる。 kzkohashi.hatenablog.com

インタフェースの定義

A Tour of Goの内容をベースに話していく。
go-tour-jp.appspot.com

インターフェースの定義は以下のように、type xxx interfaceで作成し、その中に実装するメソッドを定義する。
ここでは、AbserインターフェースにAbs関数を定義している。

type Abser interface {
    Abs() float64
}

ややこしいんだけど、interface{}型とは全く違う概念みたいなので、注意しとく。
interface{}型はintboolなど全ての値を取りうる型とのこと。

var a interface{}
a = 1 // int
a = 1.0 // float
a = "あ" // string

こちらが参考になった。ただ、ゆるく使うにしてもどう使うんだろう。
初心者に送りたいinterfaceの使い方[Golang] – Since I want to start “blog” that looks like men do, I do start. – Medium

インタフェースの実装

次にインタフェースを実装する。
float型のMyFloatを定義し、先ほどインターフェスで定義したAbsメソッドを実装する。

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

ちゃんと理解していなかったんだが、typeは構造体で使うというより、指定した型を別名でつけれるようものとのこと。
上記のMyFloatリテラルfloatの別名として、新しい型として定義されたものだ。
言われてみると当たり前な気はするけど、忘れないようにと。以下の記事がとても参考になった。

qiita.com

また、以下のようにstructにもAbsメソッドを実装している。

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

準備は整ったので、実際に使用してみる。
すこし変更させてもらって、実装したどちらのメソッドも実行している。

func main() {
    var a,b Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat implements Abser
        // a = &v
    b = &v // a *Vertex implements Abser

    fmt.Println(a.Abs())
    fmt.Println(b.Abs())
}

終わりに

勉強っぽい感じの作りなんで、実際のインタフェースの使い所など学んで行きたい。
以下の記事ではGoでのインタフェースを書く注意点が書いてあるので参考になった。

qiita.com

次はGoルーチンとチャネルを書こうかな。

LaravelerがGoを初めてさわって見る

ここ最近LaravelPythonしか書いてないので、技術のアップデートをしよう思ったのがきっかけ。
なぜGoなのかと言われると、以下の理由で決めて見た。

  • Laravel(PHP)が動的型付け言語なので、静的型付け言語を学び直したい
  • 流行っている風のものをやりたいため(流行る理由があると思うので)
  • Laravel(PHP)Webサービス用、Pythonは分析用、Goはちょっとしたツール用という立ち位置にしやすいので、継続しやすいと思っているため
  • 毎日お世話になっているpecoみたいなのを作って見たい

学び方の順序などは、こちらの記事を参考にさせてもらった。 qiita.com

Goの背景や網羅的な使い方などを学ぶ

こちらの記事が全体感を掴むにはちょうどいいと思う。 gihyo.jp

細かいところや、理解が深まらない箇所が必ずでてくるのでその場合はこちらから細かいやり方を学ぶ。 go-tour-jp.appspot.com

大体の理解なら技評の記事 <-> A Tour Goを行き来すればできるけど、部分的にググったりして学んでいく。

忘れている箇所やつまった点をまとめる。

静的型付けと動的型付けの違い

Wikipediaがわかりやすい。 動的型付け - Wikipedia

とても極端に分かれているが、PHP8ではJITコンパイラの採用が可決されたみたいで、IDEとの組み合わせで静的型付けみたいなこともできるんじゃないかなと考えてる。
この記事が面白い。どっちもいいとこ取りはし始めてる。

jp.quora.com

関数とメソッドの違い

自分の頭の中で関数 = メソッドと勝手に認識してるせいで、途中こいつらは何を言っているんだ・・と思ってしまった。
関数

func sum(i, j int) int {
    return i + j
}

func main() {
    fmt.Println(sum(1,2)) // 3
}

関数の方はPHPとほぼ同じなのでわかりやすい。
メソッドの方は構造体(ここだとData)と言われる入れ物に、sumという振る舞いが追加されてるかのように扱える。
GoにはClassがないため、ここらへんで色々工夫するのかな。

type Data struct {
    i int
    j int
}

func (d *Data) sum() int {
    return d.i + d.j
}

func main() {
    data := Data {1, 2}
    fmt.Println(data.sum()) // 3
}

エラー処理

Goでは、errorインターフェースがあって、それを使ってエラーを通知するが、それとは別に例外処理が存在する。
どこまで深く勉強したいところだけど、以下の方の記事がすごく腑に落ちそう。

blog.amedama.jp

終わりに

インターフェースや、醍醐味の一つである並列処理などを書きたかったが、久々のブログで体力がないので次回。

Laravel ShiftでLaravelを簡単アップグレード!

Laravel #2 Advent Calendar 2018の11日目です!
今回は、Laravelのアップデートのやり方について書きたいと思います。

バージョンをあげる時の課題

僕らのチームは元々Laravelのバージョンを5.5を使っていて、そろそろあげたいよねって話をしてました。
ただ、「調査や変更による工数がかかる」という(多分心理的な)課題があり、そんな時にメンバーの一人が「Laravel Shift」使えば楽そうですよという話から今回に至りました。

Laravel Shiftとは

簡単にいうと、課金すればLaravelのアップグレードを自動でやってくれるというものです。

laravelshift.com

なかなか便利そうな感じが漂ってますが、いっきにバージョンアップすることはできないので一つずつバージョンをあげていきます。
今回は5.5から5.7にあげたいので、5.5から5.6($9)、5.6から5.7(7$)の計16$でバージョンアップをしたいと思います。

f:id:kzkohashi:20181210202747p:plain

5.5から5.6へのアップグレード

アップグレードは簡単で、GitHubと連携し、クレジットカードを入力すれば始まります。

f:id:kzkohashi:20181210203204p:plain

数分待つと、指定したブランチへプルリクが自動で作られます。
(自分が英語で話した気分になるので、すごくできた気分になれます)

f:id:kzkohashi:20181210203417p:plain

主なプルリクの内容は以下です。

  • PSR-2によるコードスタイルの変更のコミット(準拠してなかったのでいい機会なので変更)
  • アップグレードに伴う各種ファイルの修正コミット
  • 自動で直せない部分を、コメントで教えてくれる

自動で直せない部分もあるのかよっと思うかもしれませんが、コメントで教えてくれるのでそれの通り直すだけなので楽チンです。
修正後は、「ユニットテスト」と「E2Eテスト」をしてバグがないことを確認したらマージします。

5.6から5.7へのアップグレード

こちらも同じように行います。

f:id:kzkohashi:20181210204250p:plain

ユニットテストだと全てのテストを網羅できてないので、E2Eテストはかなり念入りに行い、無事マージ/リリースを行います。

アップグレードによるバグ

大きいバグは1ヶ月たった今もまだありません。
ただ、5.7にあるbootstrap/cacheservices.phppackages.phpのオーナー権限がおかしい問題のせいでデプロイのたびにすごく困ってます・・。
修正済みなので、composer updateすればもう大丈夫です。

github.com

と、おもったら今度はTestResponseJSON周りがおかしくてテストがこける。。。 マージはされたので早くreleaseしてほしいと願っております(12/11現在)

github.com

終わりに

最後はあまり関係ないバグの話になってしまいましたが、かかった工数は全部合わせても数時間です。 たった16$でエンジニアの工数をあまり使わずでき、新規開発にすぐ取り組めて、ビジネスサイドにもほぼ迷惑かからない最高のツールでした。ありがとうございます。

Genderize.ioを使って性別判定してみる

以前、顔写真を使って性別判定についての検証をした。
顔写真による性別判定はそこそこ判定が高くすごく満足しているのだが、顔がない場合の判定はどうすればいいんだろうと思い、ちょい調べてみた。

kzkohashi.hatenablog.com

名前による判定

Genderize.ioと呼ばれる、名前による男女の判定はすでに存在していた。
自分たちでもできそうな気もしなくもないけど。。。とりあえず使ってみる。

https://store.genderize.io/

デモでapiが使えるため、実際に自分の名前出たていてみると

curl https://api.genderize.io/?name=kazuki
{"name":"kazuki","gender":"male","probability":1,"count":16}

jsonでデータがかえってくるため、各項目の意味を調べると以下になる。

name: 入力した名前
gender: 性別
probability: 確からしさ(精度)
count: ヒットしたデータ数

公式には、probabilitycountを使って自分たちで閾値みたいなのを決めてねと書いてある。
今回だとprobability1なのでほぼあってそうに見えるけど、countが少ないため少し怪しいのかなーと思う。kazukiでも女性の名前はいるので、日本語は少し弱いのかな?という印象を受ける。

終わりに

いくつか名前の検証をした感じだと良さそう。本番である程度試したらまた検証記事書こうと思う。

DataFrameで特定カラムでユニークに集計する方法

DataFrameで特定カラムでユニークに集計する方法についてのメモ。

利用するデータ

今回利用するデータは、一つのアカウントに対して、複数の本のタイトルが紐づいてるデータとする。

df[['account_id', 'title']]

f:id:kzkohashi:20180828224809p:plain

本のタイトルがいくつ紐づいてるか集計すると以下のようになる。

df.groupby(['title']).agg({'title': 'count'}).sort_values(by='title', ascending=False)

f:id:kzkohashi:20180828224220p:plain

このままだとあるアカウントが同じ本のタイトルに何回も紐づいてしまうため、アカウントごとに同じタイトルの本が複数紐づいてる場合は一つとカウントしたい。

agg関数をうまく使ってユニークにする

若干怪しいところあるけど、とりあえずはこれでできた。

unique_df = df.groupby(['account_id','title']).title.agg(lambda x: x.unique()).to_frame()
unique_df.reset_index(drop=True)
unique_df.groupby('title').agg({'title': 'count'}).sort_values(by='title', ascending=False)

f:id:kzkohashi:20180828224714p:plain

agg関数で集計する際に、値をユニークにしてるがミソ。

終わりに

SQLならこうできるのに!というのがまだあるため、もっとDataFrameに慣れないとなー。

Python(pandas)でざっくりデータを確認する方法

原さんのブログ読んでいたら、データの傾向を見る方法について簡単に書かれていたので真似したみた。

toohsk.hateblo.jp

結論から言うと、確かに傾向を見るのはすごく楽だし、癖にしたい。

要約統計量の確認

実はブログを見る以前に、「ヒストグラム」と「要約統計量」については学んでいた。

kzkohashi.hatenablog.com

ただ、自分が調べてみたいものではなかったせいのもあり、今思うと全然理解できてなかったんだなと悔しみ。。

気を取り直して、今回は色々な著名人のツイートデータを使用する。
pandasにはdescribeと呼ばれる要約統計量をサクッとだせる便利すぎる関数があるのでそれ使う。

import pandas as pd
import csv

chomei_df = pd.read_csv('../data/tyomeijinn.csv')

chomei_df.describe()

f:id:kzkohashi:20180827230050p:plain

favorite_countを見ると、平均が374と多そうに見えるが、分散は2915とかなり散らばっている。中央値(50パーセンタイル順位)は18、75パーセンタイル順位は106と差が大きそうに見える。

ヒストグラムを使ってデータのばらつきなどを確認する

先ほどのfavrote_countを使ってヒストグラムをみて見る。

chomei_df.plot.hist(y=['favorite_count'], bins=50, alpha=0.6, figsize=(12,8), sharex=True)

f:id:kzkohashi:20180827231313p:plain

傾向的にはほぼ先ほどの要約統計量でみた通り、ほとんどのいいねが前半に集まっている。異常な値は他の数値をみづらくするので、75%以内に納めるデータでグラフ化すると

chomei_df.query('favorite_count < 106').plot.hist(y=['favorite_count'], bins=20, alpha=0.6, figsize=(12,8), sharex=True)

f:id:kzkohashi:20180827231941p:plain

だいぶみやすくなって、傾向を追いやすくなった。当たり前だけどいいね数が多くなるほど対象のツイートが減っていいっている。

終わりに

pandasを使って要約統計量やヒストグラムをだした。ざっくりとした値や傾向をみたい時には便利だなと思ったので重宝したい。