使用 JavaScript reduce 函数对数组进行排序

Posted

技术标签:

【中文标题】使用 JavaScript reduce 函数对数组进行排序【英文标题】:Sorting Array with JavaScript reduce function 【发布时间】:2018-10-19 03:13:09 【问题描述】:

我经常研究一些javascript的面试问题,突然看到一个关于reduce函数对Array进行排序的问题,我在MDN中看到了它的用法,在一些medium中看到了它的用法文章,但是对Array 进行排序是如此创新:

const arr = [91,4,6,24,8,7,59,3,13,0,11,98,54,23,52,87,4];

我想了很多,但我不知道如何回答这个问题,reducecall back函数必须是什么? reduceinitialValue 函数是什么?而reducecall back函数的accumulatorcurrentValue又是什么?

最后,这种方式是否比其他排序算法有一些好处?或者改进其他算法有用吗?

【问题讨论】:

【参考方案1】:

这里使用reduce是没有意义的,但是你可以使用一个新数组作为累加器并对所有元素进行插入排序:

array.reduce((sorted, el) => 
  let index = 0;
  while(index < sorted.length && el < sorted[index]) index++;
  sorted.splice(index, 0, el);
  return sorted;
, []);

这是没有 reduce的版本:

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

现在有一些编写 reducer 的一般技巧:

reduce回调函数应该怎么做?

你要么采用累加器的方法,然后reducer应该根据当前元素对累加器应用修改并返回它:

(acc, el) => acc

或者如果累加器和元素具有健全的类型并且逻辑上相等,则不需要区分它们:

 (a, b) => a + b

reduce函数的initialValue是什么?

你应该问自己“当它应用于空数组时应该减少什么?”

现在最重要的是:什么时候使用reduce? (海事组织)

如果您想将数组的值归结为单个值或对象

【讨论】:

【参考方案2】:

一个糟糕的排序算法可以用 ES6 写成一行:

const sorter = (xs, x) => xs.slice(-1)[0] >= x ? [x, ...xs] : [...xs, x];

如果当前元素大于或等于先前排序列表的最后一个元素,则将其附加到末尾,否则附加到开头。

[3,4,1].reduce(sorter,[]).reduce(sorter,[])

//returns [1,3,4]

除了最简单的数组之外,它还需要多个应用程序才能对任何内容进行排序。

但它最终会到达那里。这需要递归!

const arr = [91,4,6,24,8,7,59,3,13,0,11,98,54,23,52,87,4];

const sorter2 =(as) => as.reduce(
  (xs, x) => x >= xs.slice(-1)[0] ? [...xs, x] 
    : xs[0] < x ? sorter2([x, ...xs])
    : [x, ...xs],
  [],
);

const result = sorter2(arr);

console.log(result.join(', '));

当当前值大于已处理数组的最后一个值时,将其追加。如果它小于第一个元素,则将其放在前面。仅当它既不在当前值之前也不在之后,并且累积的数组通过递归调用再次排序。该方法应该等同于插入排序(请评论!)。

【讨论】:

【参考方案3】:

Array.sort 改变数组,使用Array.reduce 鼓励纯函数。您可以在排序之前克隆数组。

我相信这个问题旨在通过强制执行约束让您以不同的方式思考。它测试您对reduce 工作原理的了解,并且答案表明有很多方法可以给猫剥皮。它会在解决这个问题时显示出你个人的 js 风格。

我选择使用Array.findIndexArray.splice

const sortingReducer = (accumulator, value) => 
  const nextIndex = accumulator.findIndex(i => value < i );
  const index = nextIndex > -1 ? nextIndex : accumulator.length;
  accumulator.splice(index, 0, value);
  return accumulator;


const input = [5,4,9,1];
const output = input.reduce(sortingReducer, []);

使用样本输入进行测试会产生

arr.reduce(sortingReducer, [])
// (17) [0, 3, 4, 4, 6, 7, 8, 11, 13, 23, 24, 52, 54, 59, 87, 91, 98]

【讨论】:

【参考方案4】:

您可以使用插入排序:

let array        = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4];
let countIfLess  = (array,v)=> array.reduce((c,n)=>n<v?c+1:c,0);
let countIfEqual = (array,v)=> array.reduce((c,n)=>n==v?c+1:c,0);

console.log(
  array.reduce(
    (a,v,i,array)=>( a[countIfLess(array,v) + countIfEqual(a,v)]=v, a ),
    new Array(array.length)
  )
);

这将创建一次目标数组,然后在 reduce 的每个步骤中执行插入,而无需重新创建目标数组。

有更有效的方法来实现countIfEqual,但我选择使用reduce而不是其他数组函数来实现所有函数。

【讨论】:

【参考方案5】:

如果没有提供数组,您可以使用一些函数来获取数组,一个函数通过获取两个参数返回一个排序数组,以及一个排序回调,其中包括通过使用另一个reduce方法来获取a的部分结果排序数组。

