How to omit values while using groupby - javascript

I have an array of JSON objects like the following:
{ BlockId: '979',
Day: 'Preliminary/Final Qualifying Day 1',
Event: 'Preliminary Qualifying',
FirstName: 'some',
LastName: 'one',
PlayerPosition: '6',
TimeSlot: '4/21/2018 12:00:00 PM'
}
I wanted to group the objects based on values and did the following:
var result = _(json)
.groupBy('Day')
.mapValues(function(groupedByDay) {
return _(groupedByDay)
.groupBy('Event')
.mapValues(function (groupedByDayAndEvent) {
return _.groupBy(groupedByDayAndEvent, 'BlockId');
})
.value();
})
.value();
Which gave me the following:
{"Preliminary/Final Qualifying Day 1":
{"Preliminary Qualifying":
{ "977":[{"BlockId":"977",
"Day":"Preliminary/Final Qualifying Day 1",
"Event":"Preliminary Qualifying",
"FirstName":"some",
"LastName":"one",
"PlayerPosition":"0",
"TimeSlot":"4/21/2018 9:00:00 AM"
}]
}
}
}
I'd like to remove the fields that I grouped by. Namely: BlockId, Day and Event. Any ideas on how I could omit these values with the code I presented? I'm lost :(
EDIT:
It seems that I forgot that _.omit creates a copy of the object without the fields given... I came to this solution that I don't believe is efficient at all. Any suggestions to make it better?
for(var day in result) {
for(var event in result[day]) {
for(var block in result[day][event]) {
for(var item in result[day][event][block]) {
delete result[day][event][block][item].BlockId;
delete result[day][event][block][item].Day;
delete result[day][event][block][item].Event;
}
}
}
}

use _omit in the deepest level
let myArr = [{
"BlockId": "979",
"Day": "Preliminary/Final Qualifying Day 1",
"Event": "Preliminary Qualifying",
"FirstName": "Robert",
"LastName": "Oristaglio",
"PlayerPosition": "6",
"TimeSlot": "4/21/2018 12:00:00 PM"
}]
var result = _(myArr)
.groupBy('Day')
.mapValues(function(groupedByDay) {
return _(groupedByDay)
.groupBy('Event')
.mapValues(function(groupedByDayAndEvent) {
return _(groupedByDayAndEvent)
.groupBy('BlockId')
.mapValues(function(groupedByAll) {
return groupedByAll.map(function(entry) {
return _.omit(entry, ['Day', 'Event', 'BlockId']);
})
})
.value();
})
.value();
})
.value();
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>

Related

Javascript grouping by data from data and returning array of objects

I have 100 objects of data within an array that looks like this:
{
id: "1",
date: "2022/01/01",
name: "John",
},
{
id: "2",
date: "2022/01/02",
name: "Chris",
},
I am trying to return an array of objects by date that also returns the names.
For example:
[
{
date: "2022/01/01",
names: ["John", "Steve"...]
},
{
date: "2022/01/02",
names: ["Chris", "Rob"...]
},
]
I have tried using the reduce method:
const groupedByDate = () =>
data.reduce((itemsIterated, { date, name }) => {
if (!itemsIterated[date]) {
itemsIterated[date] = {
date,
names: [],
};
}
itemsIterated[date].names.push(name);
return itemsIterated;
}, []);
The issue is this gives me array with a key of the date and then the object with date/names but I don't know how to return just the array of objects by date.
The function groupedByDate would return an object like this -
const result = {
'2022/01/01': {
date: "2022/01/01",
names: ["John", "Steve"...]
},
'2022/01/02': {
date: "2022/01/02",
names: ["Chris", "Rob"...]
},
}
However, to retrieve it in the format you need, you would need to make use of Object.values().
Object.values(result);
/*
[
{
date: "2022/01/01",
names: ["John", "Steve"...]
},
{
date: "2022/01/02",
names: ["Chris", "Rob"...]
},
]
*/
NOTE
To learn more about Object.values() - MDN
reduce's second parameter is the initial value of the accumulator. Here, we would need to use {} instead of [] in the groupedByDate function.

Pick one line to improve. Beginner efficiency code question

I was given homework on BigO Notation and we are supposed to improve a set of code for each exercise but I am having trouble on the final question. For this, they want us to improve the code efficiency by changing one line of code, two at most. When I run it through VSCode, it tells me that .values does not exist on object types so I am assuming to improve efficiency, it has something to do with that line but not sure what to change.
let myMovies = [
{
"title":"Rush Hour 2",
"year":2001,
"cast":[
"Jackie Chan",
"Chris Tucker"
],
"genres":[
"Comedy"
]
},
{
"title":"The Safety of Objects",
"year":2001,
"cast":[
"Glenn Close",
"Dermot Mulroney",
"Patricia Clarkson"
],
"genres":[
"Drama"
]
},
{
"title":"Rush Hour 2",
"year":2001,
"cast":[
"Jackie Chan",
"Chris Tucker"
],
"genres":[
"Comedy"
]
},
//etc...
]
function removeDuplicates(movies) {
let indexedMovies = {};
movies.forEach( (movie) => {
if (Object.keys(indexedMovies).indexOf(movie.title) < 0) {
indexedMovies[movie.title] = movie;
}
})
return indexedMovies.values();
}
let fixedMovies = removeDuplicates(myMovies);
I think the idea is that you would take this section of code:
movies.forEach( (movie) => {
if (Object.keys(indexedMovies).indexOf(movie.title) < 0) {
indexedMovies[movie.title] = movie;
}
})
And replace it with
movies.forEach( (movie) => {
indexedMovies[movie.title] = movie;
})
Because when you do indexedMovies[movie.title] it will replace any existing "duplicate", so there's no need to check for it explicitly with all the code in the if statement, which is pretty inefficient on its own -- creating an array just to linearly search for an item in that array.
1) There is no need to search for existing movie title as
Object.keys(indexedMovies).indexOf(movie.title) < 0
since it will take O(n) and increase the complexity, You can directly check for existence as(if you want to)
!indexedMovies[movie.title]
Any even you can eliminate this process and directly assign movie
indexedMovies[movie.title] = movie;
2) There is not such method on values() on indexedMovies object. Either return indexedMovies object or its values as
Object.values(indexedMovies)
let myMovies = [{
title: "Rush Hour 2",
year: 2001,
cast: ["Jackie Chan", "Chris Tucker"],
genres: ["Comedy"],
},
{
title: "The Safety of Objects",
year: 2001,
cast: ["Glenn Close", "Dermot Mulroney", "Patricia Clarkson"],
genres: ["Drama"],
},
{
title: "Rush Hour 2",
year: 2001,
cast: ["Jackie Chan", "Chris Tucker"],
genres: ["Comedy"],
},
//etc...
];
function removeDuplicates(movies) {
let indexedMovies = {};
movies.forEach((movie) => indexedMovies[movie.title] = movie);
return Object.values(indexedMovies);
}
let fixedMovies = removeDuplicates(myMovies);
console.log(fixedMovies);
/* This is not a part of answer. It is just to give the output fill height. So IGNORE IT */
.as-console-wrapper {
max-height: 100% !important;
top: 0;
}

