Goで機能を追加しつつ私のクローンを書き直した

この記事は高専 Advent Calender 20183日目の記事です。前日はnomunomu0504さんのAndroid, Kotlin + Camera API v2 でカメラ機能を実装するでした。アプリ開発ができる人、とてもかっこいい。

nomunomu.hateblo.jp

誰ですか

ゆきばらです。

TL; DR

Golangマルコフ連鎖をして呟くBotを作った。 焼き直しでつなぐ私の人生。

何をしましたか

先日、Haskellで私のクローンを書きました。

twitter.com

閲覧してくださった方は本当にありがとうございます。 m76r.hateblo.jp

で、関数型わからんになりました。
なので、社会に迎合してGolang*1いっちょ書き直してみますかという次第です。
普通にGolang良さそうなのもあります。

OSはWindows 10 Home Editionです。(怒らないでください、殴らないでください)

どうしましたか

今回のソースコードはこちらです。

https://github.com/Yukibara/Goooooooo_ykbr-ai

まずGoをインストールします。
Windows環境だとmsi形式のインストーラがあるのでそれを使うと良さげです。
GOPATHとかその辺りもうまくやってくれます。PATHは自分で通しておきましょう。

> go version
go version go1.11.2 windows/amd64

ソースコードを書いたらgo build main.go でビルドします。すると実行ファイルが生えます。それは実行できます。

形態素解析器をインストールする

前回はとことんMeCabに凹まされたので、ikawaha様の開発されたkagomeを使います。

qiita.com

インストールも
go get github.com/ikawaha/kagome/... だけで済みとても楽です。前回のように死ぬほどエラーを吐くこともありません。

ここで少し使ってみます。お使いのシェルを起動して kagome とだけ打ち込んでみましょう。
その後適当な文章を打ち込むと、しっかり動いてくれます。

f:id:m76r:20181129153334p:plain

今回はライブラリとして使うので、忘れずソース内でimportしておきましょう。

import (
    "github.com/ikawaha/kagome/tokenizer"
)

Twitter APIを扱うライブラリ

anacondaを使いました。
go get github.com/ChimeraCoder/anaconda をしておきましょう。

マルコフ連鎖

前回は3単語ごとに区切っていましたが、今回は2単語ごとになっています。そのため、少し不自然さがあるようです。
例えば、

になくどこか?

であったり

シンプルな

などが生成されました。「シンプルな」ではない。

ロジック自体は前回とほぼ同じです。

575の実装。

これだけだと味気ないので、@ykbr__ai 575に反応して一句読んでくれるようにしました。Goだけに。575。Go7Go。

まずは何も考えず実装して、こうなりました。

575という騒ぎではないことが分かります。しかも、これに突っ込みのリプライを入れたところ

我ながら面白いと思います。

改良してみたところ、

https
(経済
https

だの

https
(ここ
https

だのが生成されました。
ぱっと見で問題は明確で、どうやらマルチバイト文字は長さが違うようです。

const five = 15
const seven = 21

などを生み出してしまいましたが、とりあえずなんとかしたつもりでした。

当然上手くいくわけがないです。
そもそも、文字数=音数とは限らないことが頭からすっぽり抜け落ちていました。
加えて、utf-8においてマルチバイト文字は常に一定のバイト数を取ると思い込んでおり(utf-8を知っていますか?)*2、よしんば日本語が文字数=音数だったとしてもうまくいかなかったこと請け合いです。

解決法ですが、ソースの長さの都合上一部抜粋でお送りします。

func partsGo7Go(num int, worded string) (string, string) {
    re := regexp.MustCompile(`^(\p{Han}|\p{Katakana}|\p{Hiragana})+$`)
    res := ""
    lastWord := ""

    // 読みで575するための辞書
    udic, err := tokenizer.NewUserDic("./userdic.txt")
    if err != nil {
        panic(err)
    }
    t := tokenizer.New()
    t.SetUserDic(udic)
    for {
        temp := makeGo7Go(num, worded)
        if len(temp) == num {
            a := strings.Join(re.FindAllString(temp, -1), "")
            count := 0
            morphs := t.Tokenize(a)
            for _, m := range morphs {
                features := m.Features()
                b := len(features)
                // 中身があれば文字数に加算
                // 存在を確認しましょう 例外で落ちるので(1敗)
                if b >= 8 {
                    // 読みがカタカナで格納されているところ
                    c := (features[7])
                    // 音数のカウント
                    count += utf8.RuneCountInString(c)
                    lastWord = features[6]
                }
            }
            if count == num/3 {
                res += temp + "\n"
                break
            }
            continue
        }
    }
    return res, lastWord
}

まず、実装しておいたmakeGo7Goで単語を取ってきます。
それを形態素解析器にかけてutf8.RuneCountInStringで音数のカウントをしています。
音数が合わなければ単語を取ってくるところからやり直しです。 次につなげるために、その単語も返しておきます。

若干強引ですが、これで575の体裁が整いました。動かしてみましょう!

何?

問題点

  • 動かしっぱなしだとメモリを食っていきます。
    辞書で単語を管理していますが、これはプログラムが動いている限り解放されることがなさそうです。そこにデータが蓄積されていきます。 多分データベースとかやればいいんだと思います。

  • 文章がお気持ち 2単語だと文章が成り立たないこともままあります。 一方3単語運用をすると元文章そっくりそのまま出てくることがあり、一長一短だなあと感じています。
    ちなみにこれも多分データベースとかやればいいです。

感想はありますか

Golang書きやすいですね。補完のおかげも多分にあるとは思います。
個人的には第2言語に良いんじゃないかと思ったりしました。Golang自体初めて触りましたが、1日でこのくらいのものができるのはすごいと思いました。
私でこうなので、周りのプロはもっと早くもっと凄いものが出来ると思います。

プロもすなるアドベントカレンダーといふものをよわよわもしてみむという気持ちでしたが、早めの日付にエントリーしてしまいバッチリ試験期間と被りました。
これがあろうがなかろうが勉強はしなかったので、参加できてよかったです。

ありがとうございました

明日はnamachan10777さんの「キーボードを作る話」です。
私はnamachan10777さんのファンなので、とても楽しみです。

adventar.org

*1:Golangを書くと社会に迎合するのか?

*2:漢字や仮名は3~4バイトで表されます