JavaScript:自然な種類の英数字の文字列


173

私は、数値とテキスト、およびこれらの組み合わせで構成される配列をソートする最も簡単な方法を探しています。

例えば

'123asd'
'19asd'
'12345asd'
'asd123'
'asd12'

になる

'19asd'
'123asd'
'12345asd'
'asd12'
'asd123'

これは、私がここで尋ねた別の質問の解決策と組み合わせて使用​​されます

並べ替え機能自体が機能します。必要なのは、「19asd」が「123asd」よりも小さいと言える関数です。

これをJavaScriptで書いています。

編集:adormituが指摘したように、私が探しているのは自然ソートの機能です


また、参照のHow do you do string comparison in JavaScript?stackoverflow.com/questions/51165/...
エイドリアンはして

1
元の質問は2010年に行われたので、驚くことではありません:)
ptrn '23 / 07/23

回答:


314

これは、localeCompareを使用する最新のブラウザーで可能になりました。numeric: trueオプションを渡すことで、数字をスマートに認識します。を使用すると、大文字と小文字を区別することができますsensitivity: 'base'。Chrome、Firefox、IE11でテスト済み。

ここに例があります。これはを返します1。つまり、10は2の後に続きます。

'10'.localeCompare('2', undefined, {numeric: true, sensitivity: 'base'})

大量の文字列を並べ替えるときのパフォーマンスについては、記事では次のように述べています。

大きな配列の並べ替えなど、多数の文字列を比較する場合は、Intl.Collat​​orオブジェクトを作成し、そのcompareプロパティによって提供される関数を使用することをお勧めします。ドキュメントリンク

var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
var myArray = ['1_Document', '11_Document', '2_Document'];
console.log(myArray.sort(collator.compare));


12
オブジェクトの配列を並べ替える場合は、次のCollat​​orも使用できます。codepen.io
pen

2
上記のコメントを明確にするには、「locales引数が指定されていないか未定義の場合、ランタイムのデフォルトロケールが使用されます。」
gkiely

46

自然な種類が必要ですか?

もしそうなら、たぶん、David koelleの作品に基づいたBrian Huismanによるこのスクリプトが必要です。

Brian Huismanのソリューションは、David Koelleのブログで直接ホストされているようです。


正しい、自然な並べ替えが私が探しているものです。送信したリンクを確認します。ありがとう
ptrn

それは非常に不自然な種類です。アルファベットのソートは生成しません。
tchrist

@tchrist:「アルファベット順でソートされない」とはどういう意味ですか?
Adrien Be

正常に動作しますが、負の数は正しく処理されません。つまり、['-1'が生成されます。「-2」、「0」、「1」、「2」]。
adrianboimvaser 2014

2
@mhitzaこのコードは良い仕事をしているようですgithub.com/litejs/natural-compare-liteクイックテストを参照してくださいjsbin.com/bevututodavi/1/edit?js,console
Adrien Be

23

値を比較するには、比較方法を使用できます-

function naturalSorter(as, bs){
    var a, b, a1, b1, i= 0, n, L,
    rx=/(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.\D+)|(\.$)/g;
    if(as=== bs) return 0;
    a= as.toLowerCase().match(rx);
    b= bs.toLowerCase().match(rx);
    L= a.length;
    while(i<L){
        if(!b[i]) return 1;
        a1= a[i],
        b1= b[i++];
        if(a1!== b1){
            n= a1-b1;
            if(!isNaN(n)) return n;
            return a1>b1? 1:-1;
        }
    }
    return b[i]? -1:0;
}

ただし、配列の並べ替えの速度を上げるには、並べ替えの前に配列をリグしてください。そうすれば、並べ替えの各ステップではなく、小文字の変換と正規表現を一度だけ実行する必要があります。

function naturalSort(ar, index){
    var L= ar.length, i, who, next, 
    isi= typeof index== 'number', 
    rx=  /(\.\d+)|(\d+(\.\d+)?)|([^\d.]+)|(\.(\D+|$))/g;
    function nSort(aa, bb){
        var a= aa[0], b= bb[0], a1, b1, i= 0, n, L= a.length;
        while(i<L){
            if(!b[i]) return 1;
            a1= a[i];
            b1= b[i++];
            if(a1!== b1){
                n= a1-b1;
                if(!isNaN(n)) return n;
                return a1>b1? 1: -1;
            }
        }
        return b[i]!= undefined? -1: 0;
    }
    for(i= 0; i<L; i++){
        who= ar[i];
        next= isi? ar[i][index] || '': who;
        ar[i]= [String(next).toLowerCase().match(rx), who];
    }
    ar.sort(nSort);
    for(i= 0; i<L; i++){
        ar[i]= ar[i][1];
    }
}

私の場合、これはうまくいきますか?内側の配列が外側の配列の順序を決定していますか?
ptrn

