Pythonでひらがなカタカナの国語辞典の見出し語順ソート

前回の記事の続きです。

yucatio.hatenablog.com

今回はひらがなに加えて、カタカナも国語辞典の見出し語順に並べます。

大辞泉の見出しの配列のページから、ひらがなとカタカナに関係ある部分をを引用します。

  1. 見出しは、五十音順に配列した。一字目が同じものは二字目のかなの五十音順とし、以下も同様に扱った。長音符号「ー」は、直前のかなの母音と同じとして扱った。
  2. 同じかなのときは、以下の基準を設けて配列した。
    1. 清音・濁音・半濁音の順。 ハート→ハード→バード→パート
    2. 拗音・促音は、直音の前。 ひょう【表】→ひ‐よう【費用】
    3. 和語・漢語・外来語の順。 めい【×姪】→めい【銘】→メイ【May】

https://dictionary.goo.ne.jp/help/jn/02_03.html

アルゴリズム

ひらがなとカタカナを国語辞典の見出し語順に並び替えるプログラムを作成しましょう。凡例の、

長音符号「ー」は、直前のかなの母音と同じとして扱った。

こちらの実装は、簡単のため、長音符号「ー」の直前の文字はカタカナであることを前提とします。また、「ー」は連続しないことを前提としています。

和語・漢語・外来語の順。

こちらは単純に、それ以前の順位が同じであれば、ひらがな→カタカナの順番で並び替えるという意味とします。 幸い、カタカナはひらがなよりも文字コード上で後なので、ひらがな表記が同じ場合は、元の文字で比較すればよさそうです。

比較の順番は以下のようになります。

  1. カタカナの長音符号「ー」を直前のカタカナの母音に変換し、さらにカタカナをひらがなに変換する。さらに濁音と半濁音を清音に、拗音と促音を直音に変換してソートする
  2. 1の並べ替えで順序が同じ場合は、カタカナの長音符号「ー」を直前のカタカナの母音に変換し、さらにカタカナをひらがなに変換する。さらに拗音と促音を直音に変換してソートする
  3. 2の並び替えでも順序が同じ場合は、カタカナの長音符号「ー」を直前のカタカナの母音に変換し、さらにカタカナをひらがなに変換してソートする
  4. 3の並び替えでも順序が同じ場合は、もとのひらがなとカタカナを使用して並べ替える

なお前回に引き続き、小さい'ゎ' 'ゕ' 'ゖ'、および旧かな使いの'ゐ''ゑ'は今回は考慮の対象外としています。

コード

上記アルゴリズムを実装したPythonのコードです。

import re
import jaconv

DAKU_TO_SEION = {
    'ゔ': 'う',
    'が': 'が', 'ぎ': 'き', 'ぐ': 'く', 'げ': 'け', 'ご': 'こ',
    'ざ': 'さ', 'じ': 'し', 'ず': 'す', 'ぜ': 'せ', 'ぞ': 'そ',
    'だ': 'た', 'ぢ': 'ち', 'づ': 'つ', 'で': 'て', 'ど': 'と',
    'ば': 'は', 'び': 'ひ', 'ぶ': 'ふ', 'べ': 'へ', 'ぼ': 'ほ',
    'ぱ': 'は', 'ぴ': 'ひ', 'ぷ': 'ふ', 'ぺ': 'へ', 'ぽ': 'ほ',
}

SMALL_TO_NORMAL = {
    'ぁ': 'あ', 'ぃ': 'い', 'ぅ': 'う', 'ぇ': 'え', 'ぉ': 'お',
    'っ': 'つ',
    'ゃ': 'や', 'ゅ': 'ゆ', 'ょ': 'よ',
}

KATAKANA_VOWEL = {
    'ァ': 'ア', 'ィ': 'イ', 'ゥ': 'ウ', 'ェ': 'エ', 'ォ': 'オ',
    'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
    'ヴ': 'ウ',
    'カ': 'ア', 'キ': 'イ', 'ク': 'ウ', 'ケ': 'エ', 'コ': 'オ',
    'ガ': 'ア', 'ギ': 'イ', 'グ': 'ウ', 'ゲ': 'エ', 'ゴ': 'オ',
    'サ': 'ア', 'シ': 'イ', 'ス': 'ウ', 'セ': 'エ', 'ソ': 'オ',
    'ザ': 'ア', 'ジ': 'イ', 'ズ': 'ウ', 'ゼ': 'エ', 'ゾ': 'オ',
    'タ': 'ア', 'チ': 'イ', 'ツ': 'ウ', 'テ': 'エ', 'ト': 'オ',
    'ダ': 'ア', 'ヂ': 'イ', 'ヅ': 'ウ', 'デ': 'エ', 'ド': 'オ',
    'ナ': 'ア', 'ニ': 'イ', 'ヌ': 'ウ', 'ネ': 'エ', 'ノ': 'オ',
    'ハ': 'ア', 'ヒ': 'イ', 'フ': 'ウ', 'ヘ': 'エ', 'ホ': 'オ',
    'バ': 'ア', 'ビ': 'イ', 'ブ': 'ウ', 'ベ': 'エ', 'ボ': 'オ',
    'パ': 'ア', 'ピ': 'イ', 'プ': 'ウ', 'ペ': 'エ', 'ポ': 'オ',
    'マ': 'ア', 'ミ': 'イ', 'ム': 'ウ', 'メ': 'エ', 'モ': 'オ',
    'ャ': 'ア', 'ュ': 'ウ', 'ョ': 'オ',
    'ヤ': 'ア', 'ユ': 'ウ', 'ヨ': 'オ',
    'ラ': 'ア', 'リ': 'イ', 'ル': 'ウ', 'レ': 'エ', 'ロ': 'オ',
    'ワ': 'ア', 'ヲ': 'オ',
}

