python#2.7 決まり字チェッカーver2

 今回は、大幅に改良しました。前と比べて大きく変わったところは

① ルビ振りとセットにして、変換ミスもその場で修正できるようにした

② ルビ振りのところで、カンマ区切りにしたことで「冒険Type A, B, C!!」のようなモノで後半が切れる事象が起きていた点を、'漢' 'a' に変えることで修正

③ リストから csv を作るのではなく、tsv から tsv を作るように変更;全体を通して、辞書のリストという形式に頼るようにする

あたりでしょうか。そもそも前のプログラムが不完全すぎましたからね……

 まだ改良したい点

① 変換ミスをチェックする際に、一気に辞書のリストを print して目で見るのはしんどい。せめてもう少し見やすい形に整えたい。改行するとか。

② テストプログラム書く…?6回くらい修正したら通ったので作りたくない

③ ②にも関わる話だが、クラス化?うぅ……クラスいまだによくわかってないよぉ

④ 内包表記使えるところは使う

⑤ for文; for i in range(len(listA)): listA[i]... とかしなくても for item in listA: item... で良いのでそれを修正する

import tkinter as tk
import tkinter.filedialog
from pykakasi import kakasi
import regex
import csv

# 文字種 (ひらがな, 漢字, 長音符以外の記号) を判別するメソッド

p = regex.compile(r'[\p{Script=Hiragana}\p{Script=Han}]+')
pAll = regex.compile(r'[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}ーa-zA-Z0-9]+')

# 漢字をひらがなに直すメソッド (ひらがなはひらがなのまま残る)

kakasi = kakasi()
kakasi.setMode('J', 'H')
conv = kakasi.getConverter()

               
# 文字列 s のうち、漢字またはひらがなの部分だけをひらがなに変換する関数 converter(s) を定義する
def converter(s):
   # まず、文字列 s のうち漢字かひらがなの箇所だけ、ひらがなに変換する         
   # 「漢字かひらがななのは、何番目の文字か」「それ以外なのは、何番目の文字か」を num_listA, B に保存する
   num_listA = []
   num_listB = []
   for i in range(len(s)):
       if p.fullmatch(s[i]):
           num_listA.append(i)
       else:
           num_listB.append(i)
   # 空の文字列 conv_s を設定し、そこに文字列を足していく方針をとる
   conv_s = ''        
   # num_listA の要素数が 0 の場合、漢字もひらがなも含まれないので、変換は不要
   if len(num_listA) == 0:
       conv_s += s
   # num_listA の要素数が 0 でない場合、漢字かひらがなが含まれるので、変換が必要
   else:            
       # num_listB の要素数が 0 である場合、漢字かひらがなしか含まれないので、丸ごと変換すれば良い
       if len(num_listB) == 0:
           conv_s += conv.do(s)                
       # num_listB の要素数が 0 でない場合
       else:                
           # 方針:漢字・ひらがなの連続部を一括変換し、漢字・ひらがな以外の連続部をセットで保存したものと組み合せたい
           # 例:s = '2020年12月31日木曜日' のとき
           # ['2020', '12', '31'] と ['ねん', 'がつ', 'にちもくようび'] というリストに保存し、zip 関数で復元したい                
           # 文字列を設定し、ここに '漢' 'a' 区切りで連続している漢字・ひらがな部分を保存し、あとでリストに戻す
           # 例:s = '2020年12月31日木曜日' のとき
           # '2020漢12漢31' と 'ねんaがつaにちもくようび' という文字列を作り、'漢' 'a' で split してリストに変換する
           # なお、文字種判定を行っているので、元から '漢' 'a' が含まれている可能性はない
           # 「連続かどうか」は、num_list における次の数字との差が 1 かどうかで判定する
           # 文字列の初期値に s のうち 1 文字目の漢字 or ひらがなを設定する
           # これで、差を考えることのできない場合 (num_list の要素数が 1 の場合) もカバーできる                
           # 先に、変換のない「漢字・ひらがな以外」の方 (num_listB の方) を処理する
           stringB = s[num_listB[0]]
           if len(num_listB) >= 2:
               # 差をとれる回数は num_list の要素数より 1 少なくなる
               for i in range(len(num_listB)-1):
                   # 差が 1 でない場合のみ '漢' を足してから
                   if num_listB[i+1] - num_listB[i] != 1:
                       stringB += '漢'
                   # stringB に「漢字・ひらがな以外」の文字を一文字追加する
                   stringB += s[num_listB[i+1]]
           # stringB を '漢' で区切ってリスト (listed_B) に変換
           listed_B = stringB.split('漢')                    
           # 次に、変換のある「漢字・ひらがな」の方 (num_listA の方) を処理する
           # listed_B と同じ要領で、listed_a を作る
           stringA = s[num_listA[0]]
           if len(num_listA) >= 2:
               for i in range(len(num_listA)-1):
                   if num_listA[i+1] - num_listA[i] != 1:
                       stringA += 'a'
                   # stringA に「漢字・ひらがな」の文字を一文字追加する
                   stringA += s[num_listA[i+1]]
           # stringA を 'a' で区切ってリストに変換
           listed_a = stringA.split('a')
           # listed_a の各要素をひらがなに変換した listed_A を作る
           listed_A = []
           for i in range(len(listed_a)):
               listed_A.append(conv.do(listed_a[i]))
           # listed_A, B という 2 つのリストはできたが、長さが違うと zip 関数では短い方に合わされてしまう
           # そこで、短い (正確には長くない) 方の最後に '' を足した上で、zip 関数で交互に文字列をつなげる
           # 「正確には長くない方」:長さが等しいところに '' を足してもそこは切られるので、特に不都合はないため
           # s の 1 文字目が漢字・ひらがなの場合、listed_A の要素数は B の要素数以上なので、B に '' を足す
           if 0 in num_listA:
               listed_B.append('')
               for strA, strB in zip(listed_A, listed_B):
                   conv_s += strA + strB                            
           # s の 1 文字目が漢字・ひらがな以外の場合、先程の逆で、A に '' を足す
           else:
               listed_A.append('')
               for strB, strA in zip(listed_B, listed_A):
                   conv_s += strB + strA
   # 戻り値として conv_s を返す
   return conv_s

