วิธีการเรียงลำดับสตริงใน JavaScript


344

ฉันมีรายการของวัตถุที่ฉันต้องการเรียงลำดับตามฟิลด์attrของสตริงประเภท ฉันพยายามใช้-

list.sort(function (a, b) {
    return a.attr - b.attr
})

แต่พบว่า-ดูเหมือนจะไม่ทำงานกับสตริงใน JavaScript ฉันจะเรียงลำดับรายการของวัตถุตามแอตทริบิวต์ด้วยสตริงชนิดได้อย่างไร


1
ดูJavaScript case insensitive string comparisonที่stackoverflow.com/questions/2140627/…
Adrien

สำหรับวิธีการแก้ปัญหา "สากล" อย่างรวดเร็ว (เพียงบางส่วนที่ฉันเดาเพราะนี่อาจไม่ครอบคลุมการเน้นเสียงทั้งหมดในโลก) คุณอาจต้องการเพียงแค่ละเว้นการเน้นเสียงนั่นคือเอาพวกเขาออก จากนั้นทำการเปรียบเทียบสตริงของคุณดูJavascript : remove accents/diacritics in stringsที่stackoverflow.com/questions/990904/…
Adrien เป็น

2
พอสนุกสนาน Jeff Atwood เขาเขียนบล็อกโพสต์เกี่ยวกับเรื่องนี้กลับมาปัญหาที่พบบ่อยในปี 2007 ดูblog.codinghorror.com/sorting-for-humans-natural-sort-order
Adrien เป็น

คำตอบ:


621

ใช้String.prototype.localeCompareตัวอย่างของคุณ:

list.sort(function (a, b) {
    return ('' + a.attr).localeCompare(b.attr);
})

เราบังคับให้ a.attr เป็นสตริงเพื่อหลีกเลี่ยงข้อยกเว้น localeCompareได้รับการสนับสนุนตั้งแต่ Internet Explorer 6และ Firefox 1 คุณอาจเห็นรหัสต่อไปนี้ใช้ซึ่งไม่เกี่ยวข้องกับภาษา:

if (item1.attr < item2.attr)
  return -1;
if ( item1.attr > item2.attr)
  return 1;
return 0;

81
ก่อนที่ทุกคนจะทำผิดอย่างเร่งด่วนเช่นเดียวกับฉันมันเป็นeเปรียบเทียบท้องถิ่นไม่ใช่เปรียบเทียบกับท้องถิ่น
ถึง

12
ทางออกแรกจะพิจารณา "A" เพื่อมาหลัง "z" แต่ก่อนหน้า "Z" เนื่องจากกำลังทำการเปรียบเทียบกับค่าอักขระ ASCII localeCompare()ไม่พบปัญหานี้ แต่ไม่เข้าใจตัวเลขดังนั้นคุณจะได้รับ ["1", "10", "2"] เหมือนกับการเปรียบเทียบการเรียงลำดับในภาษาส่วนใหญ่ หากคุณต้องการเรียงลำดับสำหรับส่วนหน้า UI ของคุณให้ดูอัลกอรึมอัลอัลกอรึมการเรียงลำดับตามธรรมชาติstackoverflow.com/questions/4340227/…หรือstackoverflow.com/questions/4321829/…
Dead.Rabit

2
โปรดทราบว่าlocaleCompare()ได้รับการสนับสนุนในเบราว์เซอร์รุ่นใหม่เท่านั้น: IE11 + ในขณะที่เขียนดูที่developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Adrien Be

3
ไม่ฉันหมายถึงบรรทัดแรกของตาราง @Adrien - IE รองรับการlocaleCompare()ย้อนกลับหลายรุ่น แต่ไม่รองรับการระบุสถานที่จนถึงรุ่นที่ 11 หมายเหตุคำถามที่ Dead.Rabit เชื่อมโยงด้วย
Shog9

3
@ Shog9 ไม่ดีดูเหมือนว่าจะรองรับตั้งแต่ IE6! ดู (เลื่อนลง / ค้นหาเพื่อ localeCompare () วิธีการ) บนmsdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx สิ่งหนึ่งที่ควรทราบในการใช้งานแบบเก่าที่เราไม่ได้ใช้อาร์กิวเมนต์ตำแหน่งและตัวเลือก (ตัวเลือกที่ใช้ก่อนหน้า IE11) สถานที่และลำดับการจัดเรียงที่ใช้นั้นขึ้นอยู่กับการใช้งานทั้งหมดกล่าวคือ Firefox, Safari, Chrome & IE ไม่เรียงลำดับสตริงในลำดับเดียวกัน ดูcode.google.com/p/v8/issues/detail?id=459
Adrien เป็น