TO_NORMAL_SEION_TABLE = str.maketrans({**DAKU_TO_SEION, **SMALL_TO_NORMAL})

TO_NORMAL_TABLE = str.maketrans(SMALL_TO_NORMAL)


def dash_to_kana(matchobj):
    """Converts "(A katakana) + 'ー'" to "(A katakana) + (katakana vowel)"
    ex) 'ター' -> 'タア', 'ヒー' -> 'ヒイ'
    """
    # second letter must be dash
    first_letter, dash = matchobj.group(0)
    if first_letter in KATAKANA_VOWEL:
        return first_letter + KATAKANA_VOWEL[first_letter]
    else:
        return first_letter + first_letter


def hiragana(kana):
    dash_translated = re.sub('.ー', dash_to_kana, kana)
    return jaconv.kata2hira(dash_translated)


def sort_as_ja_dict(kana_list):
    return sorted(kana_list,
                  key=lambda kana: (hiragana(kana).translate(TO_NORMAL_SEION_TABLE),
                                    hiragana(kana).translate(TO_NORMAL_TABLE), hiragana(kana), kana))


# 使い方
kana_item_2 = [
    'こおどり',
    'メイ',
    'コード',
    'コーヒー',
    'コート',
    'こおり',
    'めい',
    'かれき',
    'カレー',
    'かれえ',
    'かり',
    'カラー',
    'からい',
]

sort_as_ja_dict(kana_item_2)
# => ['カラー', 'からい', 'かり', 'かれえ', 'カレー', 'かれき', 'コート', 'コード', 'こおどり', 'コーヒー', 'こおり', 'めい', 'メイ']

コードの解説

カタカナの長音符号「ー」を直前のカタカナに変換するのは以下の部分です。

KATAKANA_VOWEL = {
    'ァ': 'ア', 'ィ': 'イ', 'ゥ': 'ウ', 'ェ': 'エ', 'ォ': 'オ',
    'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
    'ヴ': 'ウ',
    'カ': 'ア', 'キ': 'イ', 'ク': 'ウ', 'ケ': 'エ', 'コ': 'オ',
    'ガ': 'ア', 'ギ': 'イ', 'グ': 'ウ', 'ゲ': 'エ', 'ゴ': 'オ',
    'サ': 'ア', 'シ': 'イ', 'ス': 'ウ', 'セ': 'エ', 'ソ': 'オ',
    'ザ': 'ア', 'ジ': 'イ', 'ズ': 'ウ', 'ゼ': 'エ', 'ゾ': 'オ',
    'タ': 'ア', 'チ': 'イ', 'ツ': 'ウ', 'テ': 'エ', 'ト': 'オ',
    'ダ': 'ア', 'ヂ': 'イ', 'ヅ': 'ウ', 'デ': 'エ', 'ド': 'オ',
    'ナ': 'ア', 'ニ': 'イ', 'ヌ': 'ウ', 'ネ': 'エ', 'ノ': 'オ',
    'ハ': 'ア', 'ヒ': 'イ', 'フ': 'ウ', 'ヘ': 'エ', 'ホ': 'オ',
    'バ': 'ア', 'ビ': 'イ', 'ブ': 'ウ', 'ベ': 'エ', 'ボ': 'オ',
    'パ': 'ア', 'ピ': 'イ', 'プ': 'ウ', 'ペ': 'エ', 'ポ': 'オ',
    'マ': 'ア', 'ミ': 'イ', 'ム': 'ウ', 'メ': 'エ', 'モ': 'オ',
    'ャ': 'ア', 'ュ': 'ウ', 'ョ': 'オ',
    'ヤ': 'ア', 'ユ': 'ウ', 'ヨ': 'オ',
    'ラ': 'ア', 'リ': 'イ', 'ル': 'ウ', 'レ': 'エ', 'ロ': 'オ',
    'ワ': 'ア', 'ヲ': 'オ',
}

