Javascript: semacam alami string alfanumerik


173

Saya mencari cara termudah untuk mengurutkan array yang terdiri dari angka dan teks, dan kombinasi dari semuanya.

Misalnya

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

berubah menjadi

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

Ini akan digunakan dalam kombinasi dengan solusi untuk pertanyaan lain yang saya tanyakan di sini .

Fungsi sortir itu sendiri berfungsi, yang saya butuhkan adalah fungsi yang dapat mengatakan bahwa '19asd' lebih kecil dari '123asd'.

Saya menulis ini dalam JavaScript.

Sunting: seperti yang ditunjukkan adormitu , yang saya cari adalah fungsi untuk penyortiran alami


lihat juga How do you do string comparison in JavaScript?di stackoverflow.com/questions/51165/...
Adrien Be

1
Pertanyaan asli ditanyakan pada tahun 2010, jadi tidak akan mengherankan :)
ptrn

Jawaban:


317

Ini sekarang dimungkinkan di peramban modern menggunakan localeCompare. Dengan melewati numeric: trueopsi, itu akan dengan cerdas mengenali angka. Anda dapat menggunakan case-insensitive sensitivity: 'base'. Diuji di Chrome, Firefox, dan IE11.

Ini sebuah contoh. Itu kembali 1, artinya 10 pergi setelah 2:

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

Untuk kinerja saat menyortir sejumlah besar string, artikel itu mengatakan:

Ketika membandingkan sejumlah besar string, seperti dalam menyortir array besar, lebih baik untuk membuat objek Intl.Collator dan menggunakan fungsi yang disediakan oleh properti pembandingnya. Tautan dokumen

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
Jika Anda ingin mengurutkan array objek, Anda juga dapat menggunakan Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky

2
Untuk mengklarifikasi komentar di atas: "Jika argumen locales tidak disediakan atau tidak terdefinisi, lokal default runtime digunakan."
gkiely

46

Jadi, Anda perlu jenis alami ?

Jika demikian, maka mungkin naskah ini karya Brian Huisman berdasarkan karya David koelle akan menjadi yang Anda butuhkan.

Sepertinya solusi Brian Huisman sekarang di-host langsung di blog David Koelle:


Jenis yang benar dan alami adalah yang saya cari. Saya akan melihat ke tautan yang Anda kirim, terima kasih
ptrn

Itu jenis yang sangat tidak wajar. Itu tidak menghasilkan semacam abjad.
tchrist

@tchrist: apa yang Anda maksud dengan "itu tidak menghasilkan semacam alfabet?"
Adrien Be

Ini berfungsi dengan baik tetapi tidak menangani angka negatif dengan benar. Yaitu: itu akan menghasilkan ['-1'. '-2', '0', '1', '2'].
adrianboimvaser

2
@mhitza kode ini tampaknya melakukan pekerjaan yang baik github.com/litejs/natural-compare-lite lihat tes cepat jsbin.com/bevututodavi/1/edit?js,console
Adrien Be

23

Untuk membandingkan nilai, Anda dapat menggunakan metode pembanding-

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;
}

Tetapi untuk kecepatan dalam mengurutkan array, rig array sebelum menyortir, jadi Anda hanya perlu melakukan konversi huruf kecil dan ekspresi reguler sekali daripada setiap langkah melalui pengurutan.

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];
    }
}

Apakah ini akan bekerja dalam kasus saya, dengan array batin menentukan urutan yang luar?
ptrn

Apa String.prototype.tlc()? Apakah ini kode Anda sendiri atau Anda mendapatkannya dari suatu tempat? Jika yang terakhir, harap tautkan ke halaman.
Andy E

maaf tentang kesalahan yang diperbaiki, terima kasih. Jika Anda ingin [1] dan b [1] mengontrol jenisnya, gunakan a = String (a [1]). ToLowerCase (); b = String (b [1]). toLowerCase ();
kennebec

Saya baru saja memiliki daftar data yang ingin saya urutkan, pikir itu seharusnya mudah dilakukan di konsol Alat Dev Chrome - terima kasih untuk fungsinya!
ajh158

9

Jika Anda memiliki array objek yang dapat Anda lakukan seperti ini:

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


1
Jawaban sempurna! Terima kasih.
hubert17

5

Perpustakaan yang berfitur lengkap untuk menangani hal ini pada tahun 2019 tampaknya merupakan peraturan alami .

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

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

const ordered = orderBy(unordered)

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

Itu tidak hanya mengambil array string, tetapi juga dapat mengurutkan berdasarkan nilai kunci tertentu dalam array objek. Itu juga dapat secara otomatis mengidentifikasi dan mengurutkan string: mata uang, tanggal, mata uang, dan banyak hal lainnya.

Anehnya, itu juga hanya 1,6 kB saat di-gzip.


2

Bayangkan fungsi padding 8 digit yang mengubah:

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

Kita dapat menggunakan string berlapis untuk membantu kita mengurutkan '19asd' untuk muncul sebelum '123asd'.

Gunakan ekspresi reguler /\d+/guntuk membantu menemukan semua angka yang perlu diisi:

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

Berikut ini menunjukkan penyortiran menggunakan teknik ini:

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

Hasil antara menunjukkan apa yang dilakukan rutinitas natural_expand () dan memberi Anda pemahaman tentang bagaimana rutinitas natural_compare selanjutnya akan bekerja:

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

Output:

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

1

Membangun jawaban @Adrien Be di atas dan menggunakan kode yang dibuat Brian Huisman & David koelle , berikut ini adalah prototipe pengurutan yang dimodifikasi untuk berbagai objek:

//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"];
  }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.