ขีดล่าง: sortBy () ตามแอตทริบิวต์หลายรายการ


115

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

var patients = [
             [{name: 'John', roomNumber: 1, bedNumber: 1}],
             [{name: 'Lisa', roomNumber: 1, bedNumber: 2}],
             [{name: 'Chris', roomNumber: 2, bedNumber: 1}],
             [{name: 'Omar', roomNumber: 3, bedNumber: 1}]
               ];

การเรียงลำดับตามroomNumberแอตทริบิวต์ฉันจะใช้รหัสต่อไปนี้:

var sortedArray = _.sortBy(patients, function(patient) {
    return patient[0].roomNumber;
});

วิธีนี้ใช้งานได้ดี แต่ฉันจะดำเนินการอย่างไรเพื่อให้ 'John' และ 'Lisa' เรียงลำดับอย่างถูกต้อง?

คำตอบ:


250

sortBy บอกว่าเป็นอัลกอริทึมการจัดเรียงที่เสถียรดังนั้นคุณควรจะสามารถจัดเรียงตามคุณสมบัติที่สองของคุณก่อนจากนั้นจัดเรียงอีกครั้งตามคุณสมบัติแรกของคุณดังนี้:

var sortedArray = _(patients).chain().sortBy(function(patient) {
    return patient[0].name;
}).sortBy(function(patient) {
    return patient[0].roomNumber;
}).value();

เมื่อวินาทีsortByพบว่าจอห์นและลิซ่ามีหมายเลขห้องเดียวกันก็จะเก็บไว้ตามลำดับที่พบซึ่งsortByชุดแรกคือ"ลิซ่าจอห์น"


12
มีบล็อกโพสต์ที่ขยายความเกี่ยวกับเรื่องนี้และมีข้อมูลที่ดีเกี่ยวกับการเรียงลำดับคุณสมบัติจากน้อยไปมากและจากมากไปหาน้อย
Alex C

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

1
คุณแน่ใจว่าผู้ป่วย [0] .name และผู้ป่วย [1] .roomNumber ควรมีดัชนีอยู่ที่นั่น? ผู้ป่วยไม่ใช่อาร์เรย์ ...
StinkyCat

[0]ดัชนีถูกต้องเนื่องจากในตัวอย่างเดิมpatientsเป็นอาร์เรย์ของอาร์เรย์ นี่คือสาเหตุที่ "วิธีแก้ปัญหาที่ง่ายกว่า" ในบล็อกโพสต์ที่กล่าวถึงในความคิดเห็นอื่นไม่สามารถใช้งานได้ที่นี่
Rory MacLeod

1
@ac_fire นี่คือที่เก็บถาวรของลิงค์ที่ตายแล้วตอนนี้: archive.is/tiatQ
lustig

52

นี่คือเคล็ดลับแฮ็คที่ฉันใช้ในบางครั้งในกรณีเหล่านี้: รวมคุณสมบัติในลักษณะที่ผลลัพธ์จะจัดเรียงได้:

var sortedArray = _.sortBy(patients, function(patient) {
  return [patient[0].roomNumber, patient[0].name].join("_");
});

อย่างไรก็ตามอย่างที่ฉันบอกว่ามันค่อนข้างแฮ็ค ในการดำเนินการนี้อย่างถูกต้องคุณอาจต้องการใช้เมธอดJavaScript หลักsort :

patients.sort(function(x, y) {
  var roomX = x[0].roomNumber;
  var roomY = y[0].roomNumber;
  if (roomX !== roomY) {
    return compare(roomX, roomY);
  }
  return compare(x[0].name, y[0].name);
});

// General comparison function for convenience
function compare(x, y) {
  if (x === y) {
    return 0;
  }
  return x > y ? 1 : -1;
}

แน่นอนสิ่งนี้จะจัดเรียงอาร์เรย์ของคุณให้เข้าที่ หากคุณต้องการคัดลอกแบบเรียงลำดับ (ต้องการ_.sortByให้คุณ) โคลนอาร์เรย์ก่อน:

function sortOutOfPlace(sequence, sorter) {
  var copy = _.clone(sequence);
  copy.sort(sorter);
  return copy;
}

ด้วยความเบื่อหน่ายฉันเพิ่งเขียนวิธีแก้ปัญหาทั่วไป (เพื่อจัดเรียงตามจำนวนคีย์ใด ๆ ก็ตาม) สำหรับสิ่งนี้เช่นกัน: ดูสิ


