2014-02-10 38 views
3

通过Eloquent JavaScript and High Order Functions工作 - 在Functional Programming部分。不能得到这个减少功能工作

尝试定义reduce函数并将其用于更高阶函数countWords();,该函数接受给定数组并计数每个特定值存在的次数并将其放入对象中。

I.e.这工作:

function combine(countMap, word) { 
    countMap[word] = ++countMap[word] || 1; // made the edit 

    return countMap; 
} 

function countWords(wordArray) { 
    return wordArray.reduce(combine, {}); 
} 

var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear']; 

countWords(inputWords); // {Apple: 2, Banana: 1, Pear: 3} 

即使这不:

function combine(countMap, word) { 
    countMap[word] = ++countMap[word] || 1; 

    return countMap; 
} 

function forEach(array, action) { 
    for (var i = 0; i < array.length; i++) { 
     action(array[i]); 
    } 
} 

function reduce(fn, base, array) { 
    forEach(array, function (element) { 
     base = fn(base, element); 
    }); 

    return base; 
} 

function countWords(wordArray) { 
    return reduce(combine, {}, wordArray); 
} 

var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear']; 

countWords(inputWords); // returned this - [object Object] { ... } - this is no longer an issue after fix keeping it noted for reference to the original issue. 

任何帮助,这将是伟大的。谢谢。

+1

这可能是一个更好的问题http://codereview.stackexchange.com。 –

+2

