Reputation: 8093
I am trying to write a reduce function that assigns an incrementing integer to each new unique value in an array and builds a dictionary mapping the unique element to the incrementing index.
My desired function consumes an array like this:
[3,1,1,2,3,1,5,1,2]
and outputs a map that looks like this:
The function looks like this:
{3: 0, 1: 1, 2: 2, 5: 3}
One potential solution is to use reduce, but it requires keeping the counter outside of the function:
var i = 0
var func = [3,1,1,2,3,1,5,1,2].reduce(function(a, b) {
if (!(a in b) {
b[a] = i
i++
}
return b
},{})
Is there a way to write this function but somehow keep the counter inside the function's scope? Obviously I could wrap everything in a function, but is there a way to do this with just reduce?
Upvotes: 0
Views: 1349
Reputation: 1016
Array.prototype.reduce takes 4 arguments. You might consider using them, since one of them is the index value. And thus the need for a counter is not necessary.
var func = [3,1,1,2,3,1,5,1,2].reduce(function(prev, curr, idx, arr) {
if(!(prev in curr)) curr[prev] = (prev.length > 0) ? prev.length - 1 : 0;
return curr;
});
Upvotes: 2
Reputation: 237100
The answer is yes, but it's probably more awkward than just using a scoping function. Basically, you just make your state value a composite of the actual result you want and the additional state, and then you extract the result when you're done reducing.
var func = [3,1,1,2,3,1,5,1,2].reduce(function(memo, val) {
if (!(val in memo[0])) {
memo[0][val] = memo[1]
memo[1]++
}
return memo
}, [{}, 0])[0]
Upvotes: 0
Reputation: 20796
You can always throw i
into the object itself:
[3,1,1,2,3,1,5,1,2].reduce(function(a,b) {
if(a[b]===undefined) a[b] = a.i++; return a},{i:0})
// {1: 1, 2: 2, 3: 0, 5: 3, i: 4}
Upvotes: 0
Reputation: 5676
Using a closure will help keeping your code clean:
fun=function(){
var i=0;
return function(b, a) {
if (!(a in b)) {
b[a] = i
i++
}
return b;
};
}();
var func = [3,1,1,2,3,1,5,1,2].reduce(fun,{})
So you have fun
while reducing.
Upvotes: 4