Gongonの雑記

INVERSUS大好きゲーマー

部首共通の熟語を見つけるプログラム

はじめに

 なんかTwitterで「○○さんがいいねしました」的なやつでTLにこんなツイートが流れてきた訳ですよ。


  許諾?僥倖?魑魅魍魎?そのあたりが思いつきます。んで私がこんなことをつぶやくわけですよ。

  で試してみたら意外と上手くいったので共有しようという流れです。

 

前置き

 私自身プログラミング初学者なので強い人からすれば汚いコードかもしれないですが、これでも頑張ったほうです。因みに環境はPython 3.7.2をWindows10で動かしてる感じです。

 

熟語データの取得

 まず、熟語の一覧表的なものが欲しいわけです。でググってみるといい感じの百科辞書のサービスがあったので借りました。神。

 リンク先の一番下にあ行~わ行までのデータをテキストファイルのlzhファイルで配布されてるのでダウンロード。Windows10では標準でlzhが解凍できないので適当なオンラインサービスで解凍。「jisho」って名前のフォルダ下に解凍したフォルダを配置。jishoフォルダと同ディレクトリにPowerShellで移動して

> Get-ChildItem ".\jisho" -Recurse -File -Filter "*" | Get-Content | Add-Content "jisho.txt"

で下位フォルダのテキストファイルをjisho.txtにまとめる。
PowerShellでテキストファイルを結合する - Qiitaからコピペ)
多分文字コードSJISになってるので手動でUTF-8に変換した。

熟語データの整形

 jisho.txtの中身を見てみると丁寧にいろいろ書いてくれてますね。よく見ると欲しいのは【】で囲まれた部分の中身だと分かります。親切にも取り出しやすい形になってます。神。

import re

with open('jisho.txt', 'r', encoding='utf-8') as f:
    line = f.readline()
    with open('jukugo.txt', 'w', encoding='utf-8') as f2:
        while line:
            m = re.search(r'【[一-龥]{2,}】', line)
            if(m):
                print(line.strip()[1:-1], file=f2)
            line = f.readline()

流れとしてはjisho.txt開いて、1行ずつ読んで、【(2文字以上の漢字)】にマッチする文字列取り出して、【】を削って、ファイル出力って感じです。
「[一-龥]{2,}」で漢字のマッチを調べてるつもりなんですがあんまり自信がありません。

部首の判定

 特定の漢字とそのパーツ(部首とか偏旁)を対応させたjsonファイルを公開してる素敵な方がいらっしゃったのでjsonファイルを借りました。

yag-ays.github.io

この中の「kanji2radical.json」を使います。このjsonファイルは部首だけじゃなくて偏旁のデータもあって「別に部首に限らず偏旁共通でもよくね?」と思ったわけです。「推進」とか部首共通してないけどそれっぽさあるでしょ。なのでタイトルは嘘です。正しくは「部首または偏旁共通の熟語を見つけるプログラム」になります。
 で、書いたのが

import json

with open('kanji2radical.json', 'r', encoding="utf-8") as f:
    df = json.load(f)

with open('jukugo.txt', 'r', encoding='utf-8') as f:
    ok_jukugo = set()
    err_set = set()

    line = f.readline()
    while line:
        char_line = list(line)
        intersection = set()
        flag = 1
        
        for c in char_line:
            if str(c) == '\n':
                if any(intersection):
                    ok_jukugo.add(line.strip())
                break

            try:
                tmp =  set(df[str(c)])
                if flag:
                    flag = 0
                    intersection = tmp
                else:
                    intersection = intersection & set(tmp)

            except KeyError:
                err_set.add(str(c))
                if flag:
                    flag = 0
                    intersection = set(str(c))
                else:
                    intersection = intersection & set(str(c))

        line = f.readline()

with open('same_bushu.txt', mode='w', encoding='utf-8') as f:
    for s in ok_jukugo:
        print(s, file=f)

