按降序对数字进行排序,但开头应为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
是否保证没有负数?
Michael-Clay Shirky在哪里,

2
如果将最后一行切换到,是否不能解决return x - y;
Mooing Duck

2
用JavaScript计算和删除零,对其余元素进行正常排序是否有效?(没有自定义比较器,因此希望JS引擎可以使用内置的数字排序功能)。然后在结果前加上正确的零个数。如果有很多零,则在排序之前将其删除会产生较小的问题。还是一次通过将零交换到数组的开头,然后对数组的尾部进行排序?
彼得·科德斯

2
这甚至能做到return y - x;吗?即使在javascript中,我也想不出既不是===0也不是!==0
乔治T

Answers:


35

您可以按band 的增量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如果两者都为零ab则比较函数将返回。这可能是不希望的行为。
dan04 '19

20
Array.prototype.sort如果比较器返回NaN,结果是由实现定义的,因此该比较器是个坏主意。它试图变得聪明,并弄错了。
user2357112支持Monica19

3
如果数组中的元素是Infinity怎么办?比较功能比较的无限数目为0时返回0
马可

4
谢谢你们。我采用可以自行减去的最大数字,并希望此数字不会在op的数组中使用。
Nina Scholz

4
绝对不可读。很酷,但是不可读。
Salman A

24

正如mdn docs所说:

如果a和b是要比较的两个元素,则:

如果compareFunction(a, b)返回小于0,则排序a到小于的索引b(即a排在前)。

如果compareFunction(a, b)返回0,则使a和b相对不变,但对所有不同元素进行排序。注意:ECMAscript标准不保证此行为,因此,并非所有浏览器(例如,至少可追溯到2003年的Mozilla版本)都遵守此规定。

如果compareFunction(a, b) 返回的值大于0,则排序b到小于a的索引(即b第一个出现)。

compareFunction(a, b)给定一对特定的元素ab作为其两个参数时,必须始终返回相同的值。如果返回不一致的结果,则排序顺序不确定。

因此,比较功能具有以下形式:

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支持Monica19

2
@ user2357112supportsMonica不过,OP尚未指定负数的期望行为,并且对于此原型,调整负数的行为以适应OP的任何要求都是微不足道的。这个答案的好处是它是惯用的,并且使用标准的比较功能来完成这项工作。
J ...

13

如果您关心效率,那么首先过滤掉零点可能是最快的。您sort甚至不想看着它们就浪费时间,更不用说在比较回调中添加额外的工作来处理这种特殊情况了。

特别是如果您期望有大量的零,那么将数据传递一次以将其过滤掉要比做一个较大的O(N log N)排序好得多,后者会多次查看每个零。

完成后,您可以有效地在正确的数字前面加上零。

阅读结果代码也一样容易。我使用TypedArray是因为它高效并且使数字排序变得容易。但是,您可以使用(a,b)=>a-bfor 的标准用法将这种技术与常规Array结合使用.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()来获取同一基础ArrayBuffer的非零元素的新TypedArray视图。排序将使您的整个数组的开头为零,结尾为已排序,而排序仅查看非零元素。

我没有在Array或TypedArray方法中看到分区函数,但是我几乎不了解JavaScript。有了良好的JIT,循环就不会比内置方法差太多。(特别是当该方法涉及到像这样的回调时.filter,除非它realloc在后台使用来缩小,否则它必须在实际过滤之前弄清楚要分配多少内存)。

.filter() 转换为TypedArray 之前,我使用了regular-Array 。如果您的输入已经是TypedArray,那么您就不会有此问题,并且这种策略会变得更具吸引力。


这可能更简单和/或更惯用,或者至少更紧凑。如果JS引擎设法消除/优化了很多复制或零初始化操作,或者使用循环代替TypedArray方法(例如,向后复制而不是反向+ .set)将有助于IDK,则使用IDK。我没有去做速度测试。如果有人想这样做,我会很感兴趣。
彼得·科德斯

根据@Salman的测试,对于较小的数组(约1000个元素,其中10%为0),此答案的执行效果类似于废话。大概所有新阵列的创建都会带来伤害。还是将TypedArray与常规混合在一起?如我的答案的第二部分所建议的,将TypedArray内的就地分区分为全零和非零+子数组排序可能会好得多。
彼得·科德斯

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
如果任何输入可能为负,则这不是一致的比较器;根据你的比较器,其中1排序前0排序之前-1,各种前0
ILMARI Karonen

使用负输入进行更新
Harunur Ra​​shid

@SalmanA,如果两个均为零,则条件!a仍然为真。它将返回-1
Harunur Ra​​shid

我认为那没什么大不了的,a和b都为零@SalmanA
Harunur Ra​​shid

1
确切地说,我编辑了答案。刚刚添加了一个条件a=b=0
Harunur Ra​​shid

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。Float64是一个很好的默认设置,因为它可以处理所有正常的JS数字。


@IlmariKaronen更好吗?
JollyJoker

您不需要过滤两次;如我的答案所示,您可以过滤一次,然后减去长度以获得的数字Array(n).fill(0)
彼得·科德斯

@PeterCordes尽管它可能更简单,但我认为代码足够长,
以致

是的,效率需要为tmp var多加1条线。我的答案使用了多行额外的代码,因为我已经习惯了C和C ++以及汇编语言,并且拥有更多单独的代码行使得在优化/分析时更容易将编译器生成的asm追溯到对其负责的语句。另外,我通常不使用JS,因此我不需要将其全部塞入几行。但是您可以这样做let f64arr = new Float64Array(arr.filter(n => n != 0)),然后[ ...Array(arr.length - f64arr.length).fill(0),...因此它增加了1条额外的行并简化了最后一行。
彼得·科德斯

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 Ra​​shid答案相同的负输入一致性问题。第二个一致的,但是将所有负数都视为零,使它们的相互排序顺序不确定。
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);

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.