166

คำตอบที่อัพเดท (ตุลาคม 2014)

ฉันรำคาญมากเกี่ยวกับการเรียงลำดับตามธรรมชาติของสตริงดังนั้นฉันจึงใช้เวลาพอสมควรในการตรวจสอบปัญหานี้ ฉันหวังว่านี่จะช่วยได้.

เรื่องสั้นสั้น

localeCompare()การสนับสนุนตัวละครเป็น badass เพียงใช้มัน ตามที่ระบุไว้Shog9คำตอบสำหรับคำถามของคุณคือ:

return item1.attr.localeCompare(item2.attr);

ข้อบกพร่องที่พบในการใช้งาน "การเรียงลำดับสตริงธรรมชาติ" จาวาสคริปต์ที่กำหนดเองทั้งหมด

มีการปรับใช้แบบกำหนดเองค่อนข้างมากพยายามเปรียบเทียบสตริงอย่างแม่นยำยิ่งขึ้นเรียกว่า "การเรียงลำดับสตริงธรรมชาติ"

เมื่อ "เล่น" กับการใช้งานเหล่านี้ฉันมักจะสังเกตเห็นตัวเลือก "เรียงลำดับตามธรรมชาติ" ที่แปลกประหลาดหรือค่อนข้างผิดพลาด (หรือละเว้นในกรณีที่ดีที่สุด)

โดยทั่วไปอักขระพิเศษ (ช่องว่างเส้นประเครื่องหมายและเครื่องหมายวงเล็บและอื่น ๆ ) จะไม่ได้รับการประมวลผลอย่างถูกต้อง

จากนั้นคุณจะพบว่าพวกเขาปรากฏตัวปะปนกันในสถานที่ต่าง ๆ ซึ่งโดยทั่วไปอาจเป็น:

  • บางอย่างจะอยู่ระหว่างตัวพิมพ์ใหญ่ 'Z' และตัวพิมพ์เล็ก 'a'
  • บางอย่างจะอยู่ระหว่าง '9' และตัวพิมพ์ใหญ่ 'A'
  • บางตัวจะอยู่หลังตัวพิมพ์เล็ก 'z'

เมื่อใครจะคาดหวังว่าตัวละครพิเศษทั้งหมดจะถูก "จัดกลุ่ม" ไว้ด้วยกันในที่เดียวยกเว้นตัวละครพิเศษที่ว่างอาจจะ (ซึ่งมักจะเป็นตัวอักษรตัวแรก) นั่นคือไม่ว่าจะก่อนหน้าตัวเลขทั้งหมดหรือทั้งหมดระหว่างตัวเลขและตัวอักษร (ตัวพิมพ์เล็กและตัวพิมพ์ใหญ่จะถูก "รวมกัน" ทีละตัว) หรือหลังตัวอักษรทั้งหมด

ข้อสรุปของฉันคือพวกเขาทั้งหมดไม่สามารถให้คำสั่งที่สอดคล้องกันเมื่อฉันเริ่มเพิ่มอักขระที่ไม่ปกติ (เช่นตัวละครที่มีนักกำกับหรือนักแสดงเช่นรีบเครื่องหมายอัศเจรีย์และอื่น ๆ )

การวิจัยเกี่ยวกับการใช้งานที่กำหนดเอง:

การใช้งาน "ลำดับการเรียงสตริงตามธรรมชาติ" ของเบราว์เซอร์ผ่าน localeCompare()

localeCompare()การใช้งานที่เก่าแก่ที่สุด (ไม่มีอาร์กิวเมนต์โลแคลและตัวเลือก) ได้รับการสนับสนุนโดย IE6 + ดูhttp://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (เลื่อนลงไปที่ localeCompare ( ) วิธี). localeCompare()วิธีการในตัวทำงานได้ดีกว่ามากในการเรียงลำดับแม้แต่ตัวละครนานาชาติและตัวละครพิเศษ ปัญหาเดียวที่ใช้localeCompare()วิธีนี้คือ"โลแคลและลำดับการเรียงที่ใช้นั้นขึ้นอยู่กับการใช้งานทั้งหมด" กล่าวอีกนัยหนึ่งเมื่อใช้ localeCompare เช่น stringOne.localeCompare (stringTwo): Firefox, Safari, Chrome & IE มีลำดับการจัดเรียงที่แตกต่างกันสำหรับ Strings

