Reputation: 549
I have an array of objects from which I would like to get names. I am trying to use reduce for that but the problem is that reduce returns the first values as undefined and only returns the last value in the array. I wonder why that is? How does reduce works with strings inside objects?
my code:
var data = [ {name: 'Bart'}, {name: 'Lisa'}, {name: 'Maggie'} ]
var result = function(){
var names = data.reduce(function(a, b){
return a.name + ',' + b.name
})
return names
};
var res = result();
console.log(res)
**`output // undefined,Maggie`**
Upvotes: 1
Views: 2867
Reputation: 135197
The why and the how
Several answers here rush to get the answer correct, but fail to build a robust, complete function. This answer is meant to be exhaustive and give you insight into why we would choose certain implementations over others
Contract
For your function to work on all [{name: String}]
inputs (it should), you have to handle 3 cases
listofNames([]) // ''
listofNames([{name: 'Bart'}]) // 'Bart'
listofNames([{name: 'Bart'},{name: 'Lisa'}, ...]) // 'Bart, Lisa, ...'
[]
) returns an empty string', '
separator is not presentFulfilling your request
You asked to do this with Array.prototype.reduce
, so we'll start there. What you'll notice about this answer is
listOfNames
a total function – several other answers fail to accommodate (1) and (2) above.if
, because that's all that is required – using if
within the reducer is wasteful
*const listOfNames = ([x,...xs]) => {
if (x === undefined)
return ''
else
return xs.reduce((acc, {name}) => acc + ', ' + name, x.name)
}
console.log(listOfNames([])) // ''
console.log(listOfNames([{name: 'Bart'}])) // 'Bart'
console.log(listOfNames([{name: 'Bart'}, {name: 'Lisa'}])) // 'Bart, Lisa'
A recommendation
You tagged this question with functional-programming so we're going to look at this from another perspective. What we did above was re-invent the Array.prototype.join
function – only ours is actually a little worse.
console.log([].join(', ')) // ''
console.log(['Bart'].join(', ')) // 'Bart'
console.log(['Bart','Lisa'].join(', ')) // 'Bart, Lisa'
Our function is more specific and does not allow us to change the separator that is used (always uses ', '
) and it assumes each element in the array has a name
property.
In functional programming paradise kingdom, we should be making our functions as generic as possible - this allows for the most code recycling.
OK, so instead of reinventing a perfectly good function, we just need to convert our array of objects into an array of names, then use the perfectly good join
function
const prop = x => y => y[x]
const listOfNames = xs =>
xs.map(prop('name')).join(', ')
console.log(listOfNames([])) // ''
console.log(listOfNames([{name: 'Bart'}])) // 'Bart'
console.log(listOfNames([{name: 'Bart'}, {name: 'Lisa'}])) // 'Bart, Lisa'
"But that's basically the same thing some other people said"
Yep, but clearly they don't realize why it's better. They think because the resulting code is shorter, it's somehow better - otherwise, they wouldn't have also given reduce
solutions that are so horribly complex/incomplete.
Now you know why certain solutions are better than others, and you can evaluate which one is best for you
Quiz: can you name one reason why the map
+join
solution could be worse than our custom reduce
solution?
Upvotes: 1
Reputation: 1492
You need to check if the previous value (a) is set. if not return the current value (b). Also pass in an empty string as the initial value for the reduce function.
var data = [ {name: 'Bart'}, {name: 'Lisa'}, {name: 'Maggie'} ]
var result = function(){
var names = data.reduce(function(a, b){
return a ? a + ',' + b.name : b.name;
},"");
return names;
};
var res = result();
console.log(res);
Upvotes: 0
Reputation: 48745
reduce
takes two arguments. The first is the accumulated value and the second is one of the elements in the array. In the beginning the supplied accumulator is used, in my example an empty string. I fnot supplied it will be the null value undefined
.
arrData.reduce(function (strAccumulator, objElement) {
return (strAccumulator ? strAccumulator + ', ' : '' ) + objElement.name;
}, "");
// ==> "Bart, Lisa, Maggie"
If you use Underscore, you can do this cleaner with pluck
and join:
_.pluck(arrData, 'name').join(', ');
// ==> "Bart, Lisa, Maggie"
Note that if your object has a toString
function that returns the string representation you would like, you can omit plucking:
function Simpson(strName) {
this.name = strName;
}
Simpson.prototype.toString = function(){
return this.name;
};
var arrData = [new Simpson('Bart'), new Simpson('Lisa'), new Simpson('Maggie')];
arrData.join(', ');
// ==> "Bart, Lisa, Maggie"
Upvotes: 1
Reputation: 8183
Reduce executes the callback function once for each element present in the array, excluding holes in the array.
First 2 arguments are :
accumulator ( in your case a
)
currentValue ( in your case b
)
InitialValue isn't provided so reduce will execute the callback function starting at index 1, skipping the first index. First time the accumulator is the object {name: 'Bart'}
, second time is a string "Bart,Lisa"
var data = [ {name: 'Bart'}, {name: 'Lisa'}, {name: 'Maggie'} ]
var result = function(){
var names = data.reduce(function(a, b){
return (a.name || a) + ',' + b.name
})
return names
};
var res = result();
console.log(res)
For a cleaner solution you can use map instead of reduce .(Is more readable )
var data = [ {name: 'Bart'}, {name: 'Lisa'}, {name: 'Maggie'} ]
var result = data.map(x =>x.name).join(",");
console.log(result);
Upvotes: 1
Reputation: 16495
like so:
var names = data.reduce(function (names, item) {
return names + ' ' + item.name;
}, "");
or:
var names = data.map(function (item) { return item.name }).join(" ");
Upvotes: 1