# 文字列 s のうち、長音符以外の記号を削除する関数 remover(s) を定義する
def remover(s):
   num_list = []
   remov_s = ''
   # 「長音符以外の記号」以外の文字かどうかを判定する
   for i in range(len(s)):
       if pAll.fullmatch(s[i]):
           num_list.append(i)
   # 「長音符以外の記号」以外の文字だけを s から取り出して remov_s に付け足していく
   for i in range(len(num_list)):
       remov_s += s[num_list[i]]
   return remov_s

# 複数のリスト a, b, c, .. を、辞書のリスト l_of_dics に組み込む関数 list_into_dic を定義する
# [属性Aの情報1,2,..,n],[B ],[C ],.. 内の情報を、
# 既存の 辞書のリスト [{X:--, Y:--, ..},{},..,{}] 内の n 個の辞書に追加する
# a に * をつけることで、2 個目以降の引数をまとめてタプルとして受け取らせる
def list_into_dic(l_of_dics, *a):
   # タプルを for ループで展開する
   for i in range(len(a)):
       x = a[i]
       # リスト と 辞書のリスト の長さが同じである場合にのみ動作させる
       if len(x) == len(l_of_dics):
           X = input('追加するキーの名前を入力してください:')
           for j in range(len(x)):
               # l_of_dics の j 番目に保存されている辞書に、キー X, 値 x[j] の組を追加する
               l_of_dics[j][X] = x[j]
       else:
           print(i + '番目のリストを組み込めません. 要素数を揃えてください.')
   return l_of_dics

# id を振る関数
def id_maker(l_of_dics):
   id_list = [str(i) for i in range(len(l_of_dics))]
   list_into_dic(l_of_dics, id_list)
   return l_of_dics
   
# 辞書のリスト の一部を修正する関数 modifying_dic を定義する
def modifying_dic(l_of_dics):
   print(l_of_dics)
   while True:
       q = input('修正箇所がまだあれば[y]を, もうなければ[n]を入力してください:')
       # 修正箇所がある場合
       if q == 'y':
           # 修正箇所の id を入力して、該当する辞書を取り出してくる
           try:
               num = input('修正したい文字列の id を入力してください:')
               for i in range(len(l_of_dics)):
                   target = l_of_dics[i]
                   # 辞書 target の 'id' に対する値が num である場合だけ修正に入る
                   if target['id'] == num:
                       print(target)
                       while True:                    
                           k = input('どのキーに属する値を修正しますか?:')
                           if k in target:
                               v = input('修正後の値を入力してください:')
                               target[k] = v
                               break
                           else:
                               print('その値はキーとして存在しません')
           # id として適切な値が入力されなかった場合の処理
           except ValueError:
               print('id として適切な値を入力してください')
       # 修正箇所がない場合
       elif q == 'n':
           break
       # 修正箇所の有無が確認できなかった場合
       else:
           print('半角英字の[y]か[n]を入力してください')
   return l_of_dics

