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/…
Adrien Be

1
คำถามเดิมก็ถามว่าในปี 2010 ดังนั้นมันจะไม่เป็นที่น่าแปลกใจ :)
ptrn

คำตอบ:


316

ตอนนี้สามารถทำได้ในเบราว์เซอร์สมัยใหม่ที่ใช้ localeCompare เมื่อผ่านnumeric: trueตัวเลือกมันจะรับรู้ตัวเลขอย่างชาญฉลาด คุณสามารถใช้ตัวพิมพ์เล็กและตัวพิมพ์ใหญ่sensitivity: 'base'ได้ ทดสอบใน Chrome, Firefox และ IE11

นี่คือตัวอย่าง มันกลับมา1ซึ่งหมายความว่า 10 ไปหลังจาก 2:

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

เพื่อประสิทธิภาพเมื่อเรียงลำดับสตริงจำนวนมากบทความพูดว่า:

เมื่อเปรียบเทียบสตริงจำนวนมากเช่นในการเรียงลำดับอาร์เรย์ขนาดใหญ่จะเป็นการดีกว่าถ้าสร้างอ็อบเจกต์ Intl.Collator และใช้ฟังก์ชันที่จัดเตรียมโดยคุณสมบัติการเปรียบเทียบ ลิงก์เอกสาร

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
หากคุณต้องการเรียงลำดับอาเรย์ของวัตถุคุณยังสามารถใช้ Collator: codepen.io/TimPietrusky/pen/rKzoGN
TimPietrusky

2
เพื่อชี้แจงความเห็นด้านบน: "ถ้าอาร์กิวเมนต์ locales ไม่ได้ถูกจัดเตรียมไว้หรือไม่ได้กำหนดไว้ locale เริ่มต้นของ runtime จะถูกใช้"
gkiely

46

ดังนั้นคุณต้องมีความเป็นธรรมชาติใช่ไหม

ถ้าเป็นเช่นนั้นอาจมากกว่าบทนี้โดย Brian Huisman จากงานของ David koelleน่าจะเป็นสิ่งที่คุณต้องการ

ดูเหมือนว่าโซลูชันของ Brian Huisman นั้นโฮสต์โดยตรงบนบล็อกของ David Koelle:


ถูกต้องเรียงตามธรรมชาติคือสิ่งที่ฉันกำลังมองหา ฉันจะดูที่ลิงค์ที่คุณส่งขอบคุณ
ptrn

นั่นเป็นสิ่งที่แปลกประหลาดมาก มันไม่ได้เรียงตามตัวอักษร
tchrist

@tchrist: คุณหมายถึงอะไรโดย "มันไม่ได้เรียงลำดับตามตัวอักษร"
Adrien เป็น

ทำงานได้ดี แต่ไม่สามารถจัดการกับจำนวนลบได้อย่างถูกต้อง Ie: มันจะสร้าง ['-1' '-2', '0', '1', '2']
adrianboimvaser

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()? นี่เป็นรหัสของคุณเองหรือคุณได้มาจากที่อื่นไหม? หากหลังกรุณาลิงค์ไปยังหน้า
Andy E

ขออภัยเกี่ยวกับความผิดพลาด - แก้ไขขอขอบคุณ หากคุณต้องการ [1] และ b [1] เพื่อควบคุมการเรียงลำดับให้ใช้ = String (a [1]) toLowerCase (); b = สตริง (b [1]). toLowerCase ();
kennebec

ฉันเพิ่งมีรายการข้อมูลที่ฉันต้องการเรียงลำดับคิดว่าควรจะทำได้ง่ายในคอนโซล Chrome Dev Tools - ขอบคุณสำหรับฟังก์ชัน!
ajh158

9

หากคุณมีวัตถุมากมายคุณสามารถทำสิ่งนี้ได้:

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


1
คำตอบที่สมบูรณ์แบบ! ขอบคุณ.
hubert17

5

ห้องสมุดมากที่สุดอย่างเต็มที่ที่โดดเด่นในการจัดการนี้เป็นของ 2019 น่าจะเป็นธรรมชาติ orderby

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

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

const ordered = orderBy(unordered)

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

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

น่าแปลกใจก็คือเพียง 1.6kB เมื่อ gzipped


2

ลองนึกภาพฟังก์ชั่น padding 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 Huisman & David 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.