def dash_to_kana(matchobj):
    """Converts "(A katakana) + 'ー'" to "(A katakana) + (katakana vowel)"
    ex) 'ター' -> 'タア', 'ヒー' -> 'ヒイ'
    """
    # second letter must be dash
    first_letter, dash = matchobj.group(0)
    if first_letter in KATAKANA_VOWEL:
        return first_letter + KATAKANA_VOWEL[first_letter]
    else:
        return first_letter + first_letter


def hiragana(kana):
    dash_translated = re.sub('.ー', dash_to_kana, kana)
    return jaconv.kata2hira(dash_translated)

長音記号とその直前の文字を取得するのに re.sub(pattern, repl, string, count=0, flags=0)( re --- 正規表現操作 — Python 3.7.6 ドキュメント )を利用します。

pattern'.ー'を指定し、長音記号と直前の文字を取得します。 replに関数(dash_to_kana)を指定し、patternにマッチした文字列を処理します。

dash_to_kanaで、matchobj.group(0)を使用してpatternにマッチした文字列全体を取り出します。

マッチした最初の文字を母音を、KATAKANA_VOWELにそって置換します。

最後に、最初の文字とその母音をつなげて返します。

カタカナからひらがなへの変換はjaconvを利用しました。

def hiragana(kana):
    dash_translated = re.sub('.ー', dash_to_kana, kana)
    return jaconv.kata2hira(dash_translated)

ひらがなとカタカナが入った並び替えでは、途中まではカタカナをひらがなに変換して比較します。 ひらがなかカタカナで順番が決まるのは最後です。

ひらがなのみのときのsort_as_ja_dictは以下でした。

# ひらがなのみ比較バージョン
def sort_as_ja_dict(kana_list):
    return sorted(kana_list,
                  key=lambda kana: (kana.translate(TO_NORMAL_SEION_TABLE),
                                    kana.translate(TO_NORMAL_TABLE), kana))

比較する値のkanaの部分をhiragana(kana)に変換し、最後にkanaによる比較を追加します。

def sort_as_ja_dict(kana_list):
    return sorted(kana_list,
                  key=lambda kana: (hiragana(kana).translate(TO_NORMAL_SEION_TABLE),
                                    hiragana(kana).translate(TO_NORMAL_TABLE), hiragana(kana), kana))

動作を確認してみましょう。 最後にひらがなの後にカタカナが並べられることがわかります。

kana_item_2 = [
    'こおどり',
    'メイ',
    'コード',
    'コーヒー',
    'コート',
    'こおり',
    'めい',
    'かれき',
    'カレー',
    'かれえ',
    'かり',
    'カラー',
    'からい',
]

sorted(kana_item_2)
# => ['からい', 'かり', 'かれえ', 'かれき', 'こおどり', 'こおり', 'めい', 'カラー', 'カレー', 'コート', 'コード', 'コーヒー', 'メイ']

sorted(kana_item_2,
       key=lambda kana: hiragana(kana).translate(TO_NORMAL_SEION_TABLE))
# => ['カラー', 'からい', 'かり', 'カレー', 'かれえ', 'かれき', 'コート', 'コード', 'こおどり', 'コーヒー', 'こおり', 'メイ', 'めい']

sorted(kana_item_2,
       key=lambda kana: (hiragana(kana).translate(TO_NORMAL_SEION_TABLE),
                         hiragana(kana).translate(TO_NORMAL_TABLE)))
# => ['カラー', 'からい', 'かり', 'カレー', 'かれえ', 'かれき', 'コート', 'コード', 'こおどり', 'コーヒー', 'こおり', 'メイ', 'めい']

sorted(kana_item_2,
       key=lambda kana: (hiragana(kana).translate(TO_NORMAL_SEION_TABLE),
                         hiragana(kana).translate(TO_NORMAL_TABLE), hiragana(kana)))
# => ['カラー', 'からい', 'かり', 'カレー', 'かれえ', 'かれき', 'コート', 'コード', 'こおどり', 'コーヒー', 'こおり', 'メイ', 'めい']

sorted(kana_item_2,
       key=lambda kana: (hiragana(kana).translate(TO_NORMAL_SEION_TABLE),
                         hiragana(kana).translate(TO_NORMAL_TABLE), hiragana(kana), kana))
# => ['カラー', 'からい', 'かり', 'かれえ', 'カレー', 'かれき', 'コート', 'コード', 'こおどり', 'コーヒー', 'こおり', 'めい', 'メイ']

以上でひらがなとカタカナを国語辞典の見出し語順に並び替えることができました。

環境