Sắp xếp các số theo thứ tự giảm dần nhưng với `0` khi bắt đầu


36

Tôi có một thách thức trong JavaScript mà tôi đang cố gắng tìm ra trong một thời gian.

Hãy xem xét mảng này:

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

Tôi phải xuất kết quả này:

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

Tôi đang theo dòng logic này để định vị các số 0 ở phía trước, điều chỉnh giá trị chỉ mục:

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

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

    return y - x;
});

Nhưng tôi bị kẹt ở kết quả này:

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

Có ai có bất cứ lời khuyên về cách giải quyết điều này?


6
Có đảm bảo không có số âm?
Michael - Clay Shirky ở đâu

2
Nó không sửa nếu dòng cuối cùng được chuyển sang return x - y;?
Vịt Mooing

2
JavaScript có hiệu quả để đếm và loại bỏ các số không, sắp xếp các phần tử còn lại bình thường không? (không có bộ so sánh tùy chỉnh nên hy vọng công cụ JS có thể sử dụng hàm sắp xếp số tích hợp). Sau đó, chuẩn bị đúng số không cho kết quả. Nếu có nhiều số không, loại bỏ chúng trước khi sắp xếp sẽ tạo ra một vấn đề nhỏ hơn. Hoặc hoán đổi các số không về đầu của mảng bằng một lượt, sau đó sắp xếp đuôi của mảng?
Peter Cordes

2
Điều này thậm chí có bao giờ nhận được return y - x;? Ngay cả trong javascript, tôi không thể nghĩ bất cứ điều gì sẽ không ===0hoặc không !==0.
George T

Câu trả lời:


35

Bạn có thể sắp xếp theo delta ba(để sắp xếp giảm dần) và lấy Number.MAX_VALUE, cho các giá trị sai lệch như 0.

Điều này:

Number.MAX_VALUE - Number.MAX_VALUE

bằng không.

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
Hàm so sánh của bạn sẽ trả về NaNnếu cả hai abbằng không. Đây có thể là hành vi không mong muốn.
dan04

20
Các kết quả Array.prototype.sortđược xác định theo thực hiện nếu bộ so sánh bao giờ trả về NaN, vì vậy bộ so sánh này là một ý tưởng tồi. Nó cố tỏ ra thông minh, và hiểu sai.
user2357112 hỗ trợ Monica

3
Nếu một phần tử bên trong mảng là Infinity thì sao? Hàm so sánh trả về 0 khi so sánh số Vô cực với 0.
Marco

4
cảm ơn tất cả. tôi lấy số lớn nhất có thể được trừ đi và hy vọng số này không được sử dụng trong mảng của op.
Nina Scholz

4
Hoàn toàn không thể đọc được. Mát mẻ, nhưng không thể đọc được.
Salman A

24

Như các tài liệu mdn nói:

Nếu a và b là hai yếu tố được so sánh, thì:

Nếu compareFunction(a, b)trả về ít hơn 0, sắp xếp atheo chỉ mục thấp hơn b(tức là đến trước).

Nếu compareFunction(a, b)trả về 0, không thay đổi a và b đối với nhau, nhưng được sắp xếp theo tất cả các yếu tố khác nhau. Lưu ý: tiêu chuẩn ECMAscript không đảm bảo hành vi này, do đó, không phải tất cả các trình duyệt (ví dụ: các phiên bản Mozilla có từ ít nhất 2003) đều tôn trọng điều này.

Nếu compareFunction(a, b) trả về lớn hơn 0, sắp xếp btheo chỉ mục thấp hơn một (tức là bđến trước).

compareFunction(a, b)phải luôn trả về cùng một giá trị khi được cung cấp một cặp phần tử cụ thể abnhư hai đối số của nó. Nếu kết quả không nhất quán được trả về, thì thứ tự sắp xếp không được xác định.

Vì vậy, hàm so sánh có dạng sau:

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 để đưa ra những gì dường như là giải pháp duy nhất cho đến nay với chức năng so sánh thực sự phù hợp (như được xác định trong tiêu chuẩn ) cho tất cả các đầu vào, bao gồm cả số âm.
Ilmari Karonen

