つらねの日記

プログラムの進捗やゲームをプレイした感想などを書き連ねる日記。

駄洒落検知

前書き

最近だじゃれが流行っているみたいですね.後々見かえせたら楽だなぁと思ったので,だじゃれだったら保存するっていうのを書きたかったっていうのが発端です.

ポケモンGOも流行ってるみたいなので,ここで一句詠みたいと思います.


ポケモンGO 除けモンGOな 古い機種


現状

誤検出が多く,イマイチ.

注意

  • OSXのpython3で書いてる
  • mecab-python3とかngramとかをpip installした記憶がある

方針

s = 入力文字列

入力文字列の句読点を除去する

s = s.translate(str.maketrans({c: None for c in ',.,.、。'}))

句読点スルーしたら,「す.」みたいなのが大量にダジャレとして検出されて怒りそうだった. あと華麗にreplaceする方法がわからなかった. .replace.replaceは嫌だし,正規表現はシンプルに書けるとは言っても,出来れば使いたくないなぁ.

わかち書きしたものを用意する.

tagger_kanji = MeCab.Tagger(r'--node-format=%m\s --eos-format=')
kanji = tagger_kanji.parse(s).split()

いまのいままで-OChasenとか-Oyomiとかわかってなかったけど,ようやく意味が判った. 普通にコマンドライン引数的な感じっぽかった. でも,tail -n1的な書き方が出来るっていうのを理解したのも最近なので,今までわからなかったのも当然とも癒える .

それの仮名バージョンも用意する.

tagger_yomi = MeCab.Tagger(r'--node-format=%f[7]\s --unk-format=%M\s --eos-format=')
_yomi = tagger_yomi.parse(s)

unknown format を指定しなかったら,永遠にNoneが返ってくる世界が広がっていたので泣いていた.

仮名がそれぞれ漢字のどこにあたるのかの対応表を作っておく

table = list()
for a, b in zip(kanji, _yomi):
    for _ in b:
        table.append(a)

Bの長さの半分から2文字までの長さ(n)の同じ文字列が存在するか見ていく

for rng in range(int(len(yomi) / 2), 1, -1):
    flag = check(yomi, table, rng)

当然だけど,逆rangeも半開区間で「グエっ」って言った.

長さnのNGramを作る

bigram = ngram.NGram(N=n)

あ,最初bigramだけでやろうとしてたから変数名がそのまんまだ...

それの出現回数を調べる

2回以上のだけフィルタ
same = [d[0] for d in count.items() if d[1] > 1]

iteritemsが補完には出るのに使えないので,エディタの設定をミスしているのかもしれない. あと,そういえばNgramの補完も出ない.

複数回出現した文字列それぞれの対応している漢字が異なるかチェックする
for s in same:
    start = 0
    words = set()
    idxs = list()
    while True:
        idx = yomi.find(s, start)
        start = idx + 1
        if idx == -1:
            break
            words.add(table[idx] + table[idx + 1])
            idxs.append(idx)
            if len(words) > 1:
                # print(s, words)
                for idx in idxs:
                    flag[idx] = True

結果

飯をお召になられる
<メシ>ヲオ<メシ>ニナラレル
雨ではないので大丈夫めです
ア<メデ>ハナイノデダイジョウブ<メデ>ス
取り敢えず,英語読みと日本語読みによる洒落はスルーで.
トリアエズエイ<ゴヨミ>トニホン<ゴヨミ>ニヨルシャラクハスルーデ
実際問題,ポケストップより,ポータルのほうが慣れ親しんでいるし短く言いやすい.
NG
このままだと、日本語と英語とフランス語も真で返してしまう。
コノママダトニホン<ゴト>エイ<ゴト>フランスゴモシンデカエシテシマウ
げんたれ無くなりそうだし、補給させて貰わないと。
NG
ポケモンGOよりGO言語
NG
外から研究棟の方に回ってる人多すぎでしょ。
NG
この世界の重力を海面とではなく、その場の地面と垂直にしてほしい。
コノセカイノジュウリョクヲカイ<メント>デハナクソノバノジ<メント>スイチョクニシテホシイ
ハングリーでアングリー
ハ<ングリー>デア<ングリー>
今日の天気に勝てんきがしない。
キョウノ<テンキ>ニカチ<テンキ>ガシナイ
今起きただなんてそおきたか。
イマ<オキタ>ダナンテソ<オキタ>カ
むしろC-jが使えるようになった分、SKKが活きると言いたいところだが、ターミナルに吸われるので結局は対策しないといけない。
ムシロC-jガツカエルヨウニナッタブンSKKガイキル<トイ>イ<タ<イ>ト>コロダガターミナルニスワレルノデケッキョクハ<タイ>サクシナ<イ<ト>イ>ケナイ

