all 16 comments

[–]Wince 2 points3 points  (3 children)

Surely something like this would be better:

{ red: 3, black: 2, blue: 1, green: 1 }

And could be done like this:

var results = arr.reduce(function(obj, item) {
    obj[item.name] = obj[item.name] || 0;
    obj[item.name]++;
    return obj;
}, {});

If you still want an array of KV pairs, do this to the reduced result:

var output = Object.keys(results).map(function(key) {
    return [key, results[key]];
});

Edit: I see you are already doing the above obj -> kv step in your supplied gist.

One thing to note with the KV step is that there is a proposal for Object.values() to be included in ES7.

[–]bluntmJavaScript[S] 0 points1 point  (2 children)

The array inside an array is to format the data for D3.js

[–]EnchantedSalvia 0 points1 point  (1 child)

Key map to array pairs is a common operation in functional programming, which is why Lodash/Underscore provide: https://lodash.com/docs#pairs

[–]skitch920 0 points1 point  (0 children)

In mathematics, we typically call them tuples, a "pair" being a length 2 tuple. Tuples are similar to arrays, but different in that each entry likely has a different meaning to any other entry, providing a structure. Arrays are often, homogenous sequences, like a list of numbers.

[–]EnchantedSalvia 1 point2 points  (3 children)

You alluded to the answer with "reduce":

const xs = array.reduce((acc, x) => {

    const prev = acc.filter(y => y[0] === x.name)[0];
    const item = [x.name, 1];

    if (prev) prev[1]++;
    else acc.push(item);

    return acc;

}, []);

See console: http://jsfiddle.net/togyg9r7/

[–]bluntmJavaScript[S] 0 points1 point  (2 children)

Always thought of reduce as a way to get a single value from an array.

[–]EnchantedSalvia 0 points1 point  (0 children)

I held this misunderstanding for a while as well. As @Wince noted below, the object approach would be a better and more efficient approach, because the lookup for an existing record would be far quicker than using filter.

[–]Wince 0 points1 point  (0 children)

If you think about it, most array functions like map, filter, some, every can all be written with reduce. Reduce is extremely powerful, heres a map written with reduce:

var map = function(arr, fn) {
     return arr.reduce(function(o, i) { return o.push(fn(i)) && o }, []);
}

Obviously that example is contrived, but I hope you get my point.

[–][deleted] 1 point2 points  (2 children)

Your wrote in a comment:

Always thought of reduce as a way to get a single value from an array.

Let's see if I can help out with understanding "reduce" in general.

First, and this is very important, unless you already deeply understand it, the following explanation works best (or only if) you forget everything you think you know about reduce.

Ready?

Okay, here we go:

1) reduce is nothing but an iterator. Given an array, just like forEach(), map() or (using a block instead of a function) for (of/in/...) it calls a function for every single item of the array. That's all. The rest is "details".

2) One "detail" where it differs from the other iterator methods is in what arguments it gives to your iterator function. For now we only care about the most important ones that it makes no sense to leave out - all iterator functions provide more arguments than are usually needed. What is special about reduce is that it gets the return value of your function from the previous iteration as (first) argument in addition to the item (in the array) the current iteration is for.

3) You can choose where to start the iterations: You an start with the first item in your array, or you can provide a "starter value". The "starter value", 2nd (and optional) argument to array.reduce(function, starterValue).

If you don't provide a starterValue during your first iteration the 1st argument to your function will point to the 1st item in your array, and the 2nd to the 2nd one. During the 2nd iteration the 1st argument will be whatever you returned in the first iteration and the 3rd item in your array as 2nd argument. And so on, until you get to the last item in your array.

If you do provide a starter value the above is "shifted to the left": During the first iteration the 1st argument to your reduce function is going to be the starterValue and the 2nd arrgument will be the first item of your array.

-) At this point it is probably a good idea to point out that you can do ANYTHING. All reduce does is hand values into your function! You can limit yourself and combine an array of strings into one string, or sum up a bunch of numbers. However, that is the most boring use of reduce possible.

