Bagaimana cara melakukan penyortiran case-insensitive dalam JavaScript?

220

Saya memiliki serangkaian string yang harus saya sortir dalam JavaScript, tetapi dengan cara case-insensitive. Bagaimana cara melakukan ini?

Jérôme Verstrynge
sumber

Jawaban:

404

Dalam (hampir :) a-liner

["Foo", "bar"].sort(function (a, b) {
    return a.toLowerCase().localeCompare(b.toLowerCase());
});

Yang mengakibatkan

[ 'bar', 'Foo' ]

Sementara

["Foo", "bar"].sort();

hasil dalam

[ 'Foo', 'bar' ]
Ivan Krechetov
sumber
9
Harap diingat bahwa opsi lanjutan localeCompare belum didukung di semua platform / browser. Saya tahu mereka tidak digunakan dalam contoh ini, tetapi hanya ingin menambah kejelasan. Lihat MDN untuk info lebih lanjut
Ayame__
97
Jika Anda akan melibatkan localeCompare (), Anda hanya bisa menggunakan nya kemampuan untuk kasus-sensitif, misalnya:return a.localeCompare(b, 'en', {'sensitivity': 'base'});
Michael Dyck
2
+1 untuk tidak menelepon toLowerCase()ketika localeComparesudah melakukannya secara default dalam beberapa kasus. Anda dapat membaca lebih lanjut tentang parameter untuk diteruskan ke sini: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Milimetric
3
@Mimimetri sesuai dengan halaman yang direferensikan, fitur itu tidak didukung oleh beberapa browser (mis. IE <11 atau Safari). solusi yang disebutkan di sini sangat baik, tetapi masih akan membutuhkan backporting / polyfill untuk beberapa browser.
3k-
2
Jika Anda memiliki array besar, masuk akal untuk menggunakan items.sort(new Intl.Collator('en').compare)untuk kinerja yang lebih baik. (Lihat MDN .)
valtlai
60
myArray.sort(
  function(a, b) {
    if (a.toLowerCase() < b.toLowerCase()) return -1;
    if (a.toLowerCase() > b.toLowerCase()) return 1;
    return 0;
  }
);

EDIT: Harap dicatat bahwa saya awalnya menulis ini untuk menggambarkan teknik daripada memiliki kinerja dalam pikiran. Lihat juga jawaban @Ivan Krechetov untuk solusi yang lebih ringkas.

ron tornambe
sumber
3
Ini dapat memanggil toLowerCasedua kali pada setiap string; akan lebih efisien untuk menyimpan versi yang lebih rendah dari string dalam variabel.
Yakub
Benar dan terimakasih. Saya menulis ini dengan jelas dalam pikiran, bukan kinerja. Saya kira saya harus mencatat itu.
ron tornambe
1
@ Jacob Agar adil, jawaban yang diterima memiliki masalah dasar yang sama: mungkin dapat memanggil .toLowerCase()beberapa kali untuk setiap item dalam array. Misalnya, 45 panggilan ke fungsi bandingkan saat mengurutkan 10 item dalam urutan terbalik. var i = 0; ["z","y","x","w","v","u","t","s","r","q"].sort(function (a, b) {++i; return a.toLowerCase().localeCompare(b.toLowerCase());}); console.log("Calls to Compare: " + i); // i === 45
nothingisnecessary
47

Inilah saatnya untuk meninjau kembali pertanyaan lama ini.

Anda sebaiknya tidak menggunakan solusi yang diandalkan toLowerCase. Mereka tidak efisien dan tidak berfungsi dalam beberapa bahasa (Turki misalnya). Lebih suka ini:

['Foo', 'bar'].sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))

Periksa dokumentasi untuk kompatibilitas browser dan semua yang perlu diketahui tentang sensitivityopsi ini.

ZunTzu
sumber
1
Hati-hati, ini tidak didukung di semua mesin javascript.
Luboš Turek
26
arr.sort(function(a,b) {
    a = a.toLowerCase();
    b = b.toLowerCase();
    if (a == b) return 0;
    if (a > b) return 1;
    return -1;
});
Niet the Dark Absol
sumber
1
ataureturn a === b ? 0 : a > b ? 1 : -1;
Devin G Rhode
Ini kemungkinan tidak akan berfungsi sebagaimana dimaksudkan untuk string yang mewakili angka. Operator aritmatika akan menggunakan semantik angka alih-alih string. Misal jika sudah ["111", "33"], kita mungkin ingin mengembalikannya ["111", "33"]karena 1 datang sebelum 3 dalam urutan kode karakter. Namun, fungsi dalam jawaban ini akan kembali ["33", "111"]karena angkanya 33kurang dari angkanya 111.
Austin Davis
@AustinDavis "33" > "111" === truedan 33 > 111 === false. Ini berfungsi sebagaimana dimaksud.
Niet the Dark Absol
12

