Panda Noir

JavaScript の限界を究めるブログです。

配列をいろは順にソートするプログラム

タイトルのとおりです。いろは順に並べ替える関数をつくりました。

まずはコード

const sorter = (a, b) => {
    const len = Math.min(a.length, b.length);
    const comp = (a, b) => (a > b) - (a < b);
    const order = 'いぃろはばぱにほぼぽへべぺとどちぢりぬるをわゎかヵがよょただれそぞつづっねならむうぅゐのおぉくぐやまけヶげふぶぷこごえぇてであぁさざきぎゆゅめみしじゑひびぴもせぜすずん';
    for (let i = 0; len > i; i++) {
        const indexA = order.indexOf(a[i]), indexB = order.indexOf(b[i]);
        if (indexA === -1 || indexB === -1) return comp(a[i], b[i]); // (1)
        if (indexA !== indexB) return comp(indexA, indexB); // (2)
    }
    return comp(a.length, b.length); // (3)
};

こんな感じで使います

['ろうか', '1', 'いか', 'abc', 'はじめ'].sort(sorter); // ['1', 'abc', 'いか', 'ろうか', 'はじめ']

仕様

  1. 頭のほうから順に比較していく
  2. 比較対象がどちらもひらがなの場合、いろは順で比較する(2)
  3. それ以外の場合は普通の辞書順で比較する(1)
  4. 同じ文字だった場合、次の文字を見る
  5. 最後まで見ても同じだった場合、文字の長さで比較する(「はじめ」と「はじめる」のような場合)(3)

こんな感じとなっています。要するに、ひらがな以外はデフォルトのソートと同じ挙動となります。

濁音、半濁音、拗音ですが、例えば「ば」と「ぱ」の場合は「は」の直後としています。つまり「いろはばぱに」という順です。いろは順について調べてもこれといったものが見つからなかったので、とりあえずこうしました。

応用編

正直いろは順でソートできてもあまりうれしいことないと思いますが、このコードのorderの部分を書き換えれば好きな順でソートできるプログラムとなります。

const createSorter = order => (a, b) => {
    const len = Math.min(a.length, b.length);
    const comp = (a, b) => (a > b) - (a < b);
    for (let i = 0; len > i; i++) {
        const indexA = order.indexOf(a[i]), indexB = order.indexOf(b[i]);
        if (indexA === -1 || indexB === -1) return comp(a[i], b[i]); // (1)
        if (indexA !== indexB) return comp(indexA, indexB); // (2)
    }
    return comp(a.length, b.length); // (3)
};
const sorter = createSorter('いぃろはばぱにほぼぽへべぺとどちぢりぬるをわゎかヵがよょただれそぞつづっねならむうぅゐのおぉくぐやまけヶげふぶぷこごえぇてであぁさざきぎゆゅめみしじゑひびぴもせぜすずん');

まあこれでもそんなに使う場面はない気がしますが

追記: 2017/10/12

改良したコードを作りました。Node v8.5.0にて検証したところ4倍、配列の長さによっては8倍ほど早くなりました。とりあえず前のコードよりはるかに速いのはまちがいないです。

ただし、sortメソッドに渡すsorterを作る、という制約のため、いちいちreplaceしなければならず、そこがネックとなりそうです。いっそ配列ごと渡す形にすればもっと高速化できると思います。

const createSorter = order => {
    const zip = (a, b) => a.map((item, i) => [item, b[i]]);
    const dic = new Map(zip([...order], [...order].sort()));
    const reg = new RegExp(`[${order}]`, 'g');
    const rep = a => dic.get(a);
    return (a, b) => {
        a = ('' + a).replace(reg, rep);
        b = ('' + b).replace(reg, rep);
        return (a > b) - (a < b);
    };
};