with open('error_kanji_list.txt', mode='w', encoding='utf-8') as f:
    print(err_set, file=f)

一応説明します。

import json

with open('kanji2radical.json', 'r', encoding="utf-8") as f:
    df = json.load(f)

先のjsonファイルを読み込んでjsonのデータを辞書としてdfに格納。dfのKeyは漢字1文字、Valueは部首、偏旁のリスト。

with open('jukugo.txt', 'r', encoding='utf-8') as f:
    ok_jukugo = set()
    err_set = set()

    line = f.readline()

熟語一覧のファイルを読み込み、所望の熟語をいれるok_jukugoとKeyが見つからないときにその文字を覚えておくerr_setをset型で宣言。なぜset型だって?同じ漢字の組で読み方が違うものが複数回出てくるからです(相殺:そうさい、そうさつ)。

    while line:
        char_line = list(line)
        intersection = set()
        flag = 1
        
        for c in char_line:
            if str(c) == '\n':
                if any(intersection):
                    ok_jukugo.add(line.strip())
                break

            try:
                tmp =  set(df[str(c)])
                if flag:
                    flag = 0
                    intersection = tmp
                else:
                    intersection = intersection & set(tmp)

            except KeyError:
                err_set.add(str(c))
                if flag:
                    flag = 0
                    intersection = set(str(c))
                else:
                    intersection = intersection & set(str(c))

        line = f.readline()

lineに熟語+改行コードがある状態です。list(line)で1文字ずつ分解して見ていきます。
部首偏旁共通か否かはset集合の積集合で判定します。分解した文字それぞれの部首偏旁の積集合が空集合でなければ「部首偏旁共通の熟語」として判定します。
 KeyErrorは発生する場合、それは大体「これ以上分解できない漢字」というものです。例を挙げると「米」。この場合はその漢字自体を「部首偏旁」として積集合の対象とします。こうすることで「米粉」が判定を通ります。
 積集合の計算をするときにintersectionの初期値が空集合だと絶対空集合になっちゃうのでflagで一回目の演算を分岐させてます。もっと賢い方法ありそうですね。
 一回tmpを挟んでるのではエラーの発生箇所を一箇所に限定したかったからです。

with open('same_bushu.txt', mode='w', encoding='utf-8') as f:
    for s in ok_jukugo:
        print(s, file=f)

with open('error_kanji_list.txt', mode='w', encoding='utf-8') as f:
    print(err_set, file=f)

あとはファイル出力するだけ。

出力はこんな感じ、見やすさのため改行をコンマにしました。

f:id:uo6uo6:20190411132335p:plain
bushu

ついでにerror_kanji_list.txtにKeyErrorが発生した漢字が記録されてます。

{'彧', '長', '竹', '雨', '八', '用', 'ま', '牛', '葈', '羊', '豕', 'か', '巾', 'ち', '水', '人', '一', '父', '食', '月', '【', 'え', '入', '艮', '金', '卜', '又', '士', '耳', 'で', '小', '工', '車', '肉', 'べ', '川', '乙', '羽', '尸', '德', 'ず', '欠', '缶', '里', '身', '匕', '山', '己', '西', '鄧', '力', '目', '心', 'ろ', '木', '十', '糸', 'み', '手', '示', '黽', '黄', '皿', '米', '燾', '寸', 'ご', '靑', '夕', '咩', '日', '刀', '戸', '大', '火', '舟', '非', '臼', '氏', '皮', '土', '斤', '女', '田', '子', '臣', '生', '二', '口', '毛', '弓', '片', '菇', 'し', '面', '門'}

なんで平仮名があんねん...

一応出力したテキストファイル共有しときます。
https://drive.google.com/open?id=1t6MZBtqEQavtFDkZGo4btNsC_Idyxypw

おわりに

 楽しかったです。あとINVERSUSってゲーム面白いからやってみな。