LoginSignup
0
0

More than 3 years have passed since last update.

Node.jsとMeCabで漢字・ひらがな・カタカナ辞書順ソート

Posted at

Node.js, MeCabを使用した日本語の辞書順ソート用compare関数を実装しました。
漢字、ひらがな、カタカナに対応しています。

String#localeCompare() は概ね辞書順に動きますが、濁音半濁音を無視します。
これを補完し、漢字の読みにも対応しました。

コード全体はこちらにおいてあります。

必須環境

  • Node.js
  • MeCab
$ npm install is-windows encoding-japanese --save

使い方

const Comparator = require('./japanese-comparator')

const list = ['b', 'a', '2', '1', '+', '-', '-2', 'あがき', '', '赤城']
// get関数は引数なしでも動作しますが、リストを渡してキャッシュを作ったほうが早くなります
const compare = new Comparator().get(list)
console.log(list.sort(compare))
/*
[
  '-', '-2', '+',
  '1', '2',  'a',
  'b', '赤城', 'あがき',
  '悪'
]
*/

内部の実装

function hiraToKana(str) {
  return str.replace(/[\u3041-\u3096]/g, function(match) {
    var chr = match.charCodeAt(0) + 0x60;
    return String.fromCharCode(chr);
  });
}

ひらがなをカタカナに変換します。

const dakuMap = hiraToKana('かが きぎ くぐ けげ こご さざ しじ すず せぜ そぞ ただ ちぢ つづ てで とど はばぱ ひびぴ ふぶぷ へべぺ ほぼぽ')
  .split(/\s/g)
  .reduce((prev, current) => {
    [...current].map(c => {
      prev[c] = current
    })
    return prev
  }, {})

濁音半濁音の文字の順序を定義しています。

function compareKana(a, b) {
  if (a[0] === undefined && b[0] === undefined) {
    return 0
  } else if (a[0] === undefined) {
    return -1
  } else if (b[0] === undefined) {
    return 1
  } else if (a[0] === b[0]) {
    return compareKana(a.substring(1), b.substring(1))
  } else if (
    dakuMap[a[0]] && dakuMap[b[0]] && dakuMap[a[0]] === dakuMap[b[0]]
  ) {
    return dakuMap[a[0]].indexOf(a[0]) - dakuMap[a[0]].indexOf(b[0])
  } else {
    return a[0].localeCompare(b[0], 'ja')
  }
}

カタカナの文字列同士を比較します。

if (isWindows()) {
  this.detected = Encoding.detect(exec(`echo テキストエンコーディングのテスト | mecab -Oyomi`))
}
...
Encoding.convert(exec(command), {
  from: this.detected,
  type: 'string'
})

Windowsでは何も考えずにmecabコマンドを叩くと出力がShift-JISになるので変換しています。

  createCache(list) {
    list = this.preprocess(list)
    const yomiAll = this.kanjiToKana(list.join(this.splitter), true).split(this.splitter)
    console.log(yomiAll)
    list.forEach((str, i) => {
      this.cache[str] = yomiAll[i]
    })
  }

compare関数が呼ばれるたびにmecabコマンドを叩いていると日が暮れてしまうので、
ソートする文字列リストを全結合し、一回だけmecabを叩き、再度分割、結果をキャッシュする仕組みを入れました。

以上です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0