9
12

More than 3 years have passed since last update.

JavaScriptで文字列と数字が混在したソート

Last updated at Posted at 2020-06-17

やる事

文字列と数字が混在した配列でも、それっぽいソートをしたい。

Test1
Test10
Test11
Test2
Test3
...

という「なんだかな~」っていう並びを

Test1
Test2
Test3
...
Test9
Test10
Test11

にするやつです。
他にいいやつあるかな~と思ったけど、この位だと作った方が早いし、多言語に移植する予定もあったので、考察の為

とりあえず、適当に列挙したこれが

yaml
- '25'
- 12.234.111.233
- 12.234.111.28
- 12.234.27.255
- 184
- a9-b
- a100
- a9-a
- tenuno01
- test
- test00030-1
- test00123-156-3
- test123-4
- wawa1

これになります。

yaml
- 12.234.27.255
- 12.234.111.28
- 12.234.111.233
- '25'
- 184
- a9-a
- a9-b
- a100
- tenuno01
- test
- test00030-1
- test123-4
- test00123-156-3
- wawa1

気に入らなければ話はここで終わりDA(改善点聞かせてください><)

考察

  • 例えば Test1-10 といった値は 「110」という捉え方ではなく、「1」 グループの中の 「10」 という捉え方とする
  • 文字列の長さ優先ソートではなく、中途半端にゼロ埋めされたデータにも対応したい( 101 とか)
  • 従って既存のゼロ埋めは無視( ABC-00123ABC-05 だと05が前に来る等)
  • 数の接続詞(- / : . (n) 等)の重みは考慮しない(考慮するときりがない・・)
  • つまり・・数字は「数字の塊」、文字は「文字の塊」と見なしてソートする
  • マイナス無視(ハイフンとみなす為)

コード

プロトタイプで失礼。バグや、他に良いものがあれば教えて下さい。

追記: 更に良いものを教えていただきましたので、この例はあくまで経緯としてご覧ください。

JavaScript
// テスト
var array_test = ["25", "12.234.111.233", "12.234.111.28", "12.234.27.255", 184, "a9-b", "a100", "a9-a", "tenuno01", "test", "test00030-1", "test00123-156-3", "test123-4", "wawa1"];

console.log(array_test.sort(sort_num_block));
// ["12.234.27.255", "12.234.111.28", "12.234.111.233", "25", 184, "a9-a", "a9-b", "a100", "tenuno01", "test", "test00030-1", "test123-4", "test00123-156-3", "wawa1"]

/**
 * 数値文字列グループ化ソート
 *
 * - ゼロ埋め無視(ゼロ埋めの桁数が合ってなくても無視)
 * - マイナス無視(ハイフンとみなす)
 */
function sort_num_block(a, b) {
    // 数字のかたまりと、文字のかたまりをグループ化
    ma = String(a).match(/([0-9]+|[^0-9]+)/g);
    mb = String(b).match(/([0-9]+|[^0-9]+)/g);
    if (!ma && !mb) return 0;
    if (!ma && mb) return -1;
    if (!mb && ma) return 1;
    for (var i = 0; i < ma.length; i++) {
        if (!mb[i]) return 1;
        if (!isNaN(ma[i]) && !isNaN(mb[i])) {
            // 両方数値化できれば数値で比較
            if (parseInt(ma[i]) != parseInt(mb[i])) {
                return parseInt(ma[i]) - parseInt(mb[i]);
            }
        } else {
            // それ以外は文字列で比較
            if (ma[i] > mb[i]) {
                return 1;
            } else if (ma[i] < mb[i]) {
                return -1;
            }
        }
    }
    if (ma.length < b.length) return -1;
    return 0;
}

追記:更に良いコード

@vf8974 さんありがとうございます!

私の書いた先程の記載だと以下の問題が生じます。

  • どうしても数値比較なので、桁数があふれるとオーバーフローを起こす。

sort_num_block の関数を以下の様に書き換えることにより改善できます。

JavaScript
function sort_num_block(a, b) {
    const sa = String(a).replace(/(\d+)/g, m => m.padStart(30, '0'));
    const sb = String(b).replace(/(\d+)/g, m => m.padStart(30, '0'));
    return sa < sb ? -1 : sa > sb ? 1 : 0;
}

数字の個所を想定範囲以上にゼロ埋めして文字列としてソートする
分かりやすい例として(コード上は30桁を想定していますが、例として10桁埋めとします。)

Test12-3-456
Test1-234-56

Test0000000012-0000000003-0000000456
Test0000000001-0000000234-0000000056

とすると、文字列でソートしても問題なくソートできるという仕組みです。

9
12
3

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
9
12