## 辞書のリストにおいて、特定のキーに対応する値 (に含まれる漢字) の読み仮名を作成し、長音符以外の記号を削除し、
## それを新しく辞書に加え直した状態の 辞書のリスト を返す関数 rubi を定義する
def rubi(l_of_dics):
   # キーの名前を確認するために 1 つめの辞書だけ表示する
   print(l_of_dics[0])
   # 特定のキーに対応する値のリストを作る
   while True:
       k = input('どのキーに属する値にルビを振りますか?:')
       # どの辞書も同じキーを有しているとし、0 番目の辞書にキー k があれば全ての辞書にキー k があるとみなす
       if k in l_of_dics[0]:
           target_vals = []
           for dic in l_of_dics:
               target_vals.append(dic[k])
           break
       # k というキーがない場合、打ち込み直す
       else:
           print('そのようなキーは存在しません')
   # リストに保存されている各文字列にルビを振り、長音符以外の記号を削除し、別のリストに保存する
   new_vals = []
   for value in target_vals:
       new_vals.append(remover(converter(value)))
   # リスト new_vals 内の情報を全て元の 辞書のリスト に登録する
   l_of_dics_2 = list_into_dic(l_of_dics, new_vals)
   # 最後に、修正したい場所を修正できるようにしておく
   l_of_dics_3 = modifying_dic(l_of_dics_2)
   return l_of_dics_3    

# 辞書のリスト をソートする関数 sorting_dic を定義する
def sorting_dic(l_of_dics):
   while True:
       k = input('どのキーでソートしますか?:')
       # どの辞書も同じキーを有しているとし、0 番目の辞書にキー k があれば全ての辞書にキー k があるとみなす
       if k in l_of_dics[0]:
           l_of_dics = sorted(l_of_dics, key = lambda x:x[k])
           break
       # k というキーがない場合、打ち込み直す
       else:
           print('そのようなキーは存在しません')
   # あとでキーを使いたいので 辞書のリスト と ソートしたキー をタプルで返す
   return (l_of_dics, k)

# 文字列 2 つを比較して決まり字を返す関数 kmrj を定義する
def kmrj(strA, strB):
   # strA と strB が同一である場合、いずれの決まり字も 'strA(重複)' と記述する
   if strA == strB:
       return ((strA + '(重複)'), (strB + '重複'))    
   # strB が strA で始まる場合、strA の決まり字は 'strA」' と記述する
   # strB の決まり字は、strB を (strA の文字数 + 1) 文字目までスライスした部分になる
   # 例:'py' と 'python' を比べるとき、決まり字はそれぞれ 'py」', 'pyt' とする
   elif strB.startswith(strA):
       return ((strA + '」'), strB[:len(strA)+1])
   # strA が strB で始まる場合、同様の処理を行う
   # 今回のメインプログラムではこのような状況は起きないので不要である
   elif strA.startswith(strB):
       return (strA[:len(strB)+1], (strB + '」'))
   # それ以外の場合  
   else:
       # 同じ文字数だけスライスする
       # スライスする文字数を増やしていき、初めて異なったところが決まり字
       for i in range(len(strA)):
           if strA[:i+1] != strB[:i+1]:
               return (strA[:i+1], strB[:i+1])
               break

## 辞書のリストにおいて、特定のキーに対応する値の決まり字を判定し、
## それを新しく辞書に加え直した状態の 辞書のリスト を返す関数 kmrj_main を定義する
def kmrj_main(l_of_dics):
   # 辞書のリスト l_of_dics を キー k でソートし、s_l_of_dics を作る
   l_of_dics, k = sorting_dic(l_of_dics)
   # 結果を保存しておく空リスト kmrjs を作る
   kmrjs = []
   # 要素が1つだけの場合、1字決まり
   if len(l_of_dics) == 1:
       kmrjs.append(l_of_dics[k][0])
   # 要素が2つの場合、kmrj 関数の戻り値がそのまま結果になる (タプル型をリスト型に変える必要あり)
   elif len(l_of_dics) == 2:
       kmrjs = list(kmrj(l_of_dics[0][k], l_of_dics[1][k]))
   # 要素が3つ以上の場合
   else:
       for i in range(len(l_of_dics)):
           # 最初は l_of_dics[0][k] と l_of_dics[1][k] で比べるだけで決まり字が決まる
           if i == 0:
               kmrjs.append(kmrj(l_of_dics[i][k], l_of_dics[i+1][k])[0])
           # 最初と最後以外は、2 回 kmrj 関数を使って長い方を決まり字とする
           # 前の要素との間で kmrj 関数を使用し、後ろの要素との間でも kmrj 関数を使用する
           elif 1 <= i <= len(l_of_dics) - 2:
               # kmrj 関数の返り値をタプルにしているのはこの2行のため
               a, B = kmrj(l_of_dics[i-1][k], l_of_dics[i][k])
               b, C = kmrj(l_of_dics[i][k], l_of_dics[i+1][k])
               # B が b で始まる or 一致する (= B の長さが b 以上である) 場合、決まり字は B
               if B.startswith(b):
                   kmrjs.append(B)
               # b の方が B より長い場合、決まり字は b
               else:
                   kmrjs.append(b)
           # 最後は l_of_dics[i-1][k] と l_of_dics[i][k] で比べるだけで決まり字が決まる
           else:
               kmrjs.append(kmrj(l_of_dics[i-1][k], l_of_dics[i][k])[1])
   # 完成した決まり字のリスト kmrjs を、ソートした辞書のリスト l_of_dics に登録する
   l_of_dics = list_into_dic(l_of_dics, kmrjs)
   return l_of_dics