Reformat list to grouped structure

I use knockout with SharePoint, I get data using getJSON query to the list of all news. I need to create a sidebar with chronological grouped list with following structure: Year -> Month -> News title.
I already set up my view and can display all data in the format that's in initialData set.
Could you please help me to reformat this list:
var obj = [{
"Year":"2018",
"Month":"January",
"Title":"News 18 January 2018"
},
{...}];
to this view:
var initialData =
[{
Year: "2018",
Months: [{
Month: "January",
News: [
{Title: "News 18 January 2018"},
{Title: "News 23 January 2018"}]
},
{Month: "February",
News: [{...}]},
]
},{ Year: "2017", Months: [{...}]
}]
I tried Object.keys with forEach loop, for loop, but totally confused.
You can't solve this by using just a single forEach loop or Object.keys. You will have to group objects on multiple keys and in multiple levels.
First, build an object with years and months as keys, to be able to find the matching arrays for each object quickly. Then expand this key-value map to a nested array.
var years = {};
obj.forEach(function(o) {
years[o.Year] = years[o.Year] || { months: {} };
years[o.Year].months[o.Month] = years[o.Year].months[o.Month] || [];
years[o.Year].months[o.Month].push(o.Title);
});
var initialData = Object.keys(years).map(function(year) {
return {
Year: year,
Months: Object.keys(years[year].months).map(function(month) {
return {
Month: month,
News: years[year].months[month].map(function(title) {
return {
Title: title,
};
})
};
})
};
});

Iterating through array produced by MongoDB aggregation query

