0

I have an array of objects - say, NFL teams. Each object (team) has a property - array of first names of team players.

var nflTeams = [
      { name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true },
      { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false },
      { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false },
      { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false },
    ];

Expected result - an object with an occurrence of first names: {'Joe': 1, 'Jimmy': 2, 'Jalen': 1 ....}

My goal is to achieve it through the use of Underscore methods such as _.map(), _.flatten(), _.reduce() all chained together using _.chain().

My current attempt miserably fails at _.reduce() stage:

var firstNameOccurence = { '{players firstName}': 0 };
    firstNameOccurence = _.chain (nflTeams)
      .map(function(team) {return team.playersFirstNames})
      .flatten()
      .reduce(function(newObject, firstName){
          console.log ('we have a first name of a player', firstName);
            return newObject[firstName] = 1 ? !newObject[firstName] :  newObject[firstName] += 1;
          
      }, {})
      .value();

Now it gives me just a boolean value of true. My ideal is try to use ternary expression because it looks more elegant. I tried using official Underscore documentation and some examples like this, this and this.

1 Answer 1

2

I wouldn't use .reduce() for this. Underscore.js has inbuilt methods that already perform what you're trying to do (ie: count the frequencies of items in an array), so you're better off using those instead of reinventing the wheel. What you can use is a combination of JavaScript's .flatMap() (annoyingly, underscore doesn't provide it, other utility libraries like Lodash do). And the _.countBy() method:

const nflTeams = [{ name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true }, { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false }, { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false }, { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false }, ];

const res = _.countBy(nflTeams.flatMap(team => team.playersFirstNames));
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js" integrity="sha512-2V49R8ndaagCOnwmj8QnbT1Gz/rie17UouD9Re5WxbzRVUGoftCu5IuqqtAM9+UC3fwfHCSJR1hkzNQh/2wdtg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

If you don't want to use JavaScript's inbuilt .flatMap() method you can chain .map() and .flatten() like you have:

const nflTeams = [{ name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true }, { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false }, { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false }, { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false }, ];

const res = _.chain(nflTeams).map('playersFirstNames').flatten().countBy().value();
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js" integrity="sha512-2V49R8ndaagCOnwmj8QnbT1Gz/rie17UouD9Re5WxbzRVUGoftCu5IuqqtAM9+UC3fwfHCSJR1hkzNQh/2wdtg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>


As for your attempt, I suggest looking into how the core operators of JavaScript work before looking into more complicated concepts like .reduce(), which includes = (assignment), ! (negation), ? : (ternary) etc. In your attempt, you're doing newObject[firstName] = 1 ? which assigns the value of 1 to the firstName property of your object as well as evaluates to the value of 1. That means the true portion of your ternary will always be evaluated and will be the thing you're returning. In your case, that's !newObject[firstName]. As you just set this to 1, you're negating it with !, and !1 is false. This results in your returning false. Then, on the next iteration, .reduce() calls your callback with newObject set to the false value you just returned. It's no longer an object. Again, your code now attempts to set a property firstName on newObject with newObject[firstName] = 1. As this is equivalent to false[firstName] = 1, it evaluates to 1, but leaves newObject (ie: false) unmodified. When !newObject[firstName] runs again, it's unable to find the property firstName on the false value so it ends up returning !undefined, ie: true. This continues until all your iterations are completed.

As you can see, your current logic in your .reduce() callback doesn't make much sense, as you're trying to return a boolean rather than an object, which is what you want your final result to be:

const nflTeams = [{ name: 'Kansas City Chiefs', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true }, { name: 'Philadelphia Eagles', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false }, { name: 'Cincinnati Bengals', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false }, { name: 'San Francisco 49ers', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false }, ];

const res = _.chain(nflTeams)
  .map('playersFirstNames')
  .flatten()
  .reduce((currObject, firstName) => { // ((currObject, firstName)) => ({...currObject, [firstName]: (currObject[firstName] || 0) + 1}) as the callback would also work here and it'd be more concise, however, it is less efficient as it creates a new object for each iteration as well as iterates all existing properties of `currObject` per loop.
    currObject[firstName] = (currObject[firstName] || 0) + 1;
    return currObject;
  }, {})
  .value();
console.log(res);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-min.js" integrity="sha512-2V49R8ndaagCOnwmj8QnbT1Gz/rie17UouD9Re5WxbzRVUGoftCu5IuqqtAM9+UC3fwfHCSJR1hkzNQh/2wdtg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Sign up to request clarification or add additional context in comments.

3 Comments

Good answer! You can be even more concise with _.map, though. Thanks to iteratee shorthands, you can do .map('playersFirstNames') instead of the equivalent arrow function.
Your answer is to the point. It took me a while to go through my mistakes, but now it makes sense.
@curious_orange No worries, if you feel like this answers your question you can accept the answer by using the grey check mark in the top left corner of the answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.