4
nums = [2 5 3 7]
result = []
result.push {x:nums[0]}
for n in nums.slice(1)
    result.push {n:n + result[-1].x}
log result
# [{x:2} {x:7} {x:10} {x:17}]

This is hard to express functionally using the function map because each element depends on the previous element. What is the correct functional solution for this algorithm?

MaiaVictor
  • 51,090
  • 44
  • 144
  • 286
  • 1
    Shouldn't the body of the loop be `result.push {x:n + result[-1].x}`? – Ted Hopp Jun 07 '13 at 20:35
  • I don't know coffeescript, but I do know that `map` is just a special case of `fold`, which is the more generic loop. With a `fold`, you can do what you ask. – kqr Jun 07 '13 at 21:30
  • map/filter() in JS does something that reduce() in JS doesn't; allows call-time _this_ setting, which enables re-usable functions that reduce() cannot implement. ex: function gt(n){return n>this;} [1,2,3,4,5].filter(gt, 3); – dandavis Oct 09 '14 at 22:08

6 Answers6

18

simplest way i know avoids performance-robbing closures, variables, extra function overhead, and globals:

result= [2, 5, 3, 7].map(function(a){ return { x: this[0]+=a }; }, [0]);

JS provides the seldom-used 2nd .map() parameter to store any state you need between iterations.

It probably doesn't get any simpler than this, but don't know coffee, sorry...

EDIT: whipped up a dual-language (js+cs) demo: http://pagedemos.com/maptranforms/

dandavis
  • 16,370
  • 5
  • 40
  • 36
8

What you are describing is a scan: a fold that also returns intermediate results. Using scan1 from prelude.ls:

nums = [2 5 3 7]
scan1 (+), nums |> map ((num) -> { x : num })
# => [{x: 2}, {x: 7}, {x: 10}, {x: 17}]

If you don't need the objects inside the array and only need the additions with intermediate results, than you can drop the map operation altogether and just write:

scan1 (+), [2 5 3 7] # => [2, 7, 10, 17]

scan1 documenation.

Noah Freitas
  • 17,240
  • 10
  • 50
  • 67
3

You need to keep some state information somewhere. Here's a JavaScript closure that does the job:

var nums = [2, 5, 3, 7];
var result = nums.map(
    (function() {
        var lastX = 0;
        return function(n) {
            return {x : (lastX += n)};
        }
     }())
);
// result is [{x:2} {x:7} {x:10} {x:17}]
Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
2

JavaScript

Keep a running counter of the total and just add the new number each time:

var nums = [2,5,3,7];

var createObject = function(nums){ 
    var result = [],
        total = 0;

    for(var i = 0; i < nums.length; i++){
        total += nums[i]; 
        result.push({"x": total});
    }

    return result;
};

JSFIDDLE

Chase
  • 29,019
  • 1
  • 49
  • 48
  • This is just a direct translation of OP's code into JavaScript. It doesn't replace the loop with a functional style. – Ted Hopp Jun 07 '13 at 20:59
2

dandavis answer in coffeescript is:

nums.map ((x)->{x: @[0] += x}), [0]

a variation that might be a bit clearer

nums.map ((x)->{x: @accum += x}), {accum:0}

using a coffeescript comprehension (and the same idea of an accumulator)

accum = 0; z = ({x: accum += i} for i in nums)
hpaulj
  • 221,503
  • 14
  • 230
  • 353
1
map (-> {x:it}) <| (fold ((acc, a) -> acc ++ [a + ((last acc) ? 0)]), []) <| [2, 5, 3, 7]
homam
  • 1,945
  • 1
  • 19
  • 26
  • 1
    # a little bit longer but getting rid of the conditional which I prefer map (-> {x:it}) <| drop 1 <| (fold ((acc, a) -> acc ++ [a + (last acc)]), [0]) <| [2 5 3 7]` – lab419 Feb 04 '14 at 10:06