Good afternoon all,
I am having a really tough time working with aggregation queries in MongoDB 3.4. I have a problem that is asking me to do push the results of my aggregation query into an empty array called categories which I have been able to do successfully using this code:
var categories = [];
database.collection("item").aggregate([{
$group : {
_id : "$category",
num : {$sum : 1}
}},
{$sort:{_id:1}}]).toArray(function(err, data){
categories.push(...data);
callback(categories);
console.log(categories);
})
}
categories looks like this:
[ { _id: 'Apparel', num: 6 },
{ _id: 'Books', num: 3 },
{ _id: 'Electronics', num: 3 },
{ _id: 'Kitchen', num: 3 },
{ _id: 'Office', num: 2 },
{ _id: 'Stickers', num: 2 },
{ _id: 'Swag', num: 2 },
{ _id: 'Umbrellas', num: 2 } ]
Next I have the following task:
In addition to the categories created by your aggregation query,
include a document for category "All" in the array of categories
passed to the callback. The "All" category should contain the total
number of items across all categories as its value for "num". The
most efficient way to calculate this value is to iterate through
the array of categories produced by your aggregation query, summing
counts of items in each category.
The problem is that it seems like inside my .toArray() method the data parameter sometimes acts like an array and sometimes not. For example if I wanted to add perhaps just the value of the num key to the categories array like so: categories.push(...data["num"]) I get an error stating undefined is not iterable.
Since I cannot iterate over each data.num key I cannot extract it's value and add it to a running total of all data.num values.
What am I not understanding about what is going on here?
You don't need to use application logic to group data, mongoDB aggregation is made for this task. Add another $group to your query with a new field All that $sum your $num field and $push all documents to a new field called categories :
db.item.aggregate([{
$group: {
_id: "$category",
num: { $sum: 1 }
}
}, { $sort: { _id: 1 } }, {
$group: {
_id: 1,
All: { $sum: "$num" },
categories: {
$push: {
_id: "$_id",
num: "$num"
}
}
}
}])
It gives :
{
"_id": 1,
"All": 23,
"categories": [{
"_id": "Swag",
"num": 2
}, {
"_id": "Office",
"num": 2
}, {
"_id": "Stickers",
"num": 2
}, {
"_id": "Apparel",
"num": 6
}, {
"_id": "Umbrellas",
"num": 2
}, {
"_id": "Kitchen",
"num": 3
}, {
"_id": "Books",
"num": 3
}, {
"_id": "Electronics",
"num": 3
}]
}
For consuming the output, data is an array, to access the first element use data[0] :
var categories = [];
database.collection("item").aggregate([{
$group: {
_id: "$category",
num: { $sum: 1 }
}
}, { $sort: { _id: 1 } }, {
$group: {
_id: 1,
All: { $sum: "$num" },
categories: {
$push: {
_id: "$_id",
num: "$num"
}
}
}
}]).toArray(function(err, data) {
var totalCount = data[0]["All"];
console.log("total count is " + totalCount);
categories = data[0]["categories"];
for (var i = 0; i < categories.length; i++) {
console.log("category : " + categories[i]._id + " | count : " + categories[i].num);
}
})
What I wanted to achieve was pushing or unshifting as we'll see in a moment an object that looked like this into my categories array:
var allCategory = {
_id: "All",
num: [sum of all data.num values]
}
I ended up messing with .reduce() method and used it on the categories array. I got lucky through some console.log-ing and ended up making this:
var categories = [];
database.collection("item").aggregate([{
$group : {
_id : "$category",
num : {$sum : 1}
}},
{$sort:{_id:1}}]).toArray(function(err, data){
categories.push(...data);
var sum = categories.reduce(function(acc, val){
// console.log(acc, val["num"])
return acc + val["num"]
},0);
var allCategory = {
_id: "All",
num: sum
}
categories.unshift(allCategory)
callback(categories);
})
First I use a spread operator to push all the objects from data into categories. Then declare sum which runs .reduce() on categories returning the accumulation of val["num"] which is really data.num (console log is life). I create the allCategory document/object then use .unshift to place it at the beginning of my categories array (this placement was a requirement) then use my callback.
I think it's a hacky way of accomplishing my goal and I had to go through some trial and error as to the correct order of methods and variables in my .toArray(). Yet it worked and I learned something. Thanks for the help #Bertrand Martel .

Sort an array of Objects by a value with lodash or JS

Trying to sort an array of objects:
mainObject = {
august: [],
december: []
}
Each array contains multiple objects with different dates and ticket info.
mainObject = {
august: [
{ date: "2016-08-22", ticket: "parking" },
{ date: "2016-08-14", ticket: "speeding" },
{ date: "2016-08-29", ticket: "parking" }
],
december: [
{ date: "2016-12-06", ticket: "parking" },
{ date: "2016-12-30", ticket: "parking" },
]
}
I print each ticket into my template, but would like them sorted ascending by date. In this case, I'd like to sort the information, before passing this data to the template, how can I do with with Lodash or otherwise?
I have attempted variations of:
_.sortBy(mainObject, [function(d) { return d.date; }]); // date => undefined
I've also tried getting deeper into the object first:
_.forEach(mainObject, function(month) {
_.sortBy(mainObject, [function(d) { return d.date; }]);
}
The above doesn't work either.
You could sort by date as string in plain Javascript.
var mainObject = { august: [ { date: "2016-08-22", ticket: "parking" }, { date: "2016-08-14", ticket: "speeding" }, { date: "2016-08-29", ticket: "parking" }], december: [{ date: "2016-12-06", ticket: "parking" }, { date: "2016-12-30", ticket: "parking" }]};
Object.keys(mainObject).forEach(function (k) {
mainObject[k].sort(function (a, b) {
return a.date.localeCompare(b.date);
});
});
console.log(mainObject);
.as-console-wrapper { max-height: 100% !important; top: 0; }
With Lodash, you can use _.mapValues() to transform ("map") the values of the august and december keys into a correctly sorted list like so:
const sortedMainObj = _.mapValues(mainObject, (tickets) => {
return _.sortBy(tickets, [(ticket) => ticket.date]);
});
console.log(sortedMainObj);

Categories

Resources