ขอบคุณมากสำหรับโซลูชันนี้ลงเอยด้วยการใช้อันที่สองเนื่องจากแอตทริบิวต์ของฉันอาจเป็นได้ทั้งสตริงและตัวเลข ดูเหมือนจะไม่มีวิธีง่ายๆในการจัดเรียงอาร์เรย์?
Christian R

3
ทำไมไม่return [patient[0].roomNumber, patient[0].name];เพียงพอถ้าไม่มีjoin?
Csaba Toth

1
ดูเหมือนว่าลิงก์ไปยังโซลูชันทั่วไปของคุณจะเสีย (หรือบางทีฉันไม่สามารถเข้าถึงได้ผ่านพร็อกซีเซิร์ฟเวอร์ของเรา) คุณช่วยโพสต์ที่นี่ได้ไหม
Zev Spitz

นอกจากนี้วิธีการที่ไม่compareค่าการจัดการที่ไม่ได้ค่าดั้งเดิม - undefined, nullหรือวัตถุธรรมดา?
Zev Spitz

FYI แฮ็คนี้จะใช้ได้ก็ต่อเมื่อคุณมั่นใจว่าความยาว str ของแต่ละค่าเท่ากันสำหรับรายการทั้งหมดในอาร์เรย์
miex

32

ฉันรู้ว่าฉันไปงานปาร์ตี้ช้า แต่ฉันต้องการเพิ่มสิ่งนี้สำหรับผู้ที่ต้องการโซลูชันที่สะอาดและรวดเร็วที่ผู้ที่แนะนำไปแล้ว คุณสามารถเชื่อมโยงการเรียก sortBy ตามลำดับคุณสมบัติที่สำคัญน้อยที่สุดไปยังคุณสมบัติที่สำคัญที่สุด ในโค้ดข้างล่างนี้ฉันจะสร้างแถวใหม่ของผู้ป่วยเรียงตามชื่อภายในRoomNumberจาก array เดิมเรียกว่าผู้ป่วย

var sortedPatients = _.chain(patients)
  .sortBy('Name')
  .sortBy('RoomNumber')
  .value();

4
แม้ว่าคุณจะมาช้า แต่ก็ยังถูกต้อง :) ขอบคุณ!
Allan Jikamu

3
สวยสะอาดมาก
Jason Turan

11

btw การเริ่มต้นของคุณสำหรับผู้ป่วยเป็นเรื่องแปลกใช่ไหม ทำไมคุณไม่เริ่มต้นตัวแปรนี้เนื่องจากนี่คืออาร์เรย์ของวัตถุที่แท้จริง - คุณสามารถทำได้โดยใช้ _.flatten ()ไม่ใช่อาร์เรย์อาร์เรย์ของออบเจ็กต์เดียวอาจเป็นปัญหาการพิมพ์ผิด):

var patients = [
        {name: 'Omar', roomNumber: 3, bedNumber: 1},
        {name: 'John', roomNumber: 1, bedNumber: 1},
        {name: 'Chris', roomNumber: 2, bedNumber: 1},
        {name: 'Lisa', roomNumber: 1, bedNumber: 2},
        {name: 'Kiko', roomNumber: 1, bedNumber: 2}
        ];

ฉันจัดเรียงรายการต่างออกไปและเพิ่ม Kiko เข้าไปในเตียงของ Lisa เพื่อความสนุกสนานและดูว่าจะมีการเปลี่ยนแปลงอะไรบ้าง ...

var sorted = _(patients).sortBy( 
                    function(patient){
                       return [patient.roomNumber, patient.bedNumber, patient.name];
                    });

ตรวจสอบการจัดเรียงแล้วคุณจะเห็นสิ่งนี้

[
{bedNumber: 1, name: "John", roomNumber: 1}, 
{bedNumber: 2, name: "Kiko", roomNumber: 1}, 
{bedNumber: 2, name: "Lisa", roomNumber: 1}, 
{bedNumber: 1, name: "Chris", roomNumber: 2}, 
{bedNumber: 1, name: "Omar", roomNumber: 3}
]