3
@IlmariKaronen: Thật không may, so sánh phù hợp với số âm, nhưng đó là so sánh sai cho số âm. Các tiêu cực sắp xếp trước 0 với bộ so sánh này và chúng sắp xếp theo thứ tự tăng dần.
user2357112 hỗ trợ Monica

2
@ user2357112supportsMonica Tuy nhiên, OP chưa chỉ định hành vi mong muốn cho các số âm và, với nguyên mẫu này, việc điều chỉnh hành vi của các số âm phù hợp với bất kỳ yêu cầu nào của OP là điều không quan trọng. Điều tốt cho câu trả lời này là nó là thành ngữ và sử dụng chức năng so sánh tiêu chuẩn để thực hiện công việc.
J ...

13

Nếu bạn quan tâm đến hiệu quả, có lẽ nhanh nhất để lọc các số không trước . Bạn không muốn sortlãng phí thời gian thậm chí nhìn vào chúng, chứ đừng nói đến việc thêm công việc vào cuộc gọi lại so sánh của bạn để xử lý trường hợp đặc biệt đó.

Đặc biệt, nếu bạn mong đợi một số lượng không đáng kể, một lần chuyển dữ liệu để lọc chúng sẽ tốt hơn nhiều so với thực hiện một loại O (N log N) lớn hơn sẽ nhìn vào mỗi số 0 nhiều lần.

Bạn có thể chuẩn bị một cách hiệu quả số lượng không đúng sau khi hoàn thành.

Nó cũng dễ đọc mã kết quả. Tôi đã sử dụng TypedArray vì nó hiệu quả và giúp việc sắp xếp số dễ dàng . Nhưng bạn có thể sử dụng kỹ thuật này với Array thông thường, sử dụng thành ngữ chuẩn của (a,b)=>a-bfor .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);
*/

Tôi không biết nếu TypedArray .sort()và sau đó .reversenhanh hơn sử dụng chức năng so sánh tùy chỉnh để sắp xếp theo thứ tự giảm dần. Hoặc nếu chúng ta có thể sao chép và đảo ngược khi đang di chuyển bằng một trình vòng lặp.


Cũng đáng xem xét: chỉ sử dụng một TypedArray có độ dài đầy đủ .

Thay vì sử dụng .filter, lặp qua nó và hoán đổi các số không về phía trước của mảng khi bạn đi. Điều này có một vượt qua dữ liệu của bạn.

Sau đó sử dụng .subarray()để có được chế độ xem TypedArray mới về các phần tử khác không của cùng một ArrayBuffer bên dưới. Sắp xếp sẽ để lại cho bạn toàn bộ mảng với bắt đầu bằng 0 và đuôi được sắp xếp, với sắp xếp chỉ nhìn vào các phần tử khác không.

Tôi không thấy hàm phân vùng trong các phương thức Array hoặc TypedArray, nhưng tôi hầu như không biết JavaScript. Với JIT tốt, một vòng lặp không nên quá tệ hơn so với phương thức tích hợp. (Đặc biệt là khi phương thức đó liên quan đến một cuộc gọi lại như thế .filter, và trừ khi nó sử dụng reallocdưới mui xe để thu nhỏ, nó phải tìm ra bao nhiêu bộ nhớ để phân bổ trước khi nó thực sự lọc).

Tôi đã sử dụng Array thường xuyên .filter() trước khi chuyển đổi thành TypedArray. Nếu đầu vào của bạn đã là TypedArray, bạn không gặp phải vấn đề này và chiến lược này thậm chí còn hấp dẫn hơn.


Điều này có thể đơn giản hơn, và / hoặc thành ngữ hơn, hoặc ít nhất là nhỏ gọn hơn. IDK nếu các công cụ JS quản lý để loại bỏ / tối ưu hóa rất nhiều việc sao chép hoặc khởi tạo bằng không hoặc nếu nó sẽ giúp sử dụng các vòng lặp thay vì các phương thức TypedArray, ví dụ như sao chép ngược thay vì đảo ngược + .set. Tôi đã không đi xung quanh để kiểm tra tốc độ; nếu ai đó muốn làm điều đó tôi sẽ quan tâm.
Peter Cordes