const
    getArray = a => Array.isArray(a) ? a : [a],
    order = (a, b) => a < b ? [a, b] : [b, a],
    sort = (a, b) => getArray(a).reduce((r, v) => r.concat(order(r.pop(), v)), [b]),
    array = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4];

console.log(array.reduce(sort));
.as-console-wrapper  max-height: 100% !important; top: 0; 

【讨论】:

【参考方案6】:

这是一个(imo)更优雅的Jonas W's 插入排序解决方案版本。回调只是构建了一个包含所有较低值、新值和所有较高值的​​新数组。避免使用显式循环或索引,因此更容易一眼看出它是否正常工作。

const insertValue = (arr, value) =>
  [...arr.filter(n => n <= value), value, ...arr.filter(n => n > value)]

const testArr = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4]

console.log(testArr.reduce(insertValue, []))

【讨论】:

可能的问题:数组中的任何NaN 条目都将被删除,因为NaN 既不是&lt;= value 也不是&gt; value。显然,OP 的示例中没有NaN,而是一个特殊情况,只是指出来。 @T.J.Crowder 谢谢,不知道。我不打算修改代码,但我猜n &lt;= value || Number.isNaN(n) 会处理它。或者将n &lt;= value 替换为!(n &gt; value) 以确保没有特殊情况可以破坏它。【参考方案7】:

reduce 将自己限制在在线排序算法中,其中数组中的每个元素只被看到一次,而您现在不需要提前知道数组的长度(请注意,使用闭包可以提供更多减少功能的信息,但这种方式违背了问题的目的)。

插入排序是一个明显而简单的例子;我不会详细说明实现,因为其他答案在这方面已经非常好。但是,您可以提及一些可能对面试官非常积极的优化:

使用二分查找查找插入点可以将插入步骤的复杂度从 O(n) 降低到 O(log n)。这称为二进制插入排序:比较的总数将从 O(n^2) 变为 O(n log n)。这不会更快,因为真正的成本是由于“拼接”输出数组,但如果您进行昂贵的比较(例如,对长字符串进行排序),它可能会有所作为。

如果对整数进行排序,可以使用radix sort,并使用reduce实现线性在线排序算法。这实现起来相当棘手,但非常适合减少。

【讨论】:

【参考方案8】:

这里是sorting 一个使用reduce函数降序排列的数组示例。

reduce函数的initialValue是什么

在下面这个函数中,初始值是[],它在reduce函数中作为thisArg传递。

 array.reduce(function(acc,curr,currIndex,array)
        //rest of the code here
        ,[]//this is initial value also known as thisArg)

所以基本上传递了一个空数组,元素将被推送到这个数组中

这里的累加器是空数组。

const arr = [91, 4, 6, 24, 8, 7, 59, 3, 13, 0, 11, 98, 54, 23, 52, 87, 4];
var m = arr.reduce(function(acc, cur) 
  // this arrVar will have the initial array
  let arrVar = arr;
  // get the max element from the array using Math.max
  // ... is spread operator
  var getMaxElem = Math.max(...arrVar);
  // in the accumulator we are pushing the max value
  acc.push(getMaxElem);
  // now need to remove the max value from the array, so that next time it 
  // shouldn't not be considered
  // splice will return a new array
  // now the arrVar is a new array and it does not contain the current
  // max value
  arrVar = arrVar.splice(arrVar.indexOf(getMaxElem), 1, '')
  return acc;
, []);
console.log(m)

【讨论】:

asc 命令应该做哪些更改? @brk @Andres 非常简单!就做m.reverse()【参考方案9】:

虽然,reduce 并不适合排序。以下解决方案就像forEach 或任何尝试使用Array.reduce 函数实现的循环函数。

var arr = [91,4,6,24,8,7,59,3,13,0,11,98,54,23,52,87,4];

arr = arr.reduce(function(acc,val)
  
  if(acc.length) 
    var temp = [];
    while(acc[acc.length -1] > val) 
      temp.push(acc.pop());
     
    acc.push(val);
    while(temp.length) 
      acc.push(temp.pop());
    
   else 
    acc.push(val);  
  
  return acc;
  
, []);

console.log(arr);

请注意,您可以使用原生函数Array.sort 进行排序,也可以拥有自己的自定义排序函数,您可以在其中定义自己的排序算法。

【讨论】:

以上是关于使用 JavaScript reduce 函数对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 reduce 函数 javascript 对对象进行分组

map/reduce函数式编程培训

在JavaScript函数式编程里使用Map和Reduce方法

我正在尝试实现一个函数,该函数接受数组输入并给出数组的乘积,在 Javascript 中跳过 0 但我只能使用 .reduce

Javascript中内建函数reduce的应用详解

JavaScript中函数式编程的体现--map()函数和reduce()函数arr.map(pow);都使用回调函数