การวิจัยเกี่ยวกับการใช้งานเบราว์เซอร์พื้นเมือง:

ความยากของ "ลำดับการเรียงสตริงตามธรรมชาติ"

การใช้อัลกอริทึมที่เป็นของแข็ง (ความหมาย: สอดคล้องกัน แต่ยังครอบคลุมถึงตัวละครที่หลากหลาย) เป็นงานที่ยากมาก UTF8 มีมากกว่า 2000 ตัวอักษรและครอบคลุมกว่า 120 สคริปต์ (ภาษา) สุดท้ายมีข้อกำหนดบางอย่างสำหรับงานนี้ก็เรียกว่า "เปรียบเทียบ Unicode ขั้นตอนวิธีการ" ซึ่งสามารถพบได้ที่http://www.unicode.org/reports/tr10/ คุณสามารถหาข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนี้ในคำถามนี้ที่ฉันโพสต์/software/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order

ข้อสรุปสุดท้าย

ดังนั้นเมื่อพิจารณาถึงระดับการสนับสนุนในปัจจุบันที่ได้รับจากการใช้งานที่กำหนดเองแบบจาวาสคริปต์ที่เราพบเราอาจจะไม่เห็นอะไรเลยที่จะใกล้เคียงกับการสนับสนุนตัวละครและสคริปต์ (ภาษา) ทั้งหมดนี้ ดังนั้นฉันอยากจะใช้วิธีดั้งเดิม localeCompare () ของเบราว์เซอร์ ใช่มันมีข้อเสียของการผึ้งที่ไม่สอดคล้องกันในเบราว์เซอร์ แต่การทดสอบขั้นพื้นฐานแสดงให้เห็นว่ามันครอบคลุมตัวละครที่หลากหลายมากขึ้นทำให้สามารถเรียงลำดับคำสั่งที่เป็นของแข็งและมีความหมายได้

ดังนั้นตามที่ระบุไว้Shog9คำตอบสำหรับคำถามของคุณคือ:

return item1.attr.localeCompare(item2.attr);

อ่านเพิ่มเติม:

ขอบคุณคำตอบที่ดีของ Shog9 ซึ่งทำให้ฉันไปในทิศทาง "ถูกต้อง" ที่ฉันเชื่อ


38

คำตอบ (ใน Modern ECMAScript)

list.sort((a, b) => (a.attr > b.attr) - (a.attr < b.attr))

หรือ

list.sort((a, b) => +(a.attr > b.attr) || -(a.attr < b.attr))

ลักษณะ

การส่งค่าบูลีนไปยังหมายเลขให้ผลดังนี้:

  • true -> 1
  • false -> 0

พิจารณาสามรูปแบบที่เป็นไปได้:

  • x ใหญ่กว่า y: (x > y) - (y < x)-> 1 - 0->1
  • x เท่ากับ y: (x > y) - (y < x)-> 0 - 0->0
  • x เล็กกว่า y: (x > y) - (y < x)-> 0 - 1->-1

(ทางเลือก)

  • x ใหญ่กว่า y: +(x > y) || -(x < y)-> 1 || 0->1
  • x เท่ากับ y: +(x > y) || -(x < y)-> 0 || 0->0
  • x เล็กกว่า y: +(x > y) || -(x < y)-> 0 || -1->-1

ดังนั้นตรรกะเหล่านี้จึงเทียบเท่ากับฟังก์ชันตัวเปรียบเทียบการเรียงลำดับทั่วไป

if (x == y) {
    return 0;
}
return x > y ? 1 : -1;

1
เมื่อฉันแสดงความคิดเห็นกับคำตอบก่อนหน้านี้ที่ใช้เคล็ดลับนี้คำตอบแบบโค้ดเท่านั้นจะมีประโยชน์มากขึ้นโดยอธิบายว่าพวกเขาทำงานอย่างไร
Dan Dascalescu

เพิ่มคำอธิบาย
mpyw

คุณสามารถแสดงความคิดเห็นว่าสิ่งนี้ดีกว่าหรือแย่กว่า localeCompare หรือไม่?
Ran Lottem