# ファイルダイアログで TSV ファイルを 辞書のリスト として読み込む関数
# キーは 1 行目に書かれているとする
def openfile():
   filedata = []
   root = tk.Tk()
   root.withdraw()
   filename = tkinter.filedialog.askopenfilename()
   root.destroy()
   if filename:
       try:
           with open(filename, 'r') as f:
               rows = list(csv.reader(f, delimiter = '\t'))
               # 1 行だけしかデータがない TSV ファイルは受け付けない
               if len(rows) <= 1:
                   print('2 行以上の TSV ファイルを読み込んでください')
               else:
                   # 1 行目をキーとして扱う
                   keys = rows[0]
                   # 2 行目以降を値として各行を辞書に変換する
                   for row in rows[1:]:
                       # 空の辞書を作る
                       dic_row = {}
                       for i in range(len(row)):
                           # keys の i 番目のキーに対する値が、row の i 番目の要素
                           dic_row[keys[i]] = row[i]
                       # 行 row を変換した辞書 dic_row を 辞書のリスト filedata に保存する
                       filedata.append(dic_row)
                   # 辞書のリストを返す
                   return filedata
       # ファイルが開けなかったときの例外処理
       except OSError:
           print('ファイルを開くことができません')
   # ファイルが指定されていないときの処理
   else:
       print('ファイルが指定されていません')
       exit()

# ファイルダイアログで 辞書のリスト を TSV ファイルとして書き出す関数
def saveas(l_of_dics):
   root = tk.Tk()
   root.withdraw()
   filename = tkinter.filedialog.asksaveasfilename()
   root.destroy()
   if filename:
       try:
           with open(filename, 'w') as f:
               # キーのリストを取得する
               keys = list(l_of_dics[0].keys())
               # 1 行目はキーを入力するので、初めに キー1 を入力する
               f.write(str(keys[0]))
               if len(keys) >= 2:
                   # [tab]キー2 [tab]キー3 ... [tab]キーn と入力する
                   for i in range(len(keys)-1):
                       f.write('\t' + str(keys[i+1]))
               f.write('\n')
               # 2 行目以降はキーに対応した値を書いていく
               for i in range(len(l_of_dics)-1):
                   # 辞書を取り出す
                   target = l_of_dics[i+1]
                   # あとは 1 行目とほぼ同様の処理を行う
                   f.write(str(target[keys[0]]))
                   if len(keys) >= 2:
                       for j in range(len(keys)-1):
                           f.write('\t' + str(target[keys[j+1]]))
                   f.write('\n')
       # ファイルが開けなかったときの例外処理
       except OSError:
           print('ファイルを開くことができません')
   # ファイルが指定されていないときの処理
   else:
       print('ファイルが指定されていません')
       exit()


# メインプログラム                            
if __name__ == '__main__':
   l_of_dics = openfile()
   id_maker(l_of_dics)
   rubi(l_of_dics)
   kmrj_main(l_of_dics)
   saveas(l_of_dics)

   

参考記事

・ファイルダイアログで複数ファイルを選択する方法

・while 文でも break は使えるのか

・辞書の扱い

・引数の個数を自由に変えられるようにする (タプルとしてまとめて受け取る)

・辞書のリストを並び替える

・lambda とはそもそも何なのか

・csv ファイルの読み込み・書き出し

・ファイルの書き出しにおける 'w' と 'a' の差

・辞書のリストから特定のキーの値のリストを取得(ここを見てようやく、for ループで for i in range(len(listA)): listA[i]... とかしなくても for item in listA: item... で良いことを思い出す )

・TSVファイルの開き方

・tsv読み込み時のエラー対処;UnicodeDecodeError: 'utf-8' codec can't decode byte 0x91 in position 15: invalid start byte

・f.write() で数値を書き出せないことの確認


この記事が気に入ったらサポートをしてみませんか?