เรียงลำดับตัวเลขจากมากไปหาน้อย แต่ด้วย `0 'ตอนเริ่มต้น


36

ฉันมีความท้าทายใน JavaScript ที่ฉันพยายามคิดอยู่พักหนึ่งแล้ว

พิจารณาอาร์เรย์นี้:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

ฉันต้องแสดงผลลัพธ์นี้:

arr = [0, 0, 0, 0, 0, 5, 4, 3, 2, 1]

ฉันกำลังตามสายของตรรกะนี้เพื่อวางตำแหน่งศูนย์ด้านหน้าปรับค่าดัชนี:

arr.sort((x, y) => {
    if (x !== 0) {
        return 1;
    }

    if (x === 0) {
        return -1;
    }

    return y - x;
});

แต่ฉันติดอยู่กับผลลัพธ์นี้:

arr = [0, 0, 0, 0, 0, 1, 2, 3, 4, 5]

ใครบ้างมีเคล็ดลับในการแก้ปัญหานี้?


6
มันรับประกันว่าจะไม่มีตัวเลขลบหรือไม่?
ไมเคิล - Clay Shirky อยู่ที่ไหน

2
ไม่แก้ไขหากบรรทัดสุดท้ายเปลี่ยนเป็นreturn x - y;?
Mooing Duck

2
JavaScript มีประสิทธิภาพในการนับและลบศูนย์หรือไม่เรียงลำดับองค์ประกอบที่เหลือตามปกติ (ไม่มีตัวเปรียบเทียบที่กำหนดเองดังนั้นหวังว่าเอ็นจิน JS สามารถใช้ฟังก์ชันการเรียงลำดับตัวเลขในตัว) จากนั้นเติมจำนวนศูนย์ที่เหมาะสมกับผลลัพธ์ หากมีศูนย์จำนวนมากการลบออกก่อนการเรียงลำดับจะทำให้เกิดปัญหาเล็กลง หรือสลับค่าศูนย์เป็นจุดเริ่มต้นของอาเรย์ด้วยการผ่านครั้งเดียวจากนั้นจัดเรียงส่วนท้ายของอาเรย์?
Peter Cordes

2
สิ่งนี้เคยไปถึงreturn y - x;หรือไม่? แม้จะอยู่ใน javascript, ฉันไม่สามารถคิดอะไรที่จะเป็นค่ามิได้===0 !==0
George T

2
JSPerf สำหรับคำตอบ: jsperf.com/stackoverflow-question-58933996-v2
Salman A

คำตอบ:


35

คุณสามารถจัดเรียงตามเดลต้าของbและa(สำหรับการเรียงลำดับจากมากไปน้อย) และรับNumber.MAX_VALUEสำหรับค่าที่ผิดพลาดเช่นศูนย์

นี้:

Number.MAX_VALUE - Number.MAX_VALUE

เท่ากับศูนย์

let array = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

array.sort((a, b) => (b || Number.MAX_VALUE) - (a || Number.MAX_VALUE));

console.log(...array);


13
ฟังก์ชั่นการเปรียบเทียบของคุณจะกลับมาNaNถ้าทั้งคู่aและbเป็นศูนย์ นี่อาจเป็นพฤติกรรมที่ไม่พึงประสงค์
dan04

20
ผลลัพธ์ของArray.prototype.sortการใช้งานจะถูกกำหนดหากตัวเปรียบเทียบกลับNaNมาดังนั้นตัวเปรียบเทียบนี้จึงเป็นแนวคิดที่ไม่ดี มันพยายามฉลาดและเข้าใจผิด
user2357112 รองรับ Monica

3
จะเกิดอะไรขึ้นถ้าองค์ประกอบภายในอาร์เรย์เป็นอนันต์ ฟังก์ชันการเปรียบเทียบส่งคืนค่า 0 เมื่อเปรียบเทียบหมายเลขอินฟินิตี้กับ 0
Marco

4
ขอบคุณทุกคน. ฉันใช้จำนวนมากที่สุดซึ่งสามารถ substracted ด้วยตัวเองและหวังว่าหมายเลขนี้ไม่ได้ใช้ในอาร์เรย์ของ op
Nina Scholz

4
ไม่สามารถอ่านได้อย่างแน่นอน เจ๋ง แต่อ่านไม่ได้
Salman

24

ดังที่ mdn docs พูดว่า:

หาก a และ b เป็นสององค์ประกอบที่ถูกเปรียบเทียบดังนั้น:

ถ้าcompareFunction(a, b)ผลตอบแทนน้อยกว่า0ให้เรียงaตามดัชนีที่ต่ำกว่าb(เช่น a มาก่อน)

ถ้า compareFunction(a, b)ส่งคืนค่า 0 ปล่อยให้ a และ b ไม่เปลี่ยนแปลงตามความเคารพซึ่งกันและกัน แต่เรียงลำดับตามส่วนต่าง ๆ ทั้งหมด หมายเหตุ: มาตรฐาน ECMAscript ไม่ได้รับประกันการทำงานนี้ดังนั้นไม่ใช่ทุกเบราว์เซอร์ (เช่นรุ่น Mozilla ย้อนหลังไปจนถึงอย่างน้อยปี 2003) เคารพสิ่งนี้

หากcompareFunction(a, b) ผลตอบแทนมากกว่า 0 เรียงbตามดัชนีที่ต่ำกว่า (เช่นbมาก่อน)

compareFunction(a, b)จะต้องส่งคืนค่าเดิมเสมอเมื่อได้รับองค์ประกอบaและคู่ที่เฉพาะเจาะจงbเป็นอาร์กิวเมนต์สองตัว หากส่งคืนผลลัพธ์ที่ไม่สอดคล้องกันลำดับการเรียงจะไม่ถูกกำหนด

ดังนั้นฟังก์ชั่นเปรียบเทียบมีรูปแบบดังต่อไปนี้:

function compare(a, b) {
  if (a is less than b by some ordering criterion) {
    return -1;
  }
  if (a is greater than b by the ordering criterion) {
    return 1;
  }
  // a must be equal to b
  return 0;
}

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

arr.sort((x, y) => {
    if (x > 0 && y > 0) {
        return y - x;
    }
    return x - y;
});

console.log(arr);


3
+1 สำหรับการให้สิ่งที่ดูเหมือนจะเป็นทางออกเดียวจนถึงกับฟังก์ชั่นการเปรียบเทียบที่สอดคล้องกันจริง ๆ (ตามที่กำหนดไว้ในมาตรฐาน ) สำหรับอินพุตทั้งหมดรวมถึงจำนวนลบ
Ilmari Karonen

3
@IlmariKaronen: น่าเสียดายที่การเปรียบเทียบนั้นสอดคล้องกับจำนวนลบ แต่เป็นการเปรียบเทียบที่ไม่ถูกต้องสำหรับตัวเลขลบ คำหลักเชิงลบจัดเรียงก่อน 0 ด้วยตัวเปรียบเทียบนี้และพวกเขาเรียงลำดับจากน้อยไปมาก
user2357112 รองรับ Monica

2
@ user2357112supportsMonica อย่างไรก็ตาม OP ไม่ได้ระบุพฤติกรรมที่ต้องการสำหรับจำนวนลบและด้วยต้นแบบนี้มันเล็กน้อยที่จะปรับพฤติกรรมของตัวเลขลบให้เหมาะสมกับความต้องการของ OP สิ่งที่ดีเกี่ยวกับคำตอบนี้คือมันเป็นสำนวนและใช้ฟังก์ชั่นการเปรียบเทียบแบบมาตรฐานในการทำงาน
เจ ...

13

หากคุณสนใจเกี่ยวกับประสิทธิภาพก็อาจจะเร็วที่สุดเพื่อกรองศูนย์แรก คุณไม่ต้องการที่sortจะเสียเวลาแม้แต่จะมองพวกเขาให้เพิ่มงานพิเศษลงในการเรียกกลับการเปรียบเทียบของคุณเพื่อจัดการกรณีพิเศษนั้น

โดยเฉพาะอย่างยิ่งถ้าคุณคาดหวังว่ามีเลขศูนย์จำนวนมากหนึ่งข้อมูลที่ผ่านการกรองข้อมูลจะดีกว่าการจัดเรียง O (N log N) ขนาดใหญ่กว่าที่จะดูแต่ละศูนย์หลาย ๆ ครั้ง

คุณสามารถเติมเลขศูนย์ได้อย่างมีประสิทธิภาพหลังจากเสร็จสิ้น

นอกจากนี้ยังง่ายต่อการอ่านรหัสผลลัพธ์ ผมใช้ TypedArray เพราะมันเป็นเรื่องที่มีประสิทธิภาพและทำให้การเรียงลำดับตัวเลขง่าย แต่คุณสามารถใช้เทคนิคนี้กับอาร์เรย์ปกติใช้สำนวนมาตรฐานสำหรับ(a,b)=>a-b.sort

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let nonzero_arr = Int32Array.from(arr.filter(n => n != 0));
let zcount = arr.length - nonzero_arr.length;
nonzero_arr.sort();      // numeric TypedArray sorts numerically, not alphabetically

// Reverse the sorted part before copying into the final array.
nonzero_arr.reverse();

 // efficient-ish TypedArray for main result
let revsorted = new Int32Array(arr.length);   // zero-filled full size
revsorted.set(nonzero_arr, zcount);           // copy after the right number of zeros

console.log(Array.from(revsorted));      // prints strangely for TypedArray, with invented "0", "1" keys

/*
   // regular Array result
let sorted = [...Array(zcount).fill(0), ...nonzero_arr]  // IDK if this is efficient
console.log(sorted);
*/

ฉันไม่รู้ว่า TypedArray .sort()นั้น.reverseเร็วกว่าการใช้ฟังก์ชันการเปรียบเทียบแบบกำหนดเองเพื่อเรียงลำดับจากมากไปน้อย หรือถ้าเราสามารถคัดลอกและย้อนกลับได้ทันทีด้วยตัววนซ้ำ


นอกจากนี้ยังมีมูลค่าการพิจารณา: ใช้เพียงหนึ่ง TypedArray ของความยาวเต็มรูปแบบ

แทนที่จะใช้.filterให้วนรอบมันและสลับค่าศูนย์ไปที่ด้านหน้าของอาร์เรย์ในขณะที่คุณไป การดำเนินการนี้จะส่งผ่านข้อมูลของคุณหนึ่งครั้ง

จากนั้นใช้.subarray()เพื่อรับมุมมอง TypedArray ใหม่ขององค์ประกอบที่ไม่เป็นศูนย์ของ ArrayBuffer ต้นแบบเดียวกัน การเรียงลำดับที่จะทำให้คุณมีอาร์เรย์เต็มโดยเริ่มต้นที่ศูนย์และหางเรียงด้วยการเรียงลำดับที่เคยดูที่องค์ประกอบที่ไม่ใช่ศูนย์เท่านั้น

ฉันไม่เห็นฟังก์ชันพาร์ติชันในวิธี Array หรือ TypedArray แต่ฉันแทบจะไม่รู้จัก JavaScript ด้วย JIT ที่ดีการวนซ้ำไม่ควรแย่ไปกว่าวิธีการในตัว (โดยเฉพาะอย่างยิ่งเมื่อวิธีนั้นเกี่ยวข้องกับการเรียกกลับเช่น.filterและเว้นแต่ว่ามันจะใช้reallocภายใต้ประทุนเพื่อลดขนาด

ฉันใช้อาร์เรย์ปกติ.filter() ก่อนที่จะแปลงเป็น TypedArray หากอินพุตของคุณเป็น TypedArray อยู่แล้วคุณไม่มีปัญหานี้และกลยุทธ์นี้ก็น่าดึงดูดยิ่งขึ้น


นี่อาจเป็นเรื่องง่ายและ / หรือสำนวนมากขึ้นหรืออย่างน้อยกะทัดรัด IDK หากเอ็นจิ้น JS จัดการเพื่อกำจัด / ปรับการคัดลอกจำนวนมากหรือการเริ่มต้นเป็นศูนย์หรือถ้ามันจะช่วยในการใช้ลูปแทนวิธี TypedArray เช่นการคัดลอกไปข้างหลังแทน reverse + .set ฉันไม่ได้ทำการทดสอบความเร็ว ถ้ามีคนต้องการทำอย่างนั้นฉันก็จะสนใจ
Peter Cordes

ตามการทดสอบของ @ Salmanคำตอบนี้ทำหน้าที่เหมือนอึสำหรับอาร์เรย์ขนาดเล็ก (จาก ~ 1,000 องค์ประกอบซึ่ง 10% เป็น 0) สันนิษฐานว่าการสร้างอาร์เรย์ใหม่ทั้งหมดสร้างความเจ็บปวด หรือบางทีการประมาทผสม TypedArray และแบบปกติ? พาร์ติชันในสถานที่ภายใน TypedArray ลงในการเรียงลำดับย่อยแบบ all-zero และ non-zero + อาจจะดีกว่ามากตามที่แนะนำในส่วนที่ 2 ของคำตอบของฉัน
Peter Cordes

8

เพียงแค่ปรับสภาพของฟังก์ชั่นการเปรียบเทียบของคุณเช่นนี้ -

let arr = [-1, 0, 1, 0, 2, -2, 0, 3, -3, 0, 4, -4, 0, 5, -5];
arr.sort((a, b) => {
   if(a && b) return b-a;
   if(!a && !b) return 0;
   return !a ? -1 : 1;
});

console.log(arr);


6
นี่ไม่ใช่ตัวเปรียบเทียบที่สอดคล้องกันหากอินพุตใด ๆ สามารถเป็นลบได้ ตามการเปรียบเทียบของคุณ 0 เรียงก่อนหน้า 1 ซึ่งเรียงลำดับก่อน -1 ซึ่งเรียงลำดับก่อนหน้านี้ 0
Ilmari Karonen

อัปเดตด้วยอินพุตเชิงลบ
Harunur Rashid

@SalmanA หากทั้งคู่เป็นศูนย์เงื่อนไข!aจะยังคงเป็นจริง มันจะกลับมา-1
Harunur Rashid

ฉันไม่คิดว่าเรื่องใหญ่คือ a และ b ทั้งคู่เป็นศูนย์ @SalmanA
Harunur Rashid

1
เพื่อความถูกต้องฉันแก้ไขคำตอบ เพิ่งเพิ่มเงื่อนไขสำหรับa=b=0
Harunur Rashid

6

ไม่เล่นรหัสกอล์ฟที่นี่:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, -1];
arr.sort(function(a, b) {
  if (a === 0 && b !== 0) {
    // a is zero b is nonzero, a goes first
    return -1;
  } else if (a !== 0 && b === 0) {
    // a is nonzero b is zero, b goes first
    return 1;
  } else {
    // both are zero or both are nonzero, sort descending
    return b - a;
  }
});
console.log(arr.toString());


5

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

const zeroSort = arr => [...arr.filter(n => n == 0),
                         ...new Float64Array(arr.filter(n => n != 0)).sort().reverse()];

console.log(zeroSort([0, 1, 0, 2, 0, 3, 0, 4, 0, 500]));

อย่าเขียนโค้ดใด ๆ ที่คุณไม่จำเป็นต้อง; คุณอาจเข้าใจผิด

เลือกTypedArrayตามประเภทของตัวเลขที่คุณต้องการให้ Array จัดการ Float64 เป็นค่าเริ่มต้นที่ดีเนื่องจากรองรับหมายเลข JS ทั้งหมดปกติ


@IlmariKaronen ดีกว่าไหม?
JollyJoker

คุณไม่จำเป็นต้องกรองซ้ำสองครั้ง Array(n).fill(0)เป็นคำตอบที่แสดงของฉันคุณสามารถกรองทันทีและลบความยาวจะได้รับหมายเลขสำหรับ
Peter Cordes

@PeterCordes ถึงแม้ว่ามันอาจจะเรียกได้ง่ายกว่า แต่ฉันคิดว่าโค้ดนั้นยาวพอที่จะอ่านได้ง่ายกว่าน้อยกว่า
JollyJoker

ใช่ประสิทธิภาพจะต้องมี 1 บรรทัดพิเศษสำหรับ tmp var คำตอบของฉันใช้หลายบรรทัดพิเศษเพราะฉันคุ้นเคยกับ C และ C ++ และภาษาแอสเซมบลีและการมีบรรทัดแยกกันมากขึ้นทำให้ง่ายต่อการติดตาม asm ที่สร้างโดยคอมไพเลอร์กลับไปที่คำสั่งที่รับผิดชอบเมื่อทำการปรับ / โปรไฟล์ นอกจากนี้ปกติฉันไม่ได้ทำ JS ดังนั้นฉันจึงไม่รู้สึกว่าจำเป็นต้องยัดมันลงบนเส้นคู่ แต่คุณสามารถทำlet f64arr = new Float64Array(arr.filter(n => n != 0))แล้ว[ ...Array(arr.length - f64arr.length).fill(0),... ดังนั้นมันจะเพิ่มสาย 1 พิเศษและลดความยุ่งยากในบรรทัดสุดท้าย
Peter Cordes

4

คุณสามารถทำสิ่งนี้ได้:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort((a,b) => {
  if(a == 0 || b == 0)
    return a-b;
  return b-a;
})
console.log(result)

หรือคุณสามารถทำสิ่งนี้:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort().sort((a,b) => {
  if(a > 0 && b > 0)
    return b-a
  return 0
})

console.log(result)


4
วิธีแก้ปัญหาแรกของคุณมีปัญหาเรื่องความสอดคล้องเช่นเดียวกันกับอินพุตที่เป็นลบตามคำตอบของ Harunur Rashid ส่วนที่สองนั้นสอดคล้องกัน แต่จะถือว่าจำนวนลบทั้งหมดเท่ากับศูนย์ทำให้ไม่ได้กำหนดลำดับการเรียงซึ่งกันและกัน
Ilmari Karonen

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