Imi
Imi

Reputation: 549

How to use javascript reduce function for objects in an array to get string values

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`**

fiddle

Upvotes: 1

Views: 2867

Answers (5)

Mulan
Mulan

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, ...'
  1. an empty array ([]) returns an empty string
  2. a single name will just return that name – ', ' separator is not present
  3. an array of 2 or more names will return the names with separators between

Fulfilling your request

You asked to do this with Array.prototype.reduce, so we'll start there. What you'll notice about this answer is

  • it works for all input scenarios described above, making listOfNames a total function – several other answers fail to accommodate (1) and (2) above.
  • it only uses a single 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

CampSafari
CampSafari

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

Sylwester
Sylwester

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

Vladu Ionut
Vladu Ionut

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

philipp
philipp

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

Related Questions