Anda juga dapat menggunakan yang baru Intl.Collator().compare, per MDN itu lebih efisien saat menyortir array. Kelemahannya adalah tidak didukung oleh peramban lama. MDN menyatakan bahwa itu tidak didukung sama sekali di Safari. Perlu memverifikasinya, karena menyatakan yang Intl.Collatordidukung.

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.

["Foo", "bar"].sort(Intl.Collator().compare); //["bar", "Foo"]
mateuscb
sumber
11

Jika Anda ingin menjamin urutan yang sama terlepas dari urutan elemen dalam larik input, berikut adalah penyortiran yang stabil :

myArray.sort(function(a, b) {
    /* Storing case insensitive comparison */
    var comparison = a.toLowerCase().localeCompare(b.toLowerCase());
    /* If strings are equal in case insensitive comparison */
    if (comparison === 0) {
        /* Return case sensitive comparison instead */
        return a.localeCompare(b);
    }
    /* Otherwise return result */
    return comparison;
});
Aalex Gabi
sumber
5

Normalisasi kasus .sort()dengan .toLowerCase().


sumber
4

Anda juga dapat menggunakan operator Elvis:

arr = ['Bob', 'charley', 'fudge', 'Fudge', 'biscuit'];
arr.sort(function(s1, s2){
    var l=s1.toLowerCase(), m=s2.toLowerCase();
    return l===m?0:l>m?1:-1;
});
console.log(arr);

Memberi:

biscuit,Bob,charley,fudge,Fudge

Metode localeCompare mungkin baik-baik saja ...

Catatan: Operator Elvis adalah bentuk pendek 'operator ternary' karena jika demikian, biasanya dengan penugasan.
Jika Anda melihat?: Sideways, sepertinya Elvis ...
yaitu bukannya:

if (y) {
  x = 1;
} else {
  x = 2;
}

kamu bisa memakai:

x = y?1:2;

yaitu ketika y benar, maka kembalikan 1 (untuk penugasan ke x), jika tidak kembalikan 2 (untuk penugasan ke x).

AndyS
sumber
5
Menjadi bertele-tele, ini bukan operator Elvis. Ini hanya operator dasar ternary. Operator Elvis sejati adalah penggabungan nol, misalnya, alih-alih x = y ? y : z, Anda dapat melakukannya x = y ?: z. Javascript tidak memiliki operator Elvis yang sebenarnya, tetapi Anda dapat menggunakannya x = y || zdengan cara yang serupa.
Charles Wood
3

Jawaban lain mengasumsikan bahwa array berisi string. Metode saya lebih baik, karena akan berfungsi walaupun array mengandung null, undefined, atau non-string.

var notdefined;
var myarray = ['a', 'c', null, notdefined, 'nulk', 'BYE', 'nulm'];

myarray.sort(ignoreCase);

alert(JSON.stringify(myarray));    // show the result

function ignoreCase(a,b) {
    return (''+a).toUpperCase() < (''+b).toUpperCase() ? -1 : 1;
}

The nullakan diurutkan antara 'nulk' dan 'nulm'. Tetapi undefinedakan selalu diurutkan terakhir.

John Henckel
sumber
(''+notdefined) === "undefined"jadi itu akan mengurutkan sebelum "z"
MattW
Kurasa aku seharusnya mencari definisi Array.prototype.sort: | karena bagian tentang (''+notdefined) === "undefined" benar - benar benar ... yang berarti jika Anda membalik -1 dan 1 dalam fungsi sortir untuk membalik urutan, undefined masih menyortir sampai akhir. Itu juga perlu dipertimbangkan ketika menggunakan fungsi perbandingan di luar konteks semacam array (seperti ketika saya menemukan pertanyaan ini).
MattW
Dan setelah merenungkan Array.prototype.sortdefinisi itu - beberapa komentar lagi. Pertama, tidak perlu untuk (''+a)- toString()script ECMAS perlu dipanggil pada elemen sebelum meneruskannya ke compareFn. Kedua, fakta yang ignoreCasekembali 1ketika membandingkan string yang sama (termasuk string yang sama tetapi untuk kasus) berarti spesifikasi tidak menentukan hasil jika ada nilai duplikat (mungkin akan baik-baik saja hanya dengan beberapa swap yang tidak perlu terjadi, saya pikir).
MattW
@ MattW, Menurut saya itu undefinedadalah kasus khusus, yang untuk x x <undefined dan x> undefined keduanya salah . Itu undefinedselalu yang terakhir, adalah produk sampingan dari implementasi semacam sort. Saya mencoba mengubah ('' a) menjadi sekadar, tetapi gagal. saya mengerti TypeError: a.toUpperCase is not a function. Ternyata toStringini tidak disebut sebelum menelepon compareFn.
John Henckel
1
Ah, oke, itu masuk akal. Untuk undefinedperbandinganFn tidak pernah disebut
John Henckel
1

