How does underscorejs reduce work?
It's simple to get
_.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0); (the result is 6).
But how do the other optional parameters work? In the docs it says:
Memo is the initial state of the reduction, and each successive step of it should be returned by iterator. The iterator is passed four arguments: the memo, then the value and index (or key) of the iteration, and finally a reference to the entire list."
But I don't understand. I tried to use reduce for the following problem, and I couldn't figure it out:
var input = [{"score": 2, "name": "Jon", "venue": "A"}, {"score": 3, "name": "Jeff", "venue":"A"}, {"score": 4, "name": "Jon", "venue":"B"}, {"score": 4, "name": "Jeff", "venue":"B"}];
var output = [{"score": 6, "name":"Jon", "venue": ["A", "B"]}, {"score": 7, "name":"Jeff", "venue": ["A", "B"]}];
How can I get as output using _reduce for input? And it will really helpful how it works inside reduce.
Reduce takes a list of values, and reduces it to a single value. What you are trying is not just reduce. You are trying to first group (by name) and then reduce each group. A possible implementation would be something like this, where you first group, and then map each group to a reduce operation that accumulates the score and appends the venue.
var input = [
{"score": 2, "name": "Jon", "venue": "A"},
{"score": 3, "name": "Jeff", "venue":"A"},
{"score": 4, "name": "Jon", "venue":"B"},
{"score": 4, "name": "Jeff", "venue":"B"}];
var output = _.map(_.groupBy(input, "name"), function(group, name) {
return _.reduce(group, function(memo, elem) {
memo.score += elem.score;
memo.venue.push(elem.venue);
return memo;
},
{ name: name, score: 0, venue: [] });
});
Instead of reduce, try using plain and simple each in this way:
_.each admits a third parameter for a context. So, for example:
var input = ["Alice", "Bob", "Charlie"];
var context = {};
_.each(input, function(o,i) { this[i] = o; }, context);
console.log(context) //=> {1: "Alice", 2: "Bob", 3: "Charlie"}
Ruby has a new method call each_with_object similar to reduce for that exact pattern, where the memo is not necessarily the value return (in fact, my function is not returning anything!); but underscore simplifies its API design simply admitting a context for the function as the third parameter.
You can use reduce, but you would need to return the memo each time, making it a bit less elegant IMHO.
Related
Still learning here. This one as simple as it seems, has beaten me up. I have managed to get the answer. But, when I do, I am getting more than one name coming out of my if statement. I used a new set to remove the duplicate names in the names array. But frankly, that seems lazy to me and I feel something can be done that is better.
Can someone show me some ways I'm missing to better go through this problem? I tried flattening and that didn't work either. Thank you for your help!
Here are the directions given to me:
Create a function passingStudents that accepts an array of student objects.
It should iterate through the list of students and return an array of the names of all the students who have an average grade of at least 70.
function passingStudents(students) {
const names = [];
students.forEach(student => {
student.grades.forEach(grade => {
if(grade.score >= 70) {
names.push(student.name);
}
});
});
let uniqueChars = [...new Set(names)];
return uniqueChars;
}
//Uncomment the lines below to test your function:
var students = [
{
"name": "Marco",
"id": 12345,
"grades": [{"id": 0, "score": 65}, {"id": 1, "score": 75}, {"id": 2, "score": 85}]
},
{
"name": "Donna",
"id": 55555,
"grades": [{"id": 0, "score": 100}, {"id": 1, "score": 100}, {"id": 2, "score": 100}]
},
{
"name": "Jukay",
"id": 94110,
"grades": [{"id": 0, "score": 65}, {"id": 1, "score": 60}, {"id": 2, "score": 65}]
}
];
console.log(passingStudents(students)); // => [ 'Marco', 'Donna' ]
The reason your solution introduces multiple people in the names array is that it adds the name for each grade they have that is at least 70. This means it will return a student if any of their grades is at least 70 rather than if their average grade is at least 70. While your solution may pass the test case provided, it will not work for every case in general. A better solution is to average the grades by using reduce and filter the original list of students based on whether their average is above 70. Then you can map the filtered students to their name.
function passingStudents(students) {
return students
.filter((student) => {
const { grades } = student;
const average = grades.reduce((sum, grade) => sum + grade.score, 0) / grades.length;
return average >= 70;
})
.map((student) => student.name);
}
I am attempting to push one index of my array into it's own array. I am attempting this by using the forEach() method.
I have an array that is nested inside of an array revData that has multiple indexes inside of its array. I expecting to push out only index 5 of the array into its own array so I can graph the data.
At the moment using doing my forEach method, my newArr only has the first index 5 five times.
My expected out come is to have the newArr have 3 results coming from the values of index 5 from revData such as :
newArr = [ 24343.1 , 44321.0, 43242.8 ]
Here is an example of my code :
let revData = [
[1, 1, 1, "22", "online stores", 24343.1 ],
[2, 2 ,2, "13", "retail stores", 44321.0],
[ 3, 3, 3, "7", "walk ins", 43242.8]
]
const newArr = []
revData[0].forEach(function () {
newArr.push(revData[0][5])
})
console.log(newArr)
Probably it should be like this
let revData = [
[1, 1, 1, "22", "online stores", 24343.1],
[2, 2, 2, "13", "retail stores", 44321.0],
[3, 3, 3, "7", "walk ins", 43242.8],
];
const newArr = [];
revData.forEach(function (child) {
newArr.push(child[5]);
});
console.log(newArr);
Or you can use map instead
const newArr = revData.map((child) => child[5]);
console.log(newArr);
You are iterating over revData[0] so you won't get the other items. Also, during the iteration you always read the value in revData[0]. You should use the first parameter of the forEach callback, like this:
let revData = [
[1, 1, 1, "22", "online stores", 24343.1],
[2, 2, 2, "13", "retail stores", 44321.0],
[3, 3, 3, "7", "walk ins", 43242.8]
]
const newArr = []
revData.forEach(function(item) {
newArr.push(item[5])
})
console.log(newArr)
One available approach here is to use a for... of loop.
Everyone will have their own preferred approach, but I generally find that, for syntactic clarity, I favour for...of loops over forEach.
Working Example:
let revData = [
[1, 1, 1, "22", "online stores", 24343.1 ],
[2, 2 ,2, "13", "retail stores", 44321.0],
[3, 3, 3, "7", "walk ins", 43242.8]
];
const newArr = [];
for (arrayElement of revData) {
newArr.push(arrayElement[5]);
}
console.log(newArr);
The language here is a little off and I'll do my best to answer.
When you say "push out" I am assuming you want to remove the element out of the array. The correct term for removing an element from the array is "pop". "Pop" is generally related to removing an element an array, while "push" is generally related to adding an element to an array.
Also Do not name your newArr as a const. const is meant to indicate values that never change. By naming your newArr as a const, it cannot change.
Now to answer your question, I can see what you are trying to do, but your syntax is a little wrong.
let revData = [
[1, 1, 1, "22", "online stores", 24343.1 ],
[2, 2 ,2, "13", "retail stores", 44321.0],
[ 3, 3, 3, "7", "walk ins", 43242.8]
]
let newArr = []
revData.forEach(function(item) {
newArr.push(item[5])
})
console.log(newArr)
https://codepen.io/junghole/pen/BajdZPq?editors=0012
I suggest that you gain a better understanding of 2d arrays and for each loops. A good resource is geeks for geeks, or simply googling. Good Luck!
I recently got interested on using the spread operator syntax, so I tried some examples, I have this example of array:
var entities = [
{
"id": 1,
"age": 33,
"hobby": "games"
},
{
"id": 2,
"age": 28,
"hobby": "chess"
},
{
"id": 3,
"age": 21,
"hobby": "comics"
},
{
"age": 23,
"hobby": "games"
}
]
Then, to update all hobbies at "once" I do the following:
entities.forEach(function(entity, index) {
this[index] = {...entity, hobby: "Some String to update all hobbies"};
}, entities);
console.log(entities)
Which works but I was wondering if there's a more efficient or shorter way to achieve it while using the spread operator. Any suggestions?
EDIT:
forEach is not necessary for me, or even do it in that way, I was curious on whether the spread syntax could be used (or not) to update nested values
The spread operator doesn't really help when you're updating the list, like you do in your example. It's easier to just update the property of each object:
var entities = [ { "id": 1, "age": 33, "hobby": "games" }, { "id": 2, "age": 28, "hobby": "chess" }, { "id": 3, "age": 21, "hobby": "comics" }, { "age": 23, "hobby": "games" } ]
entities.forEach(entity => {
entity.hobby = "Some String to update all hobbies";
});
console.log(entities)
The spread operator is useful if you want to create copies of objects, like you might want to do in a .map:
var entities = [ { "id": 1, "age": 33, "hobby": "games" }, { "id": 2, "age": 28, "hobby": "chess" }, { "id": 3, "age": 21, "hobby": "comics" }, { "age": 23, "hobby": "games" } ]
const newEntities = entities.map(entity =>
({...entity, hobby: "Some String to update all hobbies"})
);
console.log(newEntities)
The spread operator will iterate over all keys in the object to copy them and their values into the new object. If you want more efficiency, don't use the spread operator. Just assign directly to each object as you iterate over the list:
entity.hobby = "Some String to update all hobbies"
Note that this modifies the object in the existing array. So you don't need to assign this[index]. Alternatively, you can use map() instead of foreach() to return a new array that is created from the existing array.
Not sure if spread operator is really needed for what you are doing?
You can also look into this link for some interesting usage of the spread, Array.from and rest operator.
More into just spread operator here.
If you are looking for a fancier/smaller way to write this, here's two, one that uses uses .map and spread to return a copy of entities, and another that uses .forEach and updates the same array entities:
const COMMON_HOBBY = 'Coding';
let entities = [{
"id": 1,
"age": 33,
"hobby": "games"
},
{
"id": 2,
"age": 28,
"hobby": "chess"
}];
// To assign to new array (copy)
let output = entities.map((entity) => ({...entity, hobby: COMMON_HOBBY }));
console.log(output);
// Mutate /edit same array entities
entities.forEach((entity) => entity.hobby = COMMON_HOBBY );
console.log(entities);
I'm practicing how to maniupulate data in JS in this article: http://learnjsdata.com/combine_data.html
var articles = [
{"id": 1, "name": "vacuum cleaner", "weight": 9.9, "price": 89.9, "brand_id": 2},
{"id": 2, "name": "washing machine", "weight": 540, "price": 230, "brand_id": 1},
{"id": 3, "name": "hair dryer", "weight": 1.2, "price": 24.99, "brand_id": 2},
{"id": 4, "name": "super fast laptop", "weight": 400, "price": 899.9, "brand_id": 3}
];
var brands = [
{"id": 1, "name": "SuperKitchen"},
{"id": 2, "name": "HomeSweetHome"}
];
articles.forEach(function(article) {
var result = brands.filter(function(brand){
return brand.id === article.brand_id;
});
delete article.brand_id;
article.brand = (result[0] !== undefined) ? result[0].name : null;
});
I'm confused with the last part: article.brand = (result[0] !== undefined) ? result[0].name : null;
I understand the conditional operation: it wants to have null value if result[0] is not defined. But I'm wondering what result[0] refers to. I thought it would take first object: {"id":2, "name": "HomeSweetHome"} so there should be for loop to iterate all objects in order to see if objects meet the condition? Could you inform me what I'm missing or/and what result[0] refers to?
Thanks,
result[0] will be undefined in case there is no element in result. result is expected to be an array of brands filtered by the filter operation
The filtered array result will have same brand as that of the current article in the outer foreach loop. The filter condition is going to achieve that.
It looks like in this particular case you will get only one element in result array always as there are unique brand ids. It might have more elements in case of duplicated brand ids.
result[0] points to first element in the array result
I'm having a rather large amount of difficulty with trying to remove nested objects from my table, without accidentally deleting all my data in the process (happened three times now, thank god I made copies).
My Object:
{
"value1": thing,
"value2": thing,
"value3": thing,
"roles": {
"1": {
"name": "Dave",
"id": "1"
},
"2": {
"name": "Jeff",
"id": "2"
},
"3": {
"name": "Rick",
"id": "3"
},
"4": {
"name": "Red",
"id": "4"
}
}
}`
I've tried a number of rethink queries, but none have worked thus far. It should be noted that 1, 2, 3, & 4 are variables that can have any amount of numbers, and thus my query must reflect that.
Some attempted queries:
function removeRole(id, roleName) {
let role = `${roleName}`
return this.r.table('guilds').get(id).replace(function(s){
return s.without({roles : {[role] : { "name": role }}})
})
}
function removeRole(id, roleName) {
return this.r.table('guilds').getAll(id).filter(this.r.replace(this.r.row.without(roleName))).run()
}
function removeRole(id, roleName) {
return this.r.table('guilds').get(id)('roles')(roleName).delete()
}
Any assistance is greatly appreciated, and if the question has issues, please let me know. Still rather new to this so feedback is appreciated.
I'm not sure if I understood your intention, but the following query seems to do what you're trying to accomplish:
r.db('test')
.table('test')
.get(id)
.replace((doc) => {
// This expression makes sure that we delete the specified keys only
const roleKeys = doc
.getField('roles')
.values()
// Make sure we have a role name is in the names array
.filter(role => r.expr(names).contains(role.getField('name')))
// This is a bit tricky, and I believe I implemented this in a not efficient
// way probably missing a first-class RethinkDB expression that supports
// such a case out of box. Since we are going to delete by nested dynamic
// ids, RethinkDB requires special syntax to denote nested ids:
// {roles: {ID_1: true, ID_2: true}}
// Well, this is just a JavaScript syntax workaround, so we're building
// such an object dynamically using fold.
.fold({}, (acc, role) => acc.merge(r.object(role.getField('id'), true)));
return doc.without({roles: roleKeys});
})
For example, if names is an array, say ['Jeff', 'Rick'], the nested roleKeys expession will be dynamically evaluated into:
{2: true, 3: true}
that is merged into the roles selector, and the above query will transform the document as follows:
{
"value1": ...,
"value2": ...,
"value3": ...,
"roles": {
"1": {"name": "Dave", "id": "1"},
"4": {"name": "Red", "id": "4"}
}
}