英文:
How to chain Underscore methods to populate an object from nested array?
问题
以下是您要求的代码部分的中文翻译:
// 假设有一个包含NFL球队信息的对象数组,每个对象都有一个名为playersFirstNames的属性,包含球队球员的名字数组。
var nflTeams = [
{ name: '堪萨斯城酋长', playersFirstNames: ['Shane', 'Chad', 'Michael', 'Ronald', 'Blake', 'Noah'], champions: true },
{ name: '费城老鹰', playersFirstNames: ['Jalen', 'Kenneth', 'Boston', 'Trey', 'Jack', 'Andre', 'Jack', 'Lane', 'Jason', 'Nakobe'], champions: false },
{ name: '辛辛那提孟加拉虎', playersFirstNames: ['Brandon', 'Joe', 'Chris', 'Joe', 'Tyler', 'Trenton', 'Trent', 'Mitchell', 'Alex', 'Trey', 'Ted'], champions: false },
{ name: '旧金山49人', playersFirstNames: ['Jimmy', 'Josh', 'Kyle', 'Jordan', 'Brandon', 'Danny', 'George', 'Tyler', 'Charlie', 'Jake', 'Nick', 'Nick', 'Kevin'], champions: false },
];
// 预期结果 - 一个包含名字出现次数的对象: {'Joe': 1, 'Jimmy': 2, 'Jalen': 1 ....}
// 我的目标是使用Underscore方法,如_.map(),_.flatten()和_.reduce(),通过_.chain()方法链来实现它。
// 我当前的尝试在_.reduce()阶段失败了:
var firstNameOccurrence = { '{球员名字}': 0 };
firstNameOccurrence = _.chain(nflTeams)
.map(function (team) { return team.playersFirstNames; })
.flatten()
.reduce(function (newObject, firstName) {
console.log('我们有一个球员的名字', firstName);
return newObject[firstName] = 1 ? !newObject[firstName] : newObject[firstName] += 1;
}, {})
.value();
// 现在它只返回一个布尔值true。我的理想是尝试使用三元表达式,因为它看起来更优雅。
// 我尝试使用官方Underscore文档以及一些示例,例如这个(https://www.geeksforgeeks.org/underscore-js-_-reduce-function/),这个(https://vegibit.com/3-examples-of-the-underscore-reduce-function/)和这个(https://www.tutorialspoint.com/underscorejs/underscorejs_reduce.htm)。
请注意,上述代码中的问题是在条件表达式中使用了错误的语法。应该使用条件表达式来判断对象属性是否已经存在,然后相应地增加计数。但上述代码中的语法不正确。
英文:
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
得分: 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);
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);
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);
英文:
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:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
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);
<!-- language: lang-html -->
<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>
<!-- end snippet -->
If you don't want to use JavaScript's inbuilt .flatMap()
method you can chain .map()
and .flatten()
like you have:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
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);
<!-- language: lang-html -->
<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>
<!-- end snippet -->
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:
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
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);
<!-- language: lang-html -->
<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>
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论