使用 JavaScript reduce 函数对数组进行排序
Sorting Array with JavaScript reduce function
经常研究一些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];
我想了很多,但我不知道如何回答这个问题,reduce
call back
功能必须如何? reduce
函数的 initialValue
是什么?还有reduce
的call back
函数的accumulator
和currentValue
是什么?
最后,这种方式是否比其他排序算法有一些好处?或者对改进其他算法有用吗?
这里使用 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 的一般技巧:
how must be the reduce call back function?
你要么采用累加器的方法,然后 reducer 应该根据当前元素对累加器应用修改,return 它:
(acc, el) => acc
或者如果accumulator和元素的类型相同并且逻辑上相等,则不需要区分它们:
(a, b) => a + b
what is the initialValue of reduce function?
你应该问问自己“当return应用于空数组时应该减少什么?”
Now the most important: When to use reduce? (IMO)
如果您想将数组的值归结为一个单一的值或对象。
不过,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 进行排序,也可以拥有自己的自定义排序函数,您可以在其中定义自己的排序算法。
这里是 sorting
使用 reduce 函数降序排列数组的示例。
what is the initialValue of reduce function
在下面这个函数中,初始值是 []
,它在 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)
Array.sort
改变数组,其中使用 Array.reduce
鼓励纯函数。您可以在排序之前克隆数组。
我相信这个问题旨在通过强制约束让您以不同的方式思考。它测试您对 reduce
工作原理的了解,正如答案所示,剥猫皮的方法有很多种。它会在解决这个问题时显示你个人的 js 风格。
我选择使用 Array.findIndex
和 Array.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]
如果未提供数组,您可以使用一些函数来获取数组,该函数 returns 通过采用两个参数和排序回调来排序数组,其中包括上述内容,通过使用另一个 reduce 方法获取排序数组的部分结果。
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; }
这是 插入排序解决方案的(imo)更优雅的版本。回调只是构建一个包含所有较低值、新值和所有较高值的新数组。避免使用显式循环或索引,因此更容易一眼看出它是否正常工作。
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, []))
reduce
将自己限制在 在线排序算法 中,数组中的每个元素都被看到一次,您现在不需要预先计算数组的长度(注意使用闭包你可以为减少功能提供更多信息,但这种类型违背了问题的目的)。
插入排序是一个明显而简单的例子;我不会详细说明实现,因为其他答案在这方面已经非常好。但是,您可以提及一些可能被面试官视为非常积极的优化:
使用二分查找查找插入点可以将一个插入步骤的复杂度从O(n)降低到O(log n)。这称为二进制插入排序:比较的总数将从 O(n^2) 变为 O(n log n)。这不会更快,因为实际成本是由于 "splicing" 输出数组,但如果您进行昂贵的比较(例如,对长字符串进行排序),它可能会有所不同。
如果对整数进行排序,可以使用radix sort并使用reduce实现线性在线排序算法。这实现起来相当棘手,但非常适合减少。
您可以使用插入排序:
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
而不是其他数组函数来实现所有函数。
一个可怕的排序算法可以用 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]
它的 return 需要多个应用程序才能对最简单的数组以外的任何内容进行排序。
但它最终会到达那里。这需要递归!
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(', '));
当当前值大于已处理数组的最后一个值时,它被追加。如果它小于第一个元素,它会被添加到前面。仅当当前值和累加数组既不在之前也不在之后时,才通过递归调用再次排序。该方法应该等同于插入排序(请评论!)。
经常研究一些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];
我想了很多,但我不知道如何回答这个问题,reduce
call back
功能必须如何? reduce
函数的 initialValue
是什么?还有reduce
的call back
函数的accumulator
和currentValue
是什么?
最后,这种方式是否比其他排序算法有一些好处?或者对改进其他算法有用吗?
这里使用 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 的一般技巧:
how must be the reduce call back function?
你要么采用累加器的方法,然后 reducer 应该根据当前元素对累加器应用修改,return 它:
(acc, el) => acc
或者如果accumulator和元素的类型相同并且逻辑上相等,则不需要区分它们:
(a, b) => a + b
what is the initialValue of reduce function?
你应该问问自己“当return应用于空数组时应该减少什么?”
Now the most important: When to use reduce? (IMO)
如果您想将数组的值归结为一个单一的值或对象。
不过,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 进行排序,也可以拥有自己的自定义排序函数,您可以在其中定义自己的排序算法。
这里是 sorting
使用 reduce 函数降序排列数组的示例。
what is the initialValue of reduce function
在下面这个函数中,初始值是 []
,它在 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)
Array.sort
改变数组,其中使用 Array.reduce
鼓励纯函数。您可以在排序之前克隆数组。
我相信这个问题旨在通过强制约束让您以不同的方式思考。它测试您对 reduce
工作原理的了解,正如答案所示,剥猫皮的方法有很多种。它会在解决这个问题时显示你个人的 js 风格。
我选择使用 Array.findIndex
和 Array.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]
如果未提供数组,您可以使用一些函数来获取数组,该函数 returns 通过采用两个参数和排序回调来排序数组,其中包括上述内容,通过使用另一个 reduce 方法获取排序数组的部分结果。
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; }
这是
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, []))
reduce
将自己限制在 在线排序算法 中,数组中的每个元素都被看到一次,您现在不需要预先计算数组的长度(注意使用闭包你可以为减少功能提供更多信息,但这种类型违背了问题的目的)。
插入排序是一个明显而简单的例子;我不会详细说明实现,因为其他答案在这方面已经非常好。但是,您可以提及一些可能被面试官视为非常积极的优化:
使用二分查找查找插入点可以将一个插入步骤的复杂度从O(n)降低到O(log n)。这称为二进制插入排序:比较的总数将从 O(n^2) 变为 O(n log n)。这不会更快,因为实际成本是由于 "splicing" 输出数组,但如果您进行昂贵的比较(例如,对长字符串进行排序),它可能会有所不同。
如果对整数进行排序,可以使用radix sort并使用reduce实现线性在线排序算法。这实现起来相当棘手,但非常适合减少。
您可以使用插入排序:
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
而不是其他数组函数来实现所有函数。
一个可怕的排序算法可以用 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]
它的 return 需要多个应用程序才能对最简单的数组以外的任何内容进行排序。
但它最终会到达那里。这需要递归!
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(', '));
当当前值大于已处理数组的最后一个值时,它被追加。如果它小于第一个元素,它会被添加到前面。仅当当前值和累加数组既不在之前也不在之后时,才通过递归调用再次排序。该方法应该等同于插入排序(请评论!)。