なにString.prototype.tlc()?これはあなた自身のコードですか、それともどこから入手したのですか?後者の場合は、ページにリンクしてください。
アンディE

間違い訂正して申し訳ありませんが、よろしくお願いします。a [1]とb [1]でソートを制御する場合は、a = String(a [1])。toLowerCase();を使用します。b = String(b [1])。toLowerCase();
ケネベック2010年

Chrome Dev Toolsコンソールで簡単に実行できるはずであると考えて、ソートしたいデータのリストがありました-関数に感謝します!
ajh158 2013

9

オブジェクトの配列がある場合は、次のようにすることができます。

myArrayObjects = myArrayObjects.sort(function(a, b) {
  return a.name.localeCompare(b.name, undefined, {
    numeric: true,
    sensitivity: 'base'
  });
});


1
完璧な答えです!ありがとうございました。
hubert17

5

2019年の時点でこれを処理するための最も完全に機能するライブラリはnatural-orderbyのようです。

const { orderBy } = require('natural-orderby')

const unordered = [
  '123asd',
  '19asd',
  '12345asd',
  'asd123',
  'asd12'
]

const ordered = orderBy(unordered)

// [ '19asd',
//   '123asd',
//   '12345asd',
//   'asd12',
//   'asd123' ]

文字列の配列だけでなく、オブジェクトの配列内の特定のキーの値で並べ替えることもできます。また、通貨、日付、通貨、その他多数の文字列を自動的に識別して並べ替えることもできます。

驚いたことに、gzipで圧縮すると1.6kBしかありません。


2

次の変換を行う8桁のパディング関数を想像してください。

  • '123asd'-> '00000123asd'
  • '19asd'-> '00000019asd'

埋め込み文字列を使用して、「19asd」を「123asd」の前に表示されるように並べ替えることができます。

正規表現/\d+/gを使用して、パディングが必要なすべての数値を見つけやすくします。

str.replace(/\d+/g, pad)

以下は、この手法を使用したソートを示しています。

var list = [
    '123asd',
    '19asd',
    '12345asd',
    'asd123',
    'asd12'
];

function pad(n) { return ("00000000" + n).substr(-8); }
function natural_expand(a) { return a.replace(/\d+/g, pad) };
function natural_compare(a, b) {
    return natural_expand(a).localeCompare(natural_expand(b));
}

console.log(list.map(natural_expand).sort()); // intermediate values
console.log(list.sort(natural_compare)); // result

中間結果は、natural_expand()ルーチンの機能を示し、後続のnatural_compareルーチンがどのように機能するかを理解します。

[
  "00000019asd",
  "00000123asd",
  "00012345asd",
  "asd00000012",
  "asd00000123"
]

出力:

[
  "19asd",
  "123asd",
  "12345asd",
  "asd12",
  "asd123"
]

1

上記の@Adrien Beの回答に基づいて構築し、Brian HuismanDavid koelleが作成したコードを使用して、オブジェクトの配列のソートされた変更されたプロトタイプを次に示します。

//Usage: unsortedArrayOfObjects.alphaNumObjectSort("name");
//Test Case: var unsortedArrayOfObjects = [{name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a10"}, {name: "a5"}, {name: "a13"}, {name: "a20"}, {name: "a8"}, {name: "8b7uaf5q11"}];
//Sorted: [{name: "8b7uaf5q11"}, {name: "a1"}, {name: "a2"}, {name: "a3"}, {name: "a5"}, {name: "a8"}, {name: "a10"}, {name: "a13"}, {name: "a20"}]

// **Sorts in place**
Array.prototype.alphaNumObjectSort = function(attribute, caseInsensitive) {
  for (var z = 0, t; t = this[z]; z++) {
    this[z].sortArray = new Array();
    var x = 0, y = -1, n = 0, i, j;

    while (i = (j = t[attribute].charAt(x++)).charCodeAt(0)) {
      var m = (i == 46 || (i >=48 && i <= 57));
      if (m !== n) {
        this[z].sortArray[++y] = "";
        n = m;
      }
      this[z].sortArray[y] += j;
    }
  }

  this.sort(function(a, b) {
    for (var x = 0, aa, bb; (aa = a.sortArray[x]) && (bb = b.sortArray[x]); x++) {
      if (caseInsensitive) {
        aa = aa.toLowerCase();
        bb = bb.toLowerCase();
      }
      if (aa !== bb) {
        var c = Number(aa), d = Number(bb);
        if (c == aa && d == bb) {
          return c - d;
        } else {
          return (aa > bb) ? 1 : -1;
        }
      }
    }

    return a.sortArray.length - b.sortArray.length;
  });

  for (var z = 0; z < this.length; z++) {
    // Here we're deleting the unused "sortArray" instead of joining the string parts
    delete this[z]["sortArray"];
  }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.