“ie。This works”--- it can not and it [does not](http://jsfiddle.net/7Zwc2/) – zerkms

+0

cc @AndersonGreen。 –

回答

-2

原因是你的ForEach实现是错误的。你应该设置i = 0;

function forEach(array, action) { 
    for(var i = 0; i < array.length; i++) { 
     action(array[i]); 
    } 
} 

似乎有什么问题。你更新一个对象。 ++countMap

function combine(countMap, word) { 
    countMap[word] = ++countMap || 1; 
    return countMap; 
} 

应该

function combine(countMap, word) { 
    countMap[word] = ++countMap[word] || 1; 
    return countMap; 
} 

我添加了一个jsbin here

+2

还有更多的错误。 – JayC

+0

是的,我注意到了。所以我添加一个jsbin链接。将它们全部更正。 –

4

你原来的降低实际上是坏了,尽管你说,它的工作原理。

这里有一个reduce实际功能

var words = ["foo", "bar", "hello", "world", "foo", "bar"]; 

var wordIndexer = function(map, word) { 
    map[word] = map[word] || 0; 
    map[word]++; 
    return map; 
}; 

var count = word.reduce(wordIndexer, {}); 

console.log(count); 

// Object {foo: 2, bar: 2, hello: 1, world: 1} 

这么说,我不是很确定你想与你的讯息下半年做什么。你只是想写forEachreduce的实现,所以你可以理解它们是如何工作的?


我会写forEach这样

var forEach = function(arr, callback) { 
    for (var i=0, len=arr.length; i<len; i++) { 
    callback(arr[i], i, arr); 
    } 
    return arr; 
}; 

而且reduce这样

var reduce = function(arr, callback, initialValue) { 
    var result = initialValue; 
    forEach(arr, function(elem, idx) { 
    result = callback(result, elem, idx, arr); 
    }); 
    return result; 
}; 

测试出来

var numbers = [10, 20, 30]; 

forEach(numbers, function(num, idx) { 
    console.log(idx, num); 
}); 

// 0, 10 
// 1, 20 
// 2, 30 
//=> [10, 20, 30] 

var n = reduce(numbers, function(sum, num, idx, arr) { 
    return sum = sum + num; 
}, 0); 

console.log(n); 
//=> 60 

对于那些好奇的关于R得出的回调,我匹配了native .reduce callback

0

我想,如果你想forEachreduce是相似的(简单)或尽可能接近/合理的ECMA5规范(忽略浏览器的bug)依靠,我喜欢尽可能接近/合理。

Array.prototype.forEach (callbackfn [ , thisArg ])

callbackfn应该是接受三个参数的函数。 forEach按升序为每个存在于数组中的元素调用一次callbackfn。callbackfn仅用于实际存在的数组的元素;它不会被要求丢失数组中的元素。

如果提供了thisArg参数,它将用作每次调用callbackfn的此值。如果未提供,则使用undefined。

使用三个参数调用callbackfn:元素的值,元素的索引和被遍历的对象。

forEach不会直接改变其被调用的对象,但该对象可能会被callbackfn的调用突变。

forEach处理的元素范围在第一次调用callbackfn之前设置。调用forEach之后追加到数组的元素将不会被callbackfn访问。如果数组中的现有元素发生更改,则传递给回调函数的值将是每次访问它们时的值;在forEach调用开始之后和被访问之前被删除的元素不会被访问。

当在foreach方法被称为具有一个或两个参数,采取以下步骤:

  1. 令O是调用ToObject传递该值作为参数的结果。
  2. 让lenValue成为用参数“length”调用O的[[Get]]内部方法的结果。
  3. 让len成为ToUint32(lenValue)。
  4. 如果IsCallable(callbackfn)为false,则引发TypeError异常。
  5. 如果提供了这个Arg,让T为thisArg;否则让T不确定。
  6. 令k为0。
  7. 重复,按住k < len个
  8. 让PK是的ToString(K)。
  9. 设kPresent是用参数Pk调用O的[[HasProperty]]内部方法的结果。
  10. 如果kPresent为真,那么
  11. 设置kValue是用参数Pk调用O的[[Get]]内部方法的结果。
  12. 呼叫callbackfn的有T 1。
  13. 返回未定义的[[调用]]内部方法作为含有kValue,k和O.
  14. 增加k处的该值和参数列表。

在foreach方法的长度属性是1

注意在foreach功能是有意通用的;它不要求它的这个值是一个Array对象。因此可以将其转换为其他类型的对象以用作方法。 forEach函数是否可以成功应用于主机对象取决于实现。

-

Array.prototype.reduce (callbackfn [ , initialValue ])

callbackfn应该是一个函数,它有四个参数。作为函数,按照升序的顺序减少对该数组中存在的每个元素的回调。

使用四个参数调用callbackfn:previousValue(或前一次调用callbackfn的值),currentValue(当前元素的值),currentIndex和遍历的对象。第一次调用回调函数时,previousValue和currentValue可以是两个值中的一个。如果在调用中提供了initialValue以reduce,则previousValue将等于initialValue,而currentValue将等于数组中的第一个值。如果未提供initialValue,则previousValue将等于数组中的第一个值,而currentValue将等于第二个值。如果数组不包含任何元素并且未提供initialValue,则它是一个TypeError。

reduce不会直接改变它被调用的对象,但对象可能会被callbackfn的调用突变。

reduce处理的元素范围在第一次调用callbackfn之前设置。 callbackfn不会访问在调用reduce之后追加到数组的元素。如果数组的现有元素发生更改,则传递给callbackfn的值将是减少访问时的值;在调用之后删除的元素在访问开始之前和访问之前不会被访问。

当减少方法被称为具有一个或两个参数,采取以下步骤:

  1. 令O是调用ToObject传递该值作为参数的结果。
  2. 让lenValue成为用参数“length”调用O的[[Get]]内部方法的结果。
  3. 让len成为ToUint32(lenValue)。
  4. 如果IsCallable(callbackfn)为false,则引发TypeError异常。
  5. 如果len为0且initialValue不存在,则引发TypeError异常。
  6. 令k为0。
  7. 如果初值存在,则
  8. 设置累加器为InitialValue。
  9. 否则,initialValue不存在
  10. 让kPresent为假。
  11. 重复,而kPresent为false并且k < len
  12. 设Pk为ToString(k)。
  13. 设kPresent是用参数Pk调用O的[[HasProperty]]内部方法的结果。
  14. 如果kPresent为真,那么
  15. 让累加器是用参数Pk调用O的[[Get]]内部方法的结果。
  16. 将k增加1.
  17. 如果kPresent为false,则引发TypeError异常。
  18. 重复,而k < len
  19. 设Pk为ToString(k)。
  20. 设kPresent是用参数Pk调用O的[[HasProperty]]内部方法的结果。
  21. 如果kPresent为真,那么
  22. 设置kValue是用参数Pk调用O的[[Get]]内部方法的结果。
  23. 设累加器是调用的结果[[调用]具有未定义作为此值和参数列表包含累加器,kValue,k和O. callbackfn的内部方法
  24. 增加K内由1
  25. 返回累加器。

的减少方法的长度属性是1

注意的降低功能被有意通用的;它不要求它的这个值是一个Array对象。因此可以将其转换为其他类型的对象以用作方法。 reduce函数是否可以成功应用于主机对象取决于实现。

对我来说,我会写(这些不是100%的规范,但接近),并保存在我的个人图书馆。

function firstToCapital(inputString) { 
    return inputString.charAt(0).toUpperCase() + inputString.slice(1).toLowerCase(); 
} 

function isClass(inputArg, className) { 
    return Object.prototype.toString.call(inputArg) === '[object ' + firstToCapital(className) + ']'; 
} 

function checkObjectCoercible(inputArg) { 
    if (typeof inputArg === 'undefined' || inputArg === null) { 
     throw new TypeError('Cannot convert argument to object'); 
    } 

    return inputArg; 
}; 

function ToObject(inputArg) { 
    checkObjectCoercible(inputArg); 
    if (isClass(inputArg, 'boolean')) { 
     inputArg = new Boolean(inputArg); 
    } else if (isClass(inputArg, 'number')) { 
     inputArg = new Number(inputArg); 
    } else if (isClass(inputArg, 'string')) { 
     inputArg = new String(inputArg); 
    } 

    return inputArg; 
} 

function ToUint32(inputArg) { 
    return inputArg >>> 0; 
} 

function throwIfNotAFunction(inputArg) { 
    if (!isClass(inputArg, 'function')) { 
     throw TypeError('Argument is not a function'); 
    } 

    return inputArg; 
} 

function forEach(array, fn, thisArg) { 
    var object = ToObject(array), 
     length, 
     index; 

    throwIfNotAFunction(fn); 
    length = ToUint32(object.length); 
    for (index = 0; index < length; index += 1) { 
     if (index in object) { 
      fn.call(thisArg, object[index], index, object); 
     } 
    } 
} 

function reduce(array, fn, initialValue) { 
    var object = ToObject(array), 
     accumulator, 
     length, 
     kPresent, 
     index; 

    throwIfNotAFunction(fn); 
    length = ToUint32(object.length); 
    if (!length && arguments.length === 2) { 
     throw new TypeError('reduce of empty array with no initial value'); 
    } 

    index = 0; 
    if (arguments.length > 2) { 
     accumulator = initialValue; 
    } else { 
     kPresent = false; 
     while (!kPresent && index < length) { 
      kPresent = index in object; 
      if (kPresent) { 
       accumulator = object[index]; 
       index += 1; 
      } 
     } 

     if (!kPresent) { 
      throw new TypeError('reduce of empty array with no initial value'); 
     } 
    } 

    while (index < length) { 
     if (index in object) { 
      accumulator = fn.call(undefined, accumulator, object[index], index, object); 
     } 

     index += 1; 
    } 

    return accumulator; 
} 

function keys(object) { 
    if (!isClass(object, 'object') && !isClass(object, 'function')) { 
     throw new TypeError('Argument must be an object or function'); 
    } 

    var props = [], 
     prop; 

    for (prop in object) { 
     if (object.hasOwnProperty(prop)) { 
      props.push(prop); 
     } 
    } 

    return props; 
} 

var inputWords = ['Apple', 'Banana', 'Apple', 'Pear', 'Pear', 'Pear']; 

var counts = reduce(inputWords, function (previous, element) { 
    previous[element] = ++previous[element] || 1; 

    return previous; 
}, {}); 

forEach(keys(counts), function (key) { 
    console.log(key, this[key]); 
}, counts); 

jsFiddle

当然,这可能是你正在做的事情有点OTT。 :)