Untuk mendukung jawaban yang diterima saya ingin menambahkan bahwa fungsi di bawah ini tampaknya mengubah nilai-nilai dalam array asli untuk diurutkan sehingga tidak hanya akan mengurutkan huruf kecil tetapi nilai huruf besar juga akan diubah menjadi huruf kecil. Ini adalah masalah bagi saya karena meskipun saya ingin melihat Mary di sebelah Maria, saya tidak berharap bahwa kasus nilai pertama Mary diubah menjadi huruf kecil.

myArray.sort(
  function(a, b) {
    if (a.toLowerCase() < b.toLowerCase()) return -1;
    if (a.toLowerCase() > b.toLowerCase()) return 1;
    return 0;
  }
);

Dalam percobaan saya, fungsi berikut dari jawaban yang diterima mengurutkan dengan benar tetapi tidak mengubah nilai.

["Foo", "bar"].sort(function (a, b) {
    return a.toLowerCase().localeCompare(b.toLowerCase());
});
John Shearing
sumber
0

Ini dapat membantu jika Anda kesulitan memahami:

var array = ["sort", "Me", "alphabetically", "But", "Ignore", "case"];
console.log('Unordered array ---', array, '------------');

array.sort(function(a,b) {
    a = a.toLowerCase();
    b = b.toLowerCase();
    console.log("Compare '" + a + "' and '" + b + "'");

    if( a == b) {
        console.log('Comparison result, 0 --- leave as is ');
        return 0;
    }
    if( a > b) {
        console.log('Comparison result, 1 --- move '+b+' to before '+a+' ');
        return 1;
    }
    console.log('Comparison result, -1 --- move '+a+' to before '+b+' ');
    return -1;


});

console.log('Ordered array ---', array, '------------');


// return logic

/***
If compareFunction(a, b) is less than 0, sort a to a lower index than b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a.
***/

http://jsfiddle.net/ianjamieson/wmxn2ram/1/

Ian Jamieson
sumber
0
arr.sort(function(a,b) {
    a = a.toLowerCase();
    b = b.toLowerCase();
    if( a == b) return 0;
    if( a > b) return 1;
    return -1;
});

Dalam fungsi di atas, jika kita hanya membandingkan ketika huruf kecil dua nilai a dan b, kita tidak akan mendapatkan hasil yang cantik.

Contoh, jika array adalah [A, a, B, b, c, C, D, d, e, E] dan kami menggunakan fungsi di atas, kami memiliki persis array itu. Itu tidak mengubah apa pun.

Agar hasilnya adalah [A, a, B, b, C, c, D, d, E, e], kita harus membandingkan lagi ketika dua nilai huruf kecil sama:

function caseInsensitiveComparator(valueA, valueB) {
    var valueALowerCase = valueA.toLowerCase();
    var valueBLowerCase = valueB.toLowerCase();

    if (valueALowerCase < valueBLowerCase) {
        return -1;
    } else if (valueALowerCase > valueBLowerCase) {
        return 1;
    } else { //valueALowerCase === valueBLowerCase
        if (valueA < valueB) {
            return -1;
        } else if (valueA > valueB) {
            return 1;
        } else {
            return 0;
        }
    }
}
Iri
sumber
-1

Saya membungkus jawaban teratas dalam polyfill sehingga saya bisa memanggil .sortIgnoreCase () pada array string

// Array.sortIgnoreCase() polyfill
if (!Array.prototype.sortIgnoreCase) {
    Array.prototype.sortIgnoreCase = function () {
        return this.sort(function (a, b) {
            return a.toLowerCase().localeCompare(b.toLowerCase());
        });
    };
}
Jason
sumber
Tolong jangan pernah melakukan ini. Hanya modifikasi prototipe hal-hal yang Anda miliki. Ini juga bukan polyfill, karena metode Array ini tidak ada dalam spesifikasi ECMAScript.
Joe Maffei
-2

Bungkus string Anda / /i. Ini adalah cara mudah menggunakan regex untuk mengabaikan casing

pengguna3225968
sumber
Pertanyaannya adalah tentang penyortiran, bukan pencocokan.
user4642212