【问题标题】:Sorting numbers in descending order but with `0`s at the start按降序对数字进行排序,但以 `0` 开头
【发布时间】:2019-11-19 12:20:22
【问题描述】:

我在 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]

谁有解决这个问题的建议?

【问题讨论】:

  • 能保证没有负数吗?
  • 最后一行切换到return x - y;不解决吗?
  • 在 JavaScript 中计算和删除零、正常排序剩余元素是否有效? (没有自定义比较器,因此希望 JS 引擎可以使用内置的数字排序功能)。然后将正确数量的零添加到结果中。如果有很多零,则在排序之前删除它们会产生较小的问题。还是一次性将零交换到数组的开头,然后对数组的尾部进行排序?
  • 这甚至可以到达return y - x;吗?即使在javascript中,我也想不出任何既不是===0也不是!==0的东西。

标签: javascript arrays sorting


【解决方案1】:

您可以按ba 的增量排序(用于降序排序)并采用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);

【讨论】:

  • 如果ab 都为零,您的比较函数将返回NaN。这可能是不受欢迎的行为。
  • 如果比较器返回NaNArray.prototype.sort 的结果是实现定义的,所以这个比较器是个坏主意。它试图变得聪明,却弄错了。
  • 如果数组中的元素是Infinity怎么办?将 Infinity 数与 0 进行比较时,比较函数返回 0。
  • "当给定一对特定的值 a 和 b 作为其两个参数时,调用 comparefn(a, b) 总是返回相同的值 v。此外,Type(v) 是 Number,而 v 不是南。”
  • 谢谢大家。我取最大可以自己减去的数,希望这个数不要用在op的数组中。
【解决方案2】:

As mdn docs says:

如果a和b是两个被比较的元素,那么:

如果compareFunction(a, b) 返回小于0,则将a 排序到下一个索引 比 b (即 a 先出现)。

如果compareFunction(a, b) 返回 0,则离开 a 和 b 相对于彼此保持不变,但按照尊重排序 到所有不同的元素。注意:ECMAscript 标准没有 保证这种行为,因此,并非所有浏览器(例如 Mozilla 版本 至少可以追溯到 2003 年)尊重这一点。

如果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);

【讨论】:

  • +1 用于提供目前看来唯一的解决方案,其比较函数对于所有输入(包括负数)实际上都是一致的(如 the standard 中所定义)。
  • @IlmariKaronen:不幸的是,负数的比较一致,但负数的比较错误。负数使用此比较器排序在 0 之前,并且按升序排序。
  • @user2357112supportsMonica 然而,OP 并没有为负数指定所需的行为,并且使用这个原型,调整负数的行为以适应 OP 的任何要求是微不足道的。这个答案的好处在于它是惯用的,并且使用标准的比较函数来完成这项工作。
【解决方案3】:

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

特别是如果您预计会有大量的零,则通过一次数据过滤以将它们过滤掉应该比进行多次查看每个零的更大的 O(N log N) 排序要好得多。

您可以在完成后efficiently 在前面加上正确数量的零。

生成的代码也很容易阅读。我使用 TypedArray 是因为它高效且 makes numeric sorting easy。但是您可以将此技术与常规 Array 一起使用,使用 (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() 获取相同底层ArrayBuffer 的非零元素的新TypedArray 视图。排序将使您的整个数组具有零开始和排序的尾,排序只查看非零元素。

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

我在转换为 TypedArray 之前使用了常规数组 .filter()。如果您的输入已经是 TypedArray,您就不会有这个问题,而且这种策略会变得更有吸引力。

【讨论】:

  • 这可能更简单,和/或更惯用,或者至少更紧凑。 IDK 如果 JS 引擎设法消除/优化大量复制或零初始化,或者如果它有助于使用循环而不是 TypedArray 方法,例如向后复制而不是反向 + .set。我没有时间对它进行速度测试。如果有人想这样做,我会很感兴趣。
  • 根据@Salman's testing,这个答案对于小型数组(约 1000 个元素,其中 10% 为 0)表现得像废话。大概所有新数组的创建都会受到伤害。或者可能是 TypedArray 和常规的粗心混合?正如我回答的第二部分所建议的那样,将 TypedArray 内的就地分区划分为全零和非零 + 子数组排序可能会好得多。
【解决方案4】:

只需像这样修改比较函数的条件 -

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

【讨论】:

  • 如果任何输入可以为负,这不是一个一致的比较器;根据您的比较器,0 在 1 之前排序,在 -1 之前排序,在 0 之前排序。
  • 用负输入更新
  • @SalmanA ,如果两者都为零,则条件 !a 仍然为真。它将返回-1
  • 我认为 a 和 b 都为零@SalmanA 这没什么大不了的
  • 确切地说,我编辑了答案。刚刚为a=b=0添加了一个条件
【解决方案5】:

这里不打代码高尔夫:

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

【讨论】:

    【解决方案6】:

    如果已经存在,请不要编写自己的数字排序。你想做的正是你在标题中所说的;除开头的零外,按降序对数字进行排序。

    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 更好吗?
    • 不需要过滤两次;正如我的回答所示,您可以过滤一次并减去长度以获得Array(n).fill(0) 的数字。
    • @PeterCordes 虽然它可能被称为更简单,但我认为代码已经足够长以至于不太容易阅读
    • 是的,效率需要为 tmp 变量增加 1 行。我的答案使用了多行额外的行,因为我习惯于 C 和 C++ 以及汇编语言,并且拥有更多单独的行可以更容易地在优化/分析时将编译器生成的 asm 追溯到负责它的语句。另外,我通常不做 JS,所以我觉得没有必要把它全部塞进几行。但是你可以先let f64arr = new Float64Array(arr.filter(n => n != 0)),然后[ ...Array(arr.length - f64arr.length).fill(0), ...所以它增加了1行并简化了最后一行。
    【解决方案7】:

    你可以这样做:

    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)

    【讨论】:

    • 您的第一个解决方案与 Harunur Ra​​shid 的回答存在相同的负输入一致性问题。第二个一致的,但将所有负数视为等于零,而它们的相互排序顺序未定义。
    【解决方案8】:

    上面的大多数答案都不是按照现实生活场景进行优化的:

    1. 并非总是要过滤掉 0。所以所有使用 Only Sort 功能的答案对我来说都不够好
    2. 输入并不总是整数,而是在排序期间要比较的字符串甚至对象。所以使用 TypedArray 进行排序和反向的答案还不够好

    我更喜欢以下内容

    function customSort( source, target )  { target - source } // a custom sorting depending on business logic required
    
    function sortAndFilter(inputArray, candidateValue) { // a Candidate Value to be filtered out
    
    // step 1: use optimized custom sort (think Merge Sort) to sort all items. You would like to pass candidateValue(s) to be Not considered for sorting and move them to separate list or at beginning/end of the array depending on business logic
    
    const reversedArray = inputArray.sort( customSort);
    
    // step 2: merge the sorted list(s) with custom logic. in this case its just moving the candidateValue to front and removing from elsewhere so following is good enough
    reversedArray.forEach( (item, index) => {
        if (item === candidateValue) {
            reversedArray.splice(index, 1)
            reversedArray.unshift(item)
        }
    });
    return reversedArray
    }
    
    console.log(sortAndFilter(arr,0))
    

    【讨论】:

      【解决方案9】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-12-28
        • 1970-01-01
        • 1970-01-01
        • 2014-02-16
        • 2017-01-09
        • 1970-01-01
        • 2011-03-25
        相关资源
        最近更新 更多