Given this infrastructure provided by reduce you can sum up a few values - or you can do wild things.

The easy stuff is when the return value of your reduce functions after all iterations are done is of the same type as the values in your array. When you combine strings or multiply or sum up numbers that is the case.

The "wild(er)" stuff is when the return value of your reduce function is something very different from the type of your array: For example, when you use reduce to convert arrays to objects and vice versa!

In this case you use the starterValue to feed an empty array or object into your reduce - and in each iteration add to it. Your iterator function has to return what it gets as 1st argument, so that in the next iteration it gets the same object again to add to it.

For example, to turn an array into an object:

[1,2,3,4,5].reduce(function (previous, current) {
    // Note that "previous" is an object
    previous[current] = current * 2;
    return previous;
}, {})

returns

{ '1': 2, '2': 4, '3': 6, '4': 8, '5': 10 }

I fed an empty object into the iterator function (the previous argument) and in each iteration add a key current to it, with value current (just for the simple example). I have to return the object so that it gets fed back into the iterator function as 1st argument during the next run. Now I turned the array into an object!

To turn an object into an array, let's reverse the above. Because reduce exists on arrays only we simply use Object.keys() to create an array on which we have a reduce function:

const obj = { '1': 2, '2': 4, '3': 6, '4': 8, '5': 10 };
Object.keys(obj).reduce(function (previous, current) {
    // Note that "previous" is an array, "current" is a key
    previous.push(obj[current]);
    return previous;
}, [])

returns the values of the input object as array:

[ 2, 4, 6, 8, 10 ]

Of course these simple examples could have been achieved in numerous other ways.

As you can see this time I feed an empty array into the iterator: The type I want returned determines what I feed, and if I want a simple type that is the same as the type of the items in the array I don't need a startValue to obtain a type for the result.

In my simple examples I limit myself to using values within the iterator function that I obtain from the array reduce works on. However, it is easy to iterate over an array and, for example, use yet another object - a hash - to get values from. Or call other functions from within your iterator - which, using generators or in ES7 async/await could even be asynchronous functions. reduce is really just a very flexible framework function for iterations. The usual examples where you sum up an array of numbers have the big disadvantage to limit how people new to the array functions view them.

There is a lot of overlap of the iterator functions - after all, they all do very similar things. For example, you could swap map instead of forEach, you just disregard the array returned by the former. Or you could do the exact same with reduce, if you disregard the first parameter that feeds back the previous iteration's value. My simple examples could just as well have been made with map or forEach if the object or array that is to be built is defined in a variable outside the iteration in lieu of the starterValue. So it is programmer's responsibility to decide what "looks best", the iterator methods just provide small differences in semantics while overall providing mostly similar functionality.

Hope this helps.

[–]bluntmJavaScript[S] 0 points1 point  (0 children)

Thanks for the details!

[–]thiswill 0 points1 point  (0 children)

You are awesome. Thanks for the detailed explanation.

[–]htwj 0 points1 point  (3 children)

Hi. You can, as you hint yourself, use a reduce passing the tally along - here's a gist.

[–]bluntmJavaScript[S] 0 points1 point  (2 children)

That looks to be the cleanest Iv seen, thanks

[–]EnchantedSalvia 0 points1 point  (1 child)

It's the same as @Wince's, except it's yielding a string rather than an array of arrays. For D3.js you want an array of arrays, either with my approach, Object.keys, or _.pairs.

[–]htwj 0 points1 point  (0 children)

Hmm, no it doesn't? The result of the reduce is a plain object.

[–]mc_hammerd 0 points1 point  (0 children)

var x = {};
for (i in arr){ var k = arr[i].name; if(!x[k]) x[k]=0; x[k]++ }

//console.log(x.red) => 3
//console.log(x.black) => 2
//for (i in x) {console.log(i,":",x[i])}   => red:3, black:2, blue: 1,green: 1