ดังนั้นคำตอบของฉันคือ: ใช้อาร์เรย์ในฟังก์ชันการโทรกลับของคุณ ซึ่งค่อนข้างคล้ายกับคำตอบของDan Taoฉันแค่ลืมการเข้าร่วม (อาจเป็นเพราะฉันลบอาร์เรย์อาร์เรย์ของรายการที่ไม่ซ้ำกัน :))
โดยใช้โครงสร้างข้อมูลของคุณแล้ว อยากจะเป็น :

var sorted = _(patients).chain()
                        .flatten()
                        .sortBy( function(patient){
                              return [patient.roomNumber, 
                                     patient.bedNumber, 
                                     patient.name];
                        })
                        .value();

และการทดสอบจะน่าสนใจ ...


อย่างจริงจังนั่นคือคำตอบ
ราเด็

7

ไม่มีคำตอบใดที่เหมาะเป็นวิธีการทั่วไปสำหรับการใช้หลายช่องในการจัดเรียง วิธีการทั้งหมดข้างต้นไม่มีประสิทธิภาพเนื่องจากต้องเรียงลำดับอาร์เรย์หลาย ๆ ครั้ง (ซึ่งในรายการที่ใหญ่พออาจทำให้สิ่งต่างๆช้าลงได้มาก) หรือสร้างวัตถุขยะจำนวนมากที่ VM จะต้องล้างข้อมูล (และในที่สุดก็ช้าลง โปรแกรมลง).

นี่คือโซลูชันที่รวดเร็วมีประสิทธิภาพช่วยให้สามารถจัดเรียงย้อนกลับได้ง่ายและสามารถใช้ร่วมกับunderscoreหรือlodashหรือโดยตรงกับArray.sort

ส่วนที่สำคัญที่สุดคือcompositeComparatorเมธอดซึ่งใช้อาร์เรย์ของฟังก์ชันตัวเปรียบเทียบและส่งคืนฟังก์ชันตัวเปรียบเทียบคอมโพสิตใหม่

/**
 * Chains a comparator function to another comparator
 * and returns the result of the first comparator, unless
 * the first comparator returns 0, in which case the
 * result of the second comparator is used.
 */
function makeChainedComparator(first, next) {
  return function(a, b) {
    var result = first(a, b);
    if (result !== 0) return result;
    return next(a, b);
  }
}

/**
 * Given an array of comparators, returns a new comparator with
 * descending priority such that
 * the next comparator will only be used if the precending on returned
 * 0 (ie, found the two objects to be equal)
 *
 * Allows multiple sorts to be used simply. For example,
 * sort by column a, then sort by column b, then sort by column c
 */
function compositeComparator(comparators) {
  return comparators.reduceRight(function(memo, comparator) {
    return makeChainedComparator(comparator, memo);
  });
}

คุณจะต้องมีฟังก์ชันเปรียบเทียบเพื่อเปรียบเทียบเขตข้อมูลที่คุณต้องการจัดเรียง naturalSortฟังก์ชั่นจะสร้างเปรียบเทียบให้สนามโดยเฉพาะอย่างยิ่ง การเขียนตัวเปรียบเทียบสำหรับการเรียงลำดับย้อนกลับก็เป็นเรื่องเล็กน้อยเช่นกัน

function naturalSort(field) {
  return function(a, b) {
    var c1 = a[field];
    var c2 = b[field];
    if (c1 > c2) return 1;
    if (c1 < c2) return -1;
    return 0;
  }
}

(รหัสทั้งหมดจนถึงตอนนี้สามารถใช้ซ้ำได้และสามารถเก็บไว้ในโมดูลยูทิลิตี้เป็นต้น)

ถัดไปคุณต้องสร้างตัวเปรียบเทียบคอมโพสิต สำหรับตัวอย่างของเราจะมีลักษณะดังนี้:

var cmp = compositeComparator([naturalSort('roomNumber'), naturalSort('name')]);

โดยจะเรียงตามหมายเลขห้องแล้วตามด้วยชื่อ การเพิ่มเกณฑ์การจัดเรียงเพิ่มเติมเป็นเรื่องเล็กน้อยและไม่มีผลต่อประสิทธิภาพของการจัดเรียง

var patients = [
 {name: 'John', roomNumber: 3, bedNumber: 1},
 {name: 'Omar', roomNumber: 2, bedNumber: 1},
 {name: 'Lisa', roomNumber: 2, bedNumber: 2},
 {name: 'Chris', roomNumber: 1, bedNumber: 1},
];