3
@RanLottem localeCompareและการเปรียบเทียบแบบมาตรฐานให้ผลลัพธ์ที่ต่างกัน คุณคาดหวังอะไร ["A", "b", "C", "d"].sort((a, b) => a.localeCompare(b))เรียงลำดับตามลำดับตัวอักษรตามตัวพิมพ์เล็กและใหญ่ในขณะที่["A", "b", "C", "d"].sort((a, b) => (a > b) - (a < b))ทำตามลำดับ codepoint
mpyw

ฉันเห็นว่าน่าจะเป็นจุดยึดหลัก มีความคิดเห็นเกี่ยวกับความแตกต่างด้านประสิทธิภาพหรือไม่
Ran Lottem

13

คุณควรใช้> หรือ <และ == ที่นี่ ดังนั้นทางออกจะเป็น:

list.sort(function(item1, item2) {
    var val1 = item1.attr,
        val2 = item2.attr;
    if (val1 == val2) return 0;
    if (val1 > val2) return 1;
    if (val1 < val2) return -1;
});

1
ในหมายเหตุด้านนี้จะไม่จัดการการเปรียบเทียบสตริงและจำนวน ตัวอย่างเช่น: 'Z' <9 (false), 'Z'> 9 (เช่น false ??), 'Z' == 9 (เช่น false !!) Silly NaN ใน JavaScript ...
Kato


7

เนื่องจากสตริงสามารถเปรียบเทียบได้โดยตรงใน javascript สิ่งนี้จะทำงาน

list.sort(function (a, b) {
    return a.attr > b.attr ? 1: -1;
})

การลบในฟังก์ชั่นการจัดเรียงจะใช้เฉพาะเมื่อต้องการเรียงลำดับที่ไม่ใช่ตัวอักษร (ตัวเลข) และแน่นอนว่ามันจะไม่ทำงานกับสตริง


6

ฉันได้รับการใส่ใจเกี่ยวกับเรื่องนี้มานานดังนั้นในที่สุดฉันก็วิจัยเรื่องนี้และให้เหตุผลที่ยืดเยื้อกับคุณว่าทำไมสิ่งต่าง ๆ ถึงเป็นแบบนี้

จากสเป็ค :

Section 11.9.4   The Strict Equals Operator ( === )

The production EqualityExpression : EqualityExpression === RelationalExpression
is evaluated as follows: 
- Let lref be the result of evaluating EqualityExpression.
- Let lval be GetValue(lref).
- Let rref be the result of evaluating RelationalExpression.
- Let rval be GetValue(rref).
- Return the result of performing the strict equality comparison 
  rval === lval. (See 11.9.6)

ดังนั้นตอนนี้เราไปที่ 11.9.6

11.9.6   The Strict Equality Comparison Algorithm

The comparison x === y, where x and y are values, produces true or false. 
Such a comparison is performed as follows: 
- If Type(x) is different from Type(y), return false.
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
- If Type(x) is Number, then
...
- If Type(x) is String, then return true if x and y are exactly the 
  same sequence of characters (same length and same characters in 
  corresponding positions); otherwise, return false.

แค่นั้นแหละ. ตัวดำเนินการเท่ากับสามที่ใช้กับสตริงจะส่งกลับค่าจริงถ้าอาร์กิวเมนต์เป็นสตริงเดียวกัน (ความยาวเท่ากันและอักขระเดียวกันในตำแหน่งที่สอดคล้องกัน)

ดังนั้น===จะทำงานในกรณีที่เรากำลังพยายามเปรียบเทียบสตริงที่อาจมาจากแหล่งที่แตกต่างกัน แต่ในที่สุดเราก็รู้ว่าจะมีค่าเหมือนกัน - เป็นสถานการณ์ที่พบได้ทั่วไปมากพอสำหรับอินไลน์สตริงในรหัสของเรา ตัวอย่างเช่นถ้าเรามีชื่อตัวแปรconnection_stateและเราต้องการที่จะรู้ว่าที่หนึ่งของรัฐต่อไปนี้มันมีอยู่ในตอนนี้เราสามารถใช้โดยตรง['connecting', 'connected', 'disconnecting', 'disconnected']===

แต่ยังมีอีกมากมาย เหนือระดับ 11.9.4 มีข้อความสั้น ๆ :

NOTE 4     
  Comparison of Strings uses a simple equality test on sequences of code 
  unit values. There is no attempt to use the more complex, semantically oriented
  definitions of character or string equality and collating order defined in the 
  Unicode specification. Therefore Strings values that are canonically equal
  according to the Unicode standard could test as unequal. In effect this 
  algorithm assumes that both Strings are already in normalized form.

