函数js - 不能递归调用自己的函数

时间:2015-12-31 20:24:07

标签: javascript recursion functional-programming

当使用函数式编程主体从泛型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>

3 个答案:

答案 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]
相关问题