// Sort using the composite
patients.sort(cmp);

console.log(patients);

ส่งคืนสิ่งต่อไปนี้

[ { name: 'Chris', roomNumber: 1, bedNumber: 1 },
  { name: 'Lisa', roomNumber: 2, bedNumber: 2 },
  { name: 'Omar', roomNumber: 2, bedNumber: 1 },
  { name: 'John', roomNumber: 3, bedNumber: 1 } ]

เหตุผลที่ฉันชอบวิธีนี้คือช่วยให้สามารถจัดเรียงได้อย่างรวดเร็วในจำนวนฟิลด์โดยพลการไม่สร้างขยะจำนวนมากหรือทำการต่อสตริงภายในการจัดเรียงและสามารถใช้งานได้อย่างง่ายดายเพื่อให้บางคอลัมน์ถูกจัดเรียงแบบย้อนกลับในขณะที่คอลัมน์ลำดับใช้ natural ประเภท



2

บางที underscore.js หรือแค่ Javascript engine อาจแตกต่างจากตอนที่เขียนคำตอบเหล่านี้ แต่ฉันสามารถแก้ปัญหานี้ได้โดยส่งคืนอาร์เรย์ของคีย์การเรียงลำดับ

var input = [];

for (var i = 0; i < 20; ++i) {
  input.push({
    a: Math.round(100 * Math.random()),
    b: Math.round(3 * Math.random())
  })
}

var output = _.sortBy(input, function(o) {
  return [o.b, o.a];
});

// output is now sorted by b ascending, a ascending

ในการดำเนินการโปรดดูซอนี้: https://jsfiddle.net/mikeular/xenu3u91/


2

เพียงส่งคืนอาร์เรย์ของคุณสมบัติที่คุณต้องการจัดเรียงด้วย:

ไวยากรณ์ ES6

var sortedArray = _.sortBy(patients, patient => [patient[0].name, patient[1].roomNumber])

ไวยากรณ์ ES5

var sortedArray = _.sortBy(patients, function(patient) { 
    return [patient[0].name, patient[1].roomNumber]
})

สิ่งนี้ไม่มีผลข้างเคียงใด ๆ จากการแปลงตัวเลขเป็นสตริง


1

คุณสามารถเชื่อมต่อคุณสมบัติที่คุณต้องการจัดเรียงในตัววนซ้ำ:

return [patient[0].roomNumber,patient[0].name].join('|');

หรือสิ่งที่เทียบเท่า

หมายเหตุ: เนื่องจากคุณกำลังแปลงแอตทริบิวต์ที่เป็นตัวเลข roomNumber เป็นสตริงคุณจะต้องทำอะไรบางอย่างถ้าคุณมีหมายเลขห้อง> 10 มิฉะนั้น 11 จะมาก่อน 2 คุณสามารถใส่เลขศูนย์นำหน้าเพื่อแก้ปัญหาได้เช่น 01 แทนที่จะเป็น 1


1

ฉันคิดว่าคุณควรใช้_.orderByแทนsortBy:

_.orderBy(patients, ['name', 'roomNumber'], ['asc', 'desc'])

4
แน่ใจหรือว่า orderBy อยู่ในเครื่องหมายขีดล่าง? ฉันมองไม่เห็นในเอกสารหรือไฟล์. d.ts ของฉัน
Zachary Dow

1
ไม่มี orderBy ในขีดล่าง
AfroMogli

1
_.orderByใช้งานได้ แต่เป็นวิธีการของไลบรารี lodash ไม่ใช่ขีดล่าง: lodash.com/docs/4.17.4#orderBy lodash ส่วนใหญ่จะเป็นการแทนที่แบบดรอปอินสำหรับขีดล่างดังนั้นจึงอาจเหมาะสมสำหรับ OP
Mike K

0

หากคุณกำลังใช้ Angular คุณสามารถใช้ตัวกรองตัวเลขในไฟล์ html แทนการเพิ่มตัวจัดการ JS หรือ CSS ตัวอย่างเช่น:

  No fractions: <span>{{val | number:0}}</span><br>

ในตัวอย่างนั้นถ้า val = 1234567 จะแสดงเป็น

  No fractions: 1,234,567

ตัวอย่างและคำแนะนำเพิ่มเติมที่: https://docs.angularjs.org/api/ng/filter/number

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