当使用函数式编程主体从泛型reduce函数生成flatten函数时,我遇到从数组flatten函数中获取不正确值的问题。我认为这是因为调用中的递归存在问题,但我不确定如何移过它,因为工作函数和非工作函数的函数签名应该相同。
感谢您的帮助。
var data = [['one','two','three'], ['four', 'five', ['six']], 'seven', ['eight', 'nine']];
// here is an example of flatten that works perfectly. it takes an array and reduces
// the internal arrays to a single flat array
function flatten( arr ){
return arr.reduce(function( ret, curr ){
if( Array.isArray( curr ) ){
ret = ret.concat( flatten( curr ) );
} else {
ret.push( curr );
}
return ret;
}, []);
}
// here is what I am trying to achieve. This one combines my reduction functon with the
// functional `reduceWith` function. The code signature is exactly the same, however the
// end result is different.
// `functionalFlatten` does resolve to the correct function inside
var functionalFlatten = reduceWith(function( ret, curr ){
if( Array.isArray( curr ) ){
ret = ret.concat( functionalFlatten( curr ) );
} else {
ret.push( curr );
}
return ret;
}, []);
// this function will return a functional reduction function
function reduceWith( fn, initial ) {
return function _reduceWith( arr ) {
return Array.prototype.reduce.call(arr, fn, initial || []);
}
}
console.log('data', data);
console.log('functionalFlatten', functionalFlatten );
console.log('normal', flatten( data ));
console.log('fuctional', functionalFlatten( data ));
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>
答案 0 :(得分:3)
以下是我修复你的功能的方法
var functionalFlatten = reduceWith(function f( ret, curr ){
if( Array.isArray( curr ) ){
ret = ret.concat( reduceWith(f, [])( curr ) );
} else {
ret.push( curr );
}
return ret;
}, []);
初始代码的问题是对父调用和递归调用的initial
使用相同的ret
值。
答案 1 :(得分:2)
我对您的代码进行了一些更改。这有效:
var data = [
['one','two','three'],
['four', 'five', ['six']],
'seven',
['eight', 'nine']
]
function flatten(arr) {
return arr.reduce(function(ret, curr) {
return ret.concat(Array.isArray(curr) ? flatten(curr) : [curr])
}, [])
}
var functionalFlatten = reduceWith(function(ret, curr) {
return Array.prototype.concat.call(ret, Array.isArray(curr) ? functionalFlatten(curr) : [curr])
}, [])
// I assume you want to use .call to keep it functional or what ever
// But I would just do it like this:
var _functionalFlatten = reduceWith(function(ret, curr) {
return ret.concat(ret, Array.isArray(curr) ? functionalFlatten(curr) : [curr])
}, [])
function reduceWith(fn, initial) {
return (function (arr) {
return Array.prototype.reduce.call(arr, fn, initial)
})
}
// Again, keep it simple...
function _reduceWith(fn, initial) {
return (function (arr) {
return arr.reduce(fn, initial)
})
}
// You had this...
function reduceWith( fn, initial ) {
// You don't need to name this function:
return function _reduceWith( arr ) {
// Let's keep this in line original function, so remove the default:
return Array.prototype.reduce.call(arr, fn, initial || []);
}
}
console.log('data', data)
console.log('functionalFlatten', functionalFlatten)
console.log('normal', flatten(data))
console.log('fuctional', functionalFlatten(data))
现在解决实际问题......
var functionalFlatten = reduceWith(function( ret, curr ){
if( Array.isArray( curr ) ){
ret = ret.concat( functionalFlatten( curr ) );
} else {
// This is your culprit:
ret.push( curr ); // push will mutate ret
}
return ret;
}, []);
reduceWith
只会被调用一次(定义{{1}}时)。functionalFlatten
有可能改变ret.push(curr)
这是证据......
initial
这将有效,即使function reduceWithMutationSafe(fn, initial) {
return (function (arr) {
// Clone initial, so that the original can't be mutated:
var clonedInitial = eval(JSON.stringify(initial))
return arr.reduce(fn, clonedInitial)
})
}
var functionalFlatten = reduceWithMutationSafe(function(ret, curr) {
if(Array.isArray(curr)) {
ret = ret.concat(functionalFlatten(curr))
} else {
ret.push(curr)
}
return ret
}, [])
与之前完全相同
functionalFlatten
将改变克隆的ret.push(curr)
,但是原始的。{1}}
不会被触及。
但最后一段代码只是证明。不应该使用initial
。
答案 2 :(得分:1)
在这里你可以写reduceWith
作为一个curried函数,它可以让你更好地重用整个函数。
// ES6
const reduce = f => y => xs => xs.reduce (f, y);
现在我们可以将flatten
写为部分应用的reduce
函数
const flatten = reduce ((y,x) => y.concat (isArray (x) ? flatten (x) : x)) ([]);
那个小isArray
助手就是
const isArray = Array.isArray;
它只是有效。没有突变.push
,没有Function.prototype.call
。只是折叠和连续。
console.log (flatten ([1,2,[3,4,[],6,[7,8,9]]]));
//=> [1,2,3,4,5,6,7,8,9]
这是ES5
// ES5
"use strict";
var reduce = function reduce(f) {
return function (y) {
return function (xs) {
return xs.reduce(f, y);
};
};
};
var isArray = Array.isArray;
var flatten = reduce(function (y, x) {
return y.concat(isArray(x) ? flatten(x) : x);
})([]);
console.log(flatten([1, 2, [3, 4, [], 6, [7, 8, 9]]]));
//=> [1,2,3,4,5,6,7,8,9]