Empty Arrays and Sorting Pushed Keys JavaScript - javascript

I am making a MEAN Stack application, and am attempting to create a database of businesses- which requires an empty array to push new instances of the Business model into.
I then want to sort the index of the businesses based on two of the keys- "name" (alphabetically) and "upVotes".
Here is what I have in my business.service file (client side):
var service = {
create: create,
businesses: [],
upVote: upVote,
showAllBiz: showAllBiz,
};
function showAllBiz(){
$http.get("/api/businesses")
.then(function(res) {
service.businesses = res.data;
}, function(err) {
$log.info(err);
});
}
function create(data) {
return $http({
method: 'POST',
url: '/api/businesses',
data: data,
headers: {
'Content-Type': 'application/json'
}
}).then(function(res) {
service.businesses.push(res.data);
});
}
I also tried to sort() on the back end, with no results. Here is what that looks like:
var Business = require("../models/business");
var nodemailer = require("nodemailer");
var transporter = nodemailer.createTransport();
var firstBy = require("thenby");
function index(req, res) {
if(req.query.search){
Business.find({}).then(function(data) {
var reg = new RegExp(req.query.search, "i");
data = data.filter(function(biz) {
if(reg.test(biz.name)) return biz
})
res.json(data);
}, function(err) {
res.json(err);
});
} else{
Business.find({}).then(function(data) {
res.json(data);
}, function(err) {
res.json(err);
});
}
}
function create(req, res) {
var business = new Business();
console.log(req.body);
business.name = req.body.name;
business.address1 = req.body.address1;
business.address2 = req.body.address2;
business.email = req.body.email;
business.twitterHandle = req.body.twitterHandle;
business.upVote = req.body.upVote;
business.save(function(err, savedBusiness) {
if (err) {
res.send(err)
}
res.json(savedBusiness);
});
I am getting stuck on the fact that I need the empty array for the new instances (in my services), but I also need to make use of the objects within the array in the .sort() method to access the keys (which I would like to sort).
I played with Teun's thenBy.js but was a bit out of my depth.
I have googled sorting arrays and arrays of objects, but these are all examples of sorting information that exists, not information that does not yet exist, thus necessitating the empty array.

Assuming I understand the question, here's the gist of it with some made up data. I left the sort function a bit more verbose to (hopefully) increase readability.
Note that in the sorter method we are comparing first name, then upVotes, then only returning 0 afterwards to signify object equality. Since we are comparing name first followed by upVotes, this is the equivalent of sorting by name, then upVotes.
let arr = [
{ id: 1, name: 'Dominos', upVotes: 21 },
{ id: 2, name: 'Pizza Hut', upVotes: 31 },
{ id: 3, name: 'Pizza Hut', upVotes: 35 },
{ id: 4, name: 'Dominos', upVotes: 61 },
{ id: 5, name: 'Dominos', upVotes: 2 },
{ id: 6, name: 'Pizza Hut', upVotes: 25 },
{ id: 7, name: 'Dominos', upVotes: 10 },
{ id: 8, name: 'Pizza Hut', upVotes: 3 }
];
function sorter(a, b) {
if (a.name > b.name)
return 1;
if (a.name < b.name)
return -1;
if (a.upVotes > b.upVotes)
return 1;
if (a.upVotes < b.upVotes)
return -1;
return 0;
}
arr.sort(sorter);
console.log(arr);
/* [ { id: 7, name: 'Dominos', upVotes: 2 },
{ id: 8, name: 'Dominos', upVotes: 10 },
{ id: 5, name: 'Dominos', upVotes: 21 },
{ id: 6, name: 'Dominos', upVotes: 61 },
{ id: 4, name: 'Pizza Hut', upVotes: 3 },
{ id: 3, name: 'Pizza Hut', upVotes: 25 },
{ id: 1, name: 'Pizza Hut', upVotes: 31 },
{ id: 2, name: 'Pizza Hut', upVotes: 35 } ] */
[].sort(sorter); // no errors. if the array is empty, the callback will never be run.

Related

Compare two arrays of objects and merge some fields

I'm working with Angular and RxJs and I have two arrays of objects. I need to change one specific field of the first array, if the second one has the field with the same value (all of the four fields has different names). I did it with nested loops, but I need to find a better solution, my code is down below
My solution is working, but it's not the best, because arrays can be really large - so the code will work slow. If there's 1000 items in each array, it will be 1000000 iterations - that's why I need to find a better solution. I got advice to use multiple consecutive loops, but I don't really get how to use it here
this.api
.getFirstArray()
.pipe(
mergeMap((firstArray) =>
this._secondApi.getSecondArray().pipe(
map((secondArray) => {
for (const item2 of secondArray) {
for (const item1 of firstArray) {
if (item1.someField === item2.otherField)
item1.someOtherField = item2.anotherField;
}
}
return firstArray;
}),
),
),
)
.subscribe((value) => {
this.gridApi?.setRowData(value);
});
So for example my data is
firstArray: [
{ id: 445; name: 'test' },
{ id: 4355; name: 'test1' },
{ id: 234_234; name: 'test2' },
];
secondArray: [
{ firstName: 'test3'; newId: 445 },
{ firstName: 'test5'; newId: 2 },
{ firstName: 'test6'; newId: 234_234 },
];
And the result should be
result: [{ id: 445; name: 'test3' }, { id: 4355; name: 'test1' }, { id: 234_234; name: 'test6' }];
Note: the ids of the first array objects may be repeated - all of the objects names need to be updated
here is the working example of your problem, may be it will help you.
let firstArray = [
{ id: 445, name: 'test' },
{ id: 4355, name: 'test1' },
{ id: '234_234', name: 'test2' },
];
let secondArray = [
{ firstName: 'test3', newId: 445 },
{ firstName: 'test5', newId: 2 },
{ firstName: 'test6', newId: '234_234' },
];
secondArray.forEach(sec => {
let see = firstArray.findIndex(first => first.id === sec.newId);
if (see > -1) {
firstArray[see].name = sec.firstName
}
})
console.log(firstArray)
You still end up with O(N²) complexity (there are two nested loops that he wants to avoid).
Instead, You can use map
const firstArray = [
{ id: 445, name: 'test' },
{ id: 4355, name: 'test1' },
{ id: '234_234', name: 'test2' },
];
const secondArray = [
{ firstName: 'test3', newId: 445 },
{ firstName: 'test5', newId: 2 },
{ firstName: 'test6', newId: '234_234' },
];
const secondMap = new Map();
secondArray.forEach((item) => {
secondMap.set(item.newId, item.firstName);
});
for (const item of firstArray) {
if (secondMap.has(item.id)) {
item.name = secondMap.get(item.id);
}
}
console.log(firstArray)

get total price from array of objects per specific user using .reduce in javascript

I have this array of objects
const items = [
{
id: '121',
itemDate: '2022-04-28',
itemName: 'testname1',
itemCategory: 'Category A',
itemPrice: { price: '100', currency: 'GBP' },
createdBy: {
username: 'user1',
name: 'Name 1',
date: '2022-04-28T22:41:59',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname2',
itemCategory: 'Category B',
itemPrice: { price: '100', currency: 'GBP' },
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:42:44',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname3',
itemCategory: 'Category C',
itemPrice: { price: '200', currency: 'GBP' },
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:43:16',
},
},
]
Code I'm using:
items.reduce(function (c, x) {
if (!c[x.createdBy.username])
c[x.createdBy.username] = {
username: x.createdBy.username,
total: 0,
}
c[x.createdBy.username].total += Number(x.itemPrice.price)
return c
}, [])
This part gives me the following output:
items :>> [
user1: { username: 'user1', total: 100},
user2: { username: 'user2', total: 300}
]
So I tried this to get rid of the object names:
let output = []
let totalSum = 0
for (const username in items) {
let temp = {
username: items[username].username,
total: items[username].total,
}
totalSum = totalSum + items[username].total
output.push(temp)
}
output.push({ username: 'allUsers', total: totalSum })
return output
And final output is as I want it now:
output :>> [
{ username: 'user1', total: 100 },
{ username: 'user2', total: 300 },
{ username: 'allUsers', total: 400}
]
My two questions...
Is there a way to update the .reduce part so that I'd get an object without the name at the beggining, without having to use the for loop?
Is there also a way to implement the part that would sum up all the totals?
Thank you
Code Sample (without comments/description)
const groupAndAdd = arr => (
Object.values(
arr.reduce(
(acc, {createdBy : {username}, itemPrice: {price}}) => {
acc.allUsers ??= { username: 'allUsers', total: 0};
acc.allUsers.total += +price;
if (username in acc) {
acc[username].total += +price;
} else {
acc[username] = {username, total: +price};
}
return acc;
},
{}
)
)
);
Presented below is a working demo to achieve the desired objective, with notes/comments to help understand.
Code Snippet
// method to group by user and sum prices
const groupAndAdd = arr => (
// extract the values from the intermediate result-object
Object.values(
arr.reduce( // generate result as object
(acc, {createdBy : {username}, itemPrice: {price}}) => {
// above line uses de-structuring to directly access username, price
// below uses logical nullish assignment to set-up "allUsers"
acc.allUsers ??= { username: 'allUsers', total: 0};
// accumulate the "price" to the all-users "total"
acc.allUsers.total += +price;
// if "acc" (accumulator) has "username", simply add price to total
if (username in acc) {
acc[username].total += +price;
} else {
// create an object for the "username" with initial total as "price"
acc[username] = {username, total: +price};
}
// always return the "acc" accumulator for ".reduce()"
return acc;
},
{} // initially set the "acc" to empty object
)
) // if required, use ".sort()" to move the all-users to last position in array
);
const items = [{
id: '121',
itemDate: '2022-04-28',
itemName: 'testname1',
itemCategory: 'Category A',
itemPrice: {
price: '100',
currency: 'GBP'
},
createdBy: {
username: 'user1',
name: 'Name 1',
date: '2022-04-28T22:41:59',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname2',
itemCategory: 'Category B',
itemPrice: {
price: '100',
currency: 'GBP'
},
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:42:44',
},
},
{
id: '122',
itemDate: '2022-04-28',
itemName: 'testname3',
itemCategory: 'Category C',
itemPrice: {
price: '200',
currency: 'GBP'
},
createdBy: {
username: 'user2',
name: 'Name 2',
date: '2022-04-28T22:43:16',
},
},
];
console.log('group and add prices per user: ', groupAndAdd(items));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
Please consider reading: What to do when my question is answered
Thank you !
For your first question, you're initialising correctly as an array, but you're using just object. Two ways you can do this.
First Option
let something = items.reduce(function(c, x) {
if (!c[x.createdBy.username])
c[x.createdBy.username] = {
username: x.createdBy.username,
total: 0,
}
c[x.createdBy.username].total += Number(x.itemPrice.price)
return c
}, {});
something = Object.values(something);
Second Option
I was thinking of using just push, but seems it's not possible, so the above is the only option.
Using push is possible, but it'll get too complicated by checking with find and updating the correct array element.
For your second question of summing up all the totals, you can use the simple syntax of:
const sum = arr.reduce((a, c) => a + c, 0);
This is the minimum code you need for array of numbers to be summed.

How to improve time complexity of this snippet?

I am trying to improve the time complexity and quality of the code snippet below.
I am iterating through one array to check if the element this array exists in the object, should this be true it should return the name matching the element id in the object.
how can I do this without having a nested loop?
Can someone tell me what I can do to make this algo better, please?
Thank you all in advance.
let genres = [28, 12, 878];
data = {
genres: [
{
id: 28,
name: 'Action',
},
{
id: 12,
name: 'Adventure',
},
{
id: 16,
name: 'Animation',
},
{
id: 35,
name: 'Comedy',
},
{
id: 80,
name: 'Crime',
},
{
id: 99,
name: 'Documentary',
},
{
id: 18,
name: 'Drama',
},
{
id: 10751,
name: 'Family',
},
{
id: 14,
name: 'Fantasy',
},
{
id: 36,
name: 'History',
},
{
id: 27,
name: 'Horror',
},
{
id: 10402,
name: 'Music',
},
{
id: 9648,
name: 'Mystery',
},
{
id: 10749,
name: 'Romance',
},
{
id: 878,
name: 'Science Fiction',
},
{
id: 10770,
name: 'TV Movie',
},
{
id: 53,
name: 'Thriller',
},
{
id: 10752,
name: 'War',
},
{
id: 37,
name: 'Western',
},
],
};
const getGenreName = () => {
let result = [];
for (let genre of data.genres) {
//console.log("genre", genre.name)
for (let id of genres) {
//console.log('id',genres[i])
if (id === genre.id) result.push(genre.name);
}
}
console.log(result);
};
getGenreName();
You can use reduce and includes as others have already shown. This will make the code a bit cleaner, but not change the overall runtime complexity. To improve runtime complexity you may need to use a different data structure.
For instance instead of
let genres = [1,2,3,4];
as a simple array, you could use a Set, which has a better lookup performance.
let genres = new Set([1,2,3,4]);
Then you can use this as follows
let result = data.genres
.filter(g => genres.has(g.id))
.map(g => g.name);
and won't need any explict for loops
The simplest improvement would probably be converting genres to a Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
and use the has method to check if each id in the data is a member of the set of chosen genres.
You can also convert the data to a map with the ids as the keys in order to look up by id quickly instead of looping, but that is only faster if the data is reused many times.
JavaScript #reduce in the example outlined below would have O(n) time complexity. This only loops through the array once. We could use filter, and map but it would result in us having to loop through the array twice.
const getGenreName = () => {
const genreSet = new Set(genres);
return data.genres.reduce((accumulator, { id, name }) => {
if (genreSet.has(id)) accumulator.push(name);
return accumulator;
}, []);
};
console.log(getGenreName()); // [ 'Action', 'Adventure', 'Science Fiction' ]
We are initializing the reducer to start with the array [], or an empty array, and then checking to see if the genre property of the object is included in the genres array, if it isn't, return the accumulator, if it is, append it to the end of the accumulator and return it.
You wanted this in one loop, so here it is:
let result = [];
data.genres.forEach(function (e) {
if (genres.includes(e.id)) result.push(e.name);
});
console.log(result);
In case you were wondering about forEach, here's a very good reference: https://www.w3schools.com/jsref/jsref_foreach.asp
The current time complexity is O(MN) where M is the length of data.genres and N is the length of genres.
Time complexity in JavaScript depends on which engine you use, but in most cases you can use a Map to reduce this time complexity to O(max{N,M}):
const getGenreName = () => {
const dataGenresMap = new Map( // O(M)
data.genres.map(({id,...params}) => [id,params]) // O(M)
)
let result = []
for (let id of genres) { // O(N)
if (dataGenresMap.has(id)) result.push(dataGenresMap.get(id).name) // O(1)
}
console.log(result)
}
If you might be doing this more than once then I'd recommend using a Map. By creating a hash map, retrieving genre names per id is much more performant.
let genres = [28, 12, 878];
data = {
genres: [
{
id: 28,
name: 'Action',
},
{
id: 12,
name: 'Adventure',
},
{
id: 16,
name: 'Animation',
},
{
id: 35,
name: 'Comedy',
},
{
id: 80,
name: 'Crime',
},
{
id: 99,
name: 'Documentary',
},
{
id: 18,
name: 'Drama',
},
{
id: 10751,
name: 'Family',
},
{
id: 14,
name: 'Fantasy',
},
{
id: 36,
name: 'History',
},
{
id: 27,
name: 'Horror',
},
{
id: 10402,
name: 'Music',
},
{
id: 9648,
name: 'Mystery',
},
{
id: 10749,
name: 'Romance',
},
{
id: 878,
name: 'Science Fiction',
},
{
id: 10770,
name: 'TV Movie',
},
{
id: 53,
name: 'Thriller',
},
{
id: 10752,
name: 'War',
},
{
id: 37,
name: 'Western',
},
],
};
const genreById = new Map ();
data.genres.forEach(({id, name}) => genreById.set(id, name));
const pushMapValueIfTruthy = map => array => key => {
const val = map.get(key);
if (val) {
array.push(val);
}
};
/** function that takes an array, then id, and pushes corresponding name (if exists) into the array. */
const pushGenreNaneIfExists = pushMapValueIfTruthy(genreById);
const getGenreNames = (ids) => {
result = [];
ids.forEach(pushGenreNaneIfExists(result));
return result;
};
console.log(getGenreNames(genres));

How to filter these arrays in JavaScript?

I have the following arrays:
const players = [{
id: 1,
ip: '198.199.162',
nickname: 'BIG BOSS'
}, {
id: 2,
ip: '198.199.162',
nickname: 'CHICKEN LITTLE'
}, {
id: 3,
ip: '198.199.162',
nickname: 'MR T'
}, {
id: 4,
ip: '198.199.162',
nickname: 'DONUT KING'
}];
const connectedRooms = [{
playerId: 4,
roomId: 1,
playedTime: 300
}, {
playerId: 2,
roomId: 1,
playedTime: 30
}, {
playerId: 1,
roomId: 2,
playedTime: 10
}, {
playerId: 3,
roomId: 3,
playedTime: 45
},
{
playerId: 1,
roomId: 3,
playedTime: 15
}
const rooms = [{
id: 1,
name: 'READY SET GO'
}, {
id: 2,
name: 'CHICKEN WINNER'
}, {
id: 3,
name: 'BURGER TIME'
}];
I need to filter those arrays in a way that I could tell what room has the player played in. My desired output would be the following:
{
key: BIG BOSS,
value: [CHICKEN WINNER, BURGER TIME]
}
So far my code is only listing the IDs of the rooms. I have tried ES6 filter but I only get the room Id as well.
let result = [];
for (let i = 0; i < players.length; i++) {
for (let j = 0; j < connectedRooms.length; j++) {
if (connectedRooms[j].playerId === players[i].id) {
result.push(connectedRooms[j].roomId);
}
}
}
How else could I filter these arrays to obtain the desired output?
result = [];
players.forEach(player=>{
value=[];
rooms.forEach(room=>{
if(connectedRooms.filter(connectedRoom=>connectedRoom.playerId==player.id&&connectedRoom.roomId==room.id).length>0) value.push(room.name);
})
if(value.length>0) result.push({key:player.nickname,value:value});
})
This may help you :
// for each players
players.map((player) => {
return {
key: player.nickname,
value: connectedRooms
// filter connectedRooms where the player is
.filter((connectedRoom) => {
return player.id === connectedRoom.playerId;
})
// for each filtered connectedRooms, you search room's datas
.map((connectedRoom) => {
return rooms.filter((room) => {
return room.id === connectedRoom.roomId;
}).pop().name; // i used `pop` assuming your datas can't have players connected to non-existent room
})
};
});
You need to get all room names from the connectedRooms according to the playerId then assign the room names into value property. In the end, getRooms returns an object holding all target data structures by playerId.
const getRoomNames = (playerId) => connectedRooms.reduce((acc, cr) => {
if(cr.playerId === playerId) {
const roomName = rooms.find(room => cr.roomId === room.id)?.name;
acc.push(roomName);
}
return acc;
}, []);
const getRooms = () => players.reduce((acc, player) => {
acc[player.id] = {
key: player.nickname,
value: getRoomNames(player.id)
}
return acc;
}, {});
Please use this code.
let result = {};
const playerId = 1;
const key = players.filter(val => val.id == playerId).map(val => val.nickname);
const value = connectedRooms.filter(val => val.playerId == playerId).map(val => rooms.filter(value => value.id == val.roomId)[0].name);
result = {key, value};
console.log(result);
Here is a solution that loops through each array once, using reduce.
Finds the user id.
Turns rooms into an object with roomObj[id] = name so I can do a look up instead of looping over rooms again and again.
Builds a new array of playerRooms, filtered by playerId, by adding the room name from roomObj.
function getRoomsBasedOn(nickname) {
// 1
let user = (player) => { return player.nickname == nickname };
let id = players.find(user).id; // crashes if nickname not found
// 2
let roomObj = rooms.reduce((obj, room) => (obj[room.id] = room.name, obj) ,{});
// 3
let playerRooms = connectedRooms.reduce((arr, room) => {
return (room.playerId == id) ? arr.concat(roomObj[room.roomId]) : arr;
}, []);
return {
'key': nickname,
'value': playerRooms
};
}
const players = [{
id: 1,
ip: '198.199.162',
nickname: 'BIG BOSS'
}, {
id: 2,
ip: '198.199.162',
nickname: 'CHICKEN LITTLE'
}, {
id: 3,
ip: '198.199.162',
nickname: 'MR T'
}, {
id: 4,
ip: '198.199.162',
nickname: 'DONUT KING'
}];
const connectedRooms = [{
playerId: 4,
roomId: 1,
playedTime: 300
}, {
playerId: 2,
roomId: 1,
playedTime: 30
}, {
playerId: 1,
roomId: 2,
playedTime: 10
}, {
playerId: 3,
roomId: 3,
playedTime: 45
},
{
playerId: 1,
roomId: 3,
playedTime: 15
}];
const rooms = [{
id: 1,
name: 'READY SET GO'
}, {
id: 2,
name: 'CHICKEN WINNER'
}, {
id: 3,
name: 'BURGER TIME'
}];
console.log( getRoomsBasedOn('BIG BOSS') );

Subtract one object from another one with in array

I want to know the efficient way to subtract two object key[amount] values based on the action key.
data = [
{ _id: { token: 'BEEF', action: 'received' }, amount: 4 },
{ _id: { token: 'BEEF', action: 'sent' }, amount: 2 },
{ _id: { token: 'GFUN', action: 'received' }, amount: 9},
{ _id: { token: 'HOT', action: 'received' }, amount: 6 },
{ _id: { token: 'HOT', action: 'sent' }, amount: 4 },
{ _id: { token: 'LINK', action: 'received' }, amount: 8},
{ _id: { token: 'METM', action: 'sent' }, amount: 7 },
{ _id: { token: 'METM', action: 'received' }, amount: 9},
{ _id: { token: 'ORTC', action: 'received' }, amount: 5},
{ _id: { token: 'ORTC', action: 'sent' }, amount: 3 }
]
desired result after calculation
[
{ token: 'BEEF', amount: 2 },
{ token: 'GFUN', amount: 9},
{ token: 'HOT' , amount: 2 },
{ token: 'LINK', amount: 8},
{ token: 'METM', amount: 2 },
{ token: 'ORTC', amount: 2},
]
Maybe something like this:
const results = [];
const idx = {};
data.forEach(v => {
const token = v['_id']['token'];
if(!token in idx) {
const item = {
token: token,
amount: 0
};
results.push(item);
idx[token] = item;
}
idx[token].amount +=
v['_id']['action'] == 'received' ? v.amount : -v.amount;
});
results is the final array. idx is a lookup into the array in order to make locating the correct item easier.
There's lots of ways to approach this, but here's what I did.
First break the array into two arrays, one for the sent items and one for the received.
const sent = data.filter(o => o._id.action === 'sent');
const recieved = data.filter(o => o._id.action === 'received');
I'm assuming that there is always an entry in received and there may or may not be an entry in sent. So I mapped from the recieved array to the results. For each entry, I find the corresponding entry in sent, and if there is no matching entry, I subtract 0.
const differences = recieved.map( r => {
const s = sent.find(o => o._id.token === r._id.token );
const sentAmount = s ? s.amount : 0;
return {
token: r._id.token,
amount: r.amount - sentAmount,
}
})

Categories