感想と今後の展望

ちゃんとリアルタイムにツイッターから取得してそれがダジャレがそうでないかを判定してログを残してくれる部分を作っちゃいたいなぁ.

「日本語と英語と」っていうやつを除去したりするためにも,方針とか,処理の書きかたてきなやつも募集しています.

全文

# -*- encode: utf-8 -*-
#!/usr/bin/env python

from collections import Counter
import ngram
import MeCab


def check(yomi, table, n=2):
    bigram = ngram.NGram(N=n)
    count = Counter(bigram.ngrams(yomi))
    same = [d[0] for d in count.items() if d[1] > 1]
    flag = [False] * len(yomi)
    if same:
        for s in same:
            start = 0
            words = set()
            idxs = list()
            while True:
                idx = yomi.find(s, start)
                start = idx + 1
                if idx == -1:
                    break
                words.add(table[idx] + table[idx + 1])
                idxs.append(idx)
            if len(words) > 1:
                # print(s, words)
                for idx in idxs:
                    flag[idx] = True
    return flag


def main():
    puns = ['飯をお召になられる',
            '雨ではないので大丈夫めです',
            '取り敢えず,英語読みと日本語読みによる洒落はスルーで.',
            '実際問題,ポケストップより,ポータルのほうが慣れ親しんでいるし短く言いやすい.',
            'このままだと、日本語と英語とフランス語も真で返してしまう。',
            'げんたれ無くなりそうだし、補給させて貰わないと。',
            'ポケモンGOよりGO言語',
            '外から研究棟の方に回ってる人多すぎでしょ。',
            'この世界の重力を海面とではなく、その場の地面と垂直にしてほしい。',
            'ハングリーでアングリー',
            '今日の天気に勝てんきがしない。',
            '今起きただなんてそおきたか。',
            'むしろC-jが使えるようになった分、SKKが活きると言いたいところだが、ターミナルに吸われるので結局は対策しないといけない。', ]

    tagger_kanji = MeCab.Tagger(r'--node-format=%m\s --eos-format=')
    tagger_yomi = MeCab.Tagger(
        r'--node-format=%f[7]\s --unk-format=%M\s --eos-format=')

    for s in puns:
        print(s)
        s = s.translate(str.maketrans({c: None for c in ',.,.、。'}))
        kanji = tagger_kanji.parse(s).split()
        _yomi = tagger_yomi.parse(s)
        # print(kanji, _yomi)
        yomi = _yomi.replace(' ', '')
        _yomi = _yomi.split()
        table = list()
        for a, b in zip(kanji, _yomi):
            for _ in b:
                table.append(a)
        # print(table)
        for rng in range(int(len(yomi) / 2), 1, -1):
            flag = check(yomi, table, rng)
            if any(flag):
                # print(rng, flag)
                for i, s in enumerate(yomi):
                    if flag[i]:
                        print('<', end='')
                    if i - rng >= 0 and flag[i - rng]:
                        print('>', end='')
                    print(s, end='')
                if len(flag) - rng >= 0 and flag[len(flag) - rng]:
                    print('>', end='')
                print()
                break
            elif rng == 2:
                print('NG')
if __name__ == '__main__':
    main()