อืมมม เกิดอะไรขึ้น สตริงที่ได้มาจากภายนอกสามารถและเป็นไปได้ที่จะเป็น unicodey ที่แปลกที่สุดและความอ่อนโยนของเรา===จะไม่ทำให้พวกเขามีความยุติธรรม ในlocaleCompareการช่วยเหลือ:

15.5.4.9   String.prototype.localeCompare (that)
    ...
    The actual return values are implementation-defined to permit implementers 
    to encode additional information in the value, but the function is required 
    to define a total ordering on all Strings and to return 0 when comparing
    Strings that are considered canonically equivalent by the Unicode standard. 

เราสามารถกลับบ้านได้แล้ว

TL; DR;

ในการเปรียบเทียบสตริงใน javascript ให้ใช้localeCompare; หากคุณรู้ว่าสตริงนั้นไม่มีองค์ประกอบที่ไม่ใช่ ASCII เพราะตัวอย่างเช่นค่าคงที่ของโปรแกรมภายในจะ===ทำงานได้เช่นกัน


0

ในการดำเนินการของคุณในคำถามเริ่มต้นคุณกำลังดำเนินการดังต่อไปนี้:

item1.attr - item2.attr

ดังนั้นสมมติว่าเป็นตัวเลข (เช่น item1.attr = "1", item2.attr = "2") คุณยังสามารถใช้โอเปอเรเตอร์ "===" (หรือผู้ประเมินอื่น ๆ ที่เข้มงวด) หากคุณแน่ใจว่าพิมพ์ ต่อไปนี้ควรทำงาน:

return parseInt(item1.attr) - parseInt(item2.attr);

หากเป็นตัวอักษรและตัวเลขให้ใช้ localCompare ()


0
list.sort(function(item1, item2){
    return +(item1.attr > item2.attr) || +(item1.attr === item2.attr) - 1;
}) 

พวกเขาทำงานอย่างไรตัวอย่าง:

+('aaa'>'bbb')||+('aaa'==='bbb')-1
+(false)||+(false)-1
0||0-1
-1

+('bbb'>'aaa')||+('bbb'==='aaa')-1
+(true)||+(false)-1
1||0-1
1

+('aaa'>'aaa')||+('aaa'==='aaa')-1
+(false)||+(true)-1
0||1-1
0

3
คำตอบที่ใช้รหัสเท่านั้นจะมีประโยชน์มากขึ้นโดยอธิบายว่าพวกเขาทำงานอย่างไร
Dan Dascalescu

-2
<!doctype html>
<html>
<body>
<p id = "myString">zyxtspqnmdba</p>
<p id = "orderedString"></p>
<script>
var myString = document.getElementById("myString").innerHTML;
orderString(myString);
function orderString(str) {
    var i = 0;
    var myArray = str.split("");
    while (i < str.length){
        var j = i + 1;
        while (j < str.length) {
            if (myArray[j] < myArray[i]){
                var temp = myArray[i];
                myArray[i] = myArray[j];
                myArray[j] = temp;
            }
            j++;
        }
        i++;
    }
    var newString = myArray.join("");
    document.getElementById("orderedString").innerHTML = newString;
}
</script>
</body>
</html>

1
โปรดเพิ่มข้อมูลข่าวสารบางส่วนเกี่ยวกับวิธีที่จะแก้ปัญหาให้กับคำตอบของคุณ ไม่ได้รับคำตอบสำหรับรหัสเท่านั้น ขอบคุณ.
wayneOS

ที่นี่คุณต้องการสั่งซื้ออักขระภายในสตริงซึ่งไม่ใช่สิ่งที่ถูกถาม คุณสามารถบรรลุการจัดเรียงนี้เพียงแค่ใช้ "Array.sort" เช่น str.split (""). sort () .join ("")
Alejadro Xalabarder

-2
var str = ['v','a','da','c','k','l']
var b = str.join('').split('').sort().reverse().join('')
console.log(b)

ในขณะที่รหัสนี้อาจแก้ปัญหารวมถึงคำอธิบายว่าทำไมและวิธีแก้ปัญหานี้จะช่วยปรับปรุงคุณภาพการโพสต์ของคุณได้อย่างไรและอาจส่งผลให้คะแนนมากขึ้น จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตไม่ใช่เพียงแค่คนที่ถามตอนนี้ โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้
เดฟ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.