Theo thử nghiệm của @ Salman, câu trả lời này thực hiện giống như crap cho các mảng nhỏ (trong số ~ 1000 phần tử, 10% trong số đó là 0). Có lẽ tất cả việc tạo ra các mảng mới gây tổn thương. Hoặc có thể bất cẩn trộn TypedArray và thường xuyên? Phân vùng tại chỗ bên trong TypedArray thành loại phân đoạn hoàn toàn không và không + có thể tốt hơn nhiều, như được đề xuất trong phần 2 của câu trả lời của tôi.
Peter Cordes

8

Chỉ cần sửa đổi điều kiện của chức năng so sánh của bạn như thế này -

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
Đây không phải là một bộ so sánh nhất quán nếu bất kỳ đầu vào nào có thể âm; theo so sánh của bạn, 0 sắp xếp trước 1 sắp xếp trước -1 sắp xếp trước 0.
Ilmari Karonen

Cập nhật với đầu vào tiêu cực
Harunur Rashid

@SalmanA, Nếu cả hai đều bằng 0, điều kiện !avẫn đúng. Nó sẽ trở lại-1
Harunur Rashid

Tôi không nghĩ rằng một vấn đề lớn là cả a và b đều bằng không @SalmanA
Harunur Rashid

1
Để chính xác, tôi chỉnh sửa câu trả lời. Chỉ cần thêm một điều kiện choa=b=0
Harunur Rashid

6

Không chơi golf ở đây:

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

Đừng viết sắp xếp số của riêng bạn nếu nó đã tồn tại. Những gì bạn muốn làm là chính xác những gì bạn nói trong tiêu đề; sắp xếp các số theo thứ tự giảm dần trừ số 0 khi bắt đầu.

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]));

Đừng viết bất kỳ mã nào bạn không cần phải làm; bạn có thể hiểu sai

Chọn TypedArray dựa trên Loại số bạn muốn Mảng xử lý. Float64 là một mặc định tốt vì nó xử lý tất cả các số JS bình thường.


@IlmariKaronen Tốt hơn?
JollyJoker

Bạn không cần phải lọc hai lần; như câu trả lời của tôi cho thấy bạn có thể lọc một lần và trừ độ dài để lấy số choArray(n).fill(0) .
Peter Cordes

@PeterCordes Mặc dù có thể gọi là đơn giản hơn nhưng tôi nghĩ mã này đủ dài để dễ đọc hơn
JollyJoker

Có, hiệu quả sẽ cần thêm 1 dòng cho một tmp var. Câu trả lời của tôi sử dụng nhiều dòng bổ sung vì tôi đã quen với C và C ++ và ngôn ngữ hợp ngữ và việc có nhiều dòng riêng biệt hơn giúp dễ dàng theo dõi asm do trình biên dịch tạo ra trở lại câu lệnh chịu trách nhiệm khi tối ưu hóa / lược tả. Thêm vào đó, tôi thường không làm JS nên tôi không cảm thấy cần phải nhồi nhét tất cả vào một vài dòng. Nhưng bạn có thể làm let f64arr = new Float64Array(arr.filter(n => n != 0)), sau đó[ ...Array(arr.length - f64arr.length).fill(0), ... vì vậy nó thêm 1 dòng bổ sung và đơn giản hóa dòng cuối cùng.
Peter Cordes

4

Bạn có thể làm điều này như thế này:

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)

hoặc bạn có thể làm điều này:

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
Giải pháp đầu tiên của bạn có cùng một vấn đề nhất quán với đầu vào tiêu cực như câu trả lời của Harunur Rashid. Số thứ hai nhất quán, nhưng coi tất cả các số âm bằng 0, không xác định thứ tự sắp xếp lẫn nhau.
Ilmari Karonen

-2

const myArray = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];
const splitArray = myArray.reduce((output, item) => {
     if(!item) output[0].push(item);
    else output[1].push(item);
    return output;
}, [[], []]);
const result = [...splitArray[0], ...splitArray[1].reverse()]
console.log(result);

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.