How to optimize big array comparision - javascript

I am working on one Reactjs project- where i have cities and areas of each city ,in each city it may have more the 200 areas . Each area is having 3 attributes cityId,AreaID ,isAdded,. And city is have one attribute cityId.
Here i need to store Areas of each city in a separate array.How can i optimize this operation
export const getAreas = (cityID,allAreas) => {
try {
let areas = [];
if (allAreas) {
allAreas?.forEach((area) => {
if (area?.cityID === cityID) {
areas.push(area);
}
});
return areas;
}
} catch (error) {
console.log(error);
}
};

const areasPerCity = new Map();
allAreas.forEach((area) => {
if (!areasPerCity.get(area.cityId)) {
areasPerCity.set(area.cityId, []);
}
areasPerCity.get(area.cityId).push(area);
});
return areasPerCity;
// here, you have a Map of a city ID => array of areas
// you could use it like `const areas = areasPerCity.get(cityId);`
Complexity is O(n) with one iteration.

You can use useMemo hook. it can memorise your returned value.
https://www.w3schools.com/react/react_usememo.asp

The fastest JavaScript iteration by FAR is the basic for loop, so something like this would be much faster:
export const getAreas = (cityID,allAreas) => {
try {
let areas = [];
if (allAreas) {
for(let area = 0; area < allAreas.length; area++){
if (allAreas[area].cityID === cityID) {
areas.push(allAreas[area]);
}
});
return areas;
}
} catch (error) {
console.log(error);
}
};

If by optimise you mean wanted to shorten your provided snippet, the below is a shorter version of your example. The Array.prototype.filter array method works great here for filtering down your initial array.
const getAreas = (cityID, allAreas) => allAreas.filter((area) => area.cityID === cityID)
Although this isn't much faster if you were looking to optimise for algorithm speed. If you want to improve this, the answers found here "What's the fastest way to loop through an array in JavaScript?" might help you speed up the loop which will have the biggest impact on performance.

Related

Trouble with Addition Assignment in Array from Firebase

I have a scenario where i need to query multiple collections at once and retrieve the values based on the collection name. I use Promise.all to do so and it works accordingly like so
var dbPromises = [];
dbPromises.push(
admin.firestore().collection("collection1").where("user_id", "==", uid).get(),
admin.firestore().collection("collection2").where("user_id", "==", uid).get(),
admin.firestore().collection("collection3").where("user_id", "==", uid).get(),
);
const promiseConst = await Promise.all(dbPromises);
promiseConst.forEach((qs) => {
if (qs.size > 0) {
if (qs.query._queryOptions.collectionId == "collection1") {
qs.docs.map((doc) => {
valuesArr1.push(doc.data().arr);
});
} else if (qs.query._queryOptions.collectionId == "Collection2") {
qs.docs.map((doc) => {
valuesArr2.push(doc.data());
});
} else if (qs.query._queryOptions.collectionId == "collection3") {
qs.docs.map((doc) => {
valuesArr3.push(doc.data());
});
}
} else {
return
}
});
for (var i=0; i < valuesArr1.length; i++) {
if (valuesArr1[i].desiredData) {
console.log('datas from for loop on datas array', valuesArr1[i].desiredData)
globalVariable += `<img src="${valuesArr1[i].desiredData}">`;
}
}
Once I do this I map the query snapshot I get and am able to retrieve the values up to this point like so
From the first collection I retrieve an array from a firestore document and then the following collections i just retrieve all documents from the collections. This all 'works' in that when I console.log into the functions console the data shows up exactly as expected. It's only when I want to iterate over the data and assign the results to a global variable to use elsewhere that strange behavior occurs.
The console.log shows the desired data in the functions console with no issues, but the output when I interpolate that data into the html and send it off in nodemailer I get the following result
undefined is always the first in the response when i use the += addition assignment operator, but if i just use the = assignment operator there's no undefined but I obviously don't get all the data I'm expecting.
There are no undefined values or documents in the collections that I'm retrieving, I've checked thoroughly and even deleted documents to make sure of it. After days of researching I've come to the conclusion it has to do with the asynchronous nature of the promise I'm working with and the data not being immediately ready when I iterate it.
Can someone help me understand what I'm doing wrong and how to fix it in node?
I figured out a solution to my problem and would like to share it in hopes it saves a future viewer some time.
Before, I was storing the results of the array from Firebase inside a global variable. To save some head scratching I'll post the code again below.
var globalVariableArray = []
var globalVariable
var dbPromises = [];
dbPromises.push(
admin.firestore().collection("DataCollection").where("user_id", "==", uid).get()
);
const promiseConst = await Promise.all(dbPromises);
promiseConst.forEach((qs) => {
if (qs.size > 0) {
if (qs.query._queryOptions.collectionId == "DataCollection") {
Promise.all(
qs.docs.map(doc => {
globalVariableArray = doc.data().arrayWithDesiredData;
})
);
}
else {
return
}
});
globalVariableArray.map(gv => {
globalVariable += `<p>gv.desiredData</p>` // <--- Right here is where the problem area was
})
var mailOptions = {
from: foo#blurdybloop.com,
to: 'bar#blurdybloop.com
subject: 'Almost but not quite',
html: `${globalVariable}`
};
The above code give the expected output, but the output would always have undefined first before the data showed. This happened no matter how the array from Firebase was iterated over.
After strengthening my Google-Fu, I worked out the following solution
var globalVariableArray = []
var globalVariable
var dbPromises = [];
dbPromises.push(
admin.firestore().collection("DataCollection").where("user_id", "==", uid).get()
);
const promiseConst = await Promise.all(dbPromises);
promiseConst.forEach((qs) => {
if (qs.size > 0) {
if (qs.query._queryOptions.collectionId == "DataCollection") {
Promise.all(
qs.docs.map(doc => {
globalVariableArray = doc.data().arrayWithDesiredData;
})
);
}
else {
return
}
});
var mailOptions = {
from: foo#blurdybloop.com,
to: 'bar#blurdybloop.com
subject: 'It works!!',
html: `${globalVariableArray.map(dataIWantedAllAlong => <p>dataIWantedAllAlong.desiredData</p> )}` <--- Here I simply loop through the array inside the interpolation blocks and voila! no more undefined showing up in the results
};
I perform the loop inside the brackets where I interpolate the dynamic data and am no longer getting that pesky undefined showing up in my emails.
Safe travels and happy coding to you all!

How to break the for loop using state

I have code as below.
I need to break the loop when first match is found.
const [isCodeValid, setIsCodeValid] = useState(false);
for (let i = 0; i < properyIds.length; i++) {
if (isCodeValid) {
break; // this breaks it but had to click twice so state would update
}
if (!isCodeValid) {
firestore().collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
console.log("should break here")
// updating state like this wont take effect right away
// it shows true on second time click. so user need to click twice right now.
setIsCodeValid(true);
}
});
})
}
}
state won't update right away so if (!isCodeValid) only works on second click.
Once I find match I need to update state or variable so I can break the for loop.
I tried to use a variable but its value also not changing in final if condition, I wonder what is the reason? can anyone please explain ?
You should try and rewrite your code such that you will always call setIsCodeValid(value) once. In your case it could be called multiple times and it might not get called at all
const [isCodeValid, setIsCodeValid] = useState(false);
function checkForValidCode() {
// map to an array of promises for companies[]
const companiesPromises = properyIds.map(propertyId =>
firestore()
.collection(`properties`)
.doc(propertyId)
.collection('companies').get())
Promise.all(companiesPromises)
// flatten the 2d array to single array, re-create to JS array because of firestores internal types?
.then(companiesArray => [...companiesArray].flatMap(v => v))
// go through all companies to find a match
.then(companies =>
companies.find(
company => _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
))
.then(foundCompany => {
// code is valid if we found a matching company
setIsCodeValue(foundCompany !== undefined)
})
}
Try something like this:
import { useState } from 'react';
function YourComponent({ properyIds }) {
const [isCodeValid, setIsCodeValid] = useState(false);
async function handleSignupClick() {
if (isCodeValid) {
return;
}
for (let i = 0; i < properyIds.length; i++) {
const companies = await firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get();
for (const company of companies.docs) {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
setIsCodeValid(true);
return;
}
}
}
}
return (<button onClick={handleSignupClick}>Sign Up</button>);
}
If you await these checks, that will allow you to sequentially loop and break out with a simple return, something you can't do inside of a callback. Note that if this is doing database queries, you should probably show waiting feedback while this is taking place so the user knows that clicking did something.
Update:
You may want to do all these checks in parallel if feasible so the user doesn't have to wait. Depends on your situation. Here's how you'd do that.
async function handleSignupClick() {
if (isCodeValid) {
return;
}
const allCompanies = await Promise.all(
properyIds.map(id => firestore()
.collection(`properties`)
.doc(`${properyIds[i]}`)
.collection('companies')
.get()
)
);
setIsCodeValid(
allCompanies.some(companiesSnapshot =>
companiesSnapshot.docs.some(company =>
_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())
)
)
);
}
Can you not break it after setIsCodeValid(true);?
Use some:
companies.some(company => {
return _.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase());
});
If some and forEach are not available then companies is not an array but an array-like object. To iterate through those, we can use for of loop:
for (const company of companies){
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// do something
break;
}
}
I tired below and it worked for me to break the loop.
I declared and tried to change this variable let codeValid and it was just not updating its value when match found. (not sure why)
But all of a sudden I tried and it just works.
I didnt change any actual code except for variable.
let codeValid = false;
let userInformation = []
for (let i = 0; i < properties.length; i++) {
console.log("called")
const companies = await firestore().collection(`properties`)
.doc(`${properties[i].id}`)
.collection('companies').get()
.then(companies => {
companies.forEach(company => {
if (_.trim(company.data().registrationCode) === _.trim(registrationCode.toUpperCase())) {
// a += 1;
codeValid = true;
userInformation.registrationCode = registrationCode.toUpperCase();
userInformation.companyName = company.data().companyName;
userInformation.propertyName = properties[i].propertyName;
}
});
})
if (codeValid) {
break;
}
}

extract data from array with javascript

I'm creating a google chrome extension that will use some data that are stored inside a JSON file. I need to compose a team of 11 members that needs to be extracted from the processed JSON file, but I don't know how to proceed. After I've parsed the file, I want that for every team position there are only x team members. For example, I need to randomly select one Goalkeeper, three, four or five defenders, three four or five midfield and one, two or three attackers. With PHP I'm able to do that without problems, but I'm not very experienced with javascript and I need help. Is there any function or any way to achieve this?
JSON
{
"player_id":3,
"player_surname":"Immobile",
"player_name":"Ciro",
"player_number":17,
"team_name":"Lazio",
"team_abbreviation":"LAZ",
"role_abbreviation":"A",
"role_name":"Attaccante",
"quotation":62,
}
JS
const uri = 'api/players_.json';
$.getJSON(uri, function(data){
// code stuff here
});
A combination of reduce, map and filter can be used to setup teams:
const players = [
{
"player_id":3,
"player_surname":"Immobile",
"player_name":"Ciro",
"player_number":17,
"team_name":"Lazio",
"team_abbreviation":"LAZ",
"role_abbreviation":"A",
"role_name":"Attaccante",
"quotation":62,
},
{
"player_id":3,
"player_surname":"Immobile",
"player_name":"Ciro",
"player_number":17,
"team_name":"Lazio",
"team_abbreviation":"BLAA",
"role_abbreviation":"A",
"role_name":"Attaccante",
"quotation":62,
}
];
const playersPerTeam = Object.values(players.reduce((acc, player) => {
const teamKey = player.team_abbreviation;
if(!acc.hasOwnProperty(teamKey)){
acc[teamKey] = [];
}
acc[teamKey].push(player);
return acc;
}, {}));
const chosenSetupPerTeam = playersPerTeam.map(playersInTeam => {
const playersNeededPerRole = {
"Portiere": 1, // Keeper
"Difensore": 4, // Defender
"Centrocampista": 4, // midfielder
"Aggressore": 2, // Attacker
};
const playersPresentPerRole = {};
// Get a team to fulfil the requirements stated in playersNeededPerRole
return playersInTeam.filter(player => {
// Role does not exist, thus the player can't join the team
if(!playersNeededPerRole.hasOwnProperty(player.role_name)){
return false;
}
// Set the default of players present per role to 0
if(!playersPresentPerRole.hasOwnProperty(player.role_name)){
playersPresentPerRole[player.role_name] = 0;
}
// Stop if all positions have been filled as specified in playersNeededPerRole
if(playersPresentPerRole[player.role_name] === playersNeededPerRole[player.role_name]){
return false;
}
playersPresentPerRole[player.role_name]++;
return true;
});
});
console.log(chosenSetupPerTeam)
Checkout the demo

Javascript Observable performance issue

Current Working Code
I have the following rxjs/Observable:
findMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
let processing: boolean = false;
this.firebaseDataService.findMessages(chatItem).forEach(firebaseItems => {
if (!processing) {
processing = true;
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
let mergedItems: any[] = this.arrayUnique(firebaseItems.concat(localItems), false);
mergedItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
if (this.me && mergedItems && mergedItems[0] && this.me.uid === mergedItems[0].memberId2) {
this.updateChatWithMessage(chatItem, mergedItems[0], false);
}
observer.next(mergedItems);
processing = false;
});
}
});
});
}
and
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
As you can see, it returns a list of firebaseItems and localItems, which are merged to mergedItems. This works perfectly.
Performance Enhancement
However, I am trying to increase the performance that the items load. So figure, I would like to first load the localItems, and then add to the list with the firebaseItems.
So I try add the following function:
findLocalMessages(chatItem: any): Observable<any[]> {
return Observable.create((observer) => {
this.localDataService.findMessages(chatItem).then((localItems: any[]) => {
localItems.sort((a, b) => {
return parseFloat(a.negativtimestamp) - parseFloat(b.negativtimestamp);
});
observer.next(localItems);
});
});
}
and call it as follows:
this.firelist = this.dataService.findLocalMessages(this.chatItem);
this.firelist = this.dataService.findMessages(this.chatItem);
this.firelist.subscribe(items => {
...
});
Problem
This has now introduced a bug, that there are now 2 Observables and the results are not as expected. The sort order is incorrect, and some of the items are not being added to the this.firelist for some reason.
Question
Whats the best way to handle this?
I was thinking if it's possible to make the findLocalMessages Observable only get fired once, and then it never works again, as the findMessages Observable will maintain the list. Is this possible? I have been looking at the Observable api, and can't seem to figure out how to do that.
Any help appreciated.
With the risk of oversimplifying the problem statement, you have two streams of data that you want to merge and sort in an efficient manner.
The separation you have made is a step in the right direction.
The reason why you are not getting all the messages is that you are overriding the first observable with the second.Have a look at the following example and see what happens if you try and assign the second observable to move instead of move2.
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
move2
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
<h1>
<div id="1">
aaaaaaaaaaaaaaaaaaaaaaaaa
</div>
<div id="2">
bbbbbbbbbbbbbbbbbbbbbbbbb
</div>
</h1>
In order to merge the two streams together properly you need to use the merge operator as shown below:
let move = Observable.fromEvent(document.getElementById("1"), 'mousemove');
let move2 = Observable.fromEvent(document.getElementById("2"), 'mousemove');
move.merge(move2)
.subscribe((event:any) => {
if (event) {
console.log(event.path[0].id)
}
});
Now all you need to do is sort them. I would advice that you do the sort only after the merge because otherwise, you will end up with two streams that are sorted locally, not globally.

How do I select unique elements from a Promise<Array[]> in Angular 2/4?

Little context, I have an API that returns a competition with a link to its matches and each match has a link to the teams that played.
My intention for now, is to build a standings table based on the competition matches therefore, my first step is to print a list of unique "teams" from that array of matches. That is I need to go through all the matches, check the home and away teams, and if the team is not present, push it into the list.
Here is the tricky part, I have the teams like this:
match.away = this.teamService.getTeam(match._links.awayTeam.href);
match.home = this.teamService.getTeam(match._links.homeTeam.href);
And my getTeam() method is returning a promise Team.
getTeam(id: string): Promise<Team> {
return this.http.get(id)
.toPromise()
.then(response => response.json() as Team)
.catch(this.handleError);
}
The Team class has teamName: string; which is the value I need to verify to build the array and that is the part I need help with. I have an OnInit implemented that goes like this:
ngOnInit(): void {
this.teams = [];
this.route.params
.switchMap((params: Params) => this.competitionService.getCompetition(+params['id']))
.subscribe(competition => {
this.competitionService.getMatches(competition)
.then(
matches => {
matches.forEach(match => {
match.away = this.teamService.getTeam(match._links.awayTeam.href);
match.home = this.teamService.getTeam(match._links.homeTeam.href);
match.away.then(away => {
var found = this.teams.find(team => {
return team.teamName.toLocaleLowerCase().includes(away.teamName)
});
if(!found) {
this.teams.push(away);
}
});
match.home.then(home => {
var found = this.teams.find(team => {
return team.teamName.toLocaleLowerCase().includes(home.teamName)
});
if(!found) {
this.teams.push(home);
}
});
});
return matches;
}
).then(matches =>
this.matches = matches
);
this.competition = competition
});
}
What I tried to do there is resolve the Promise first and check for the teams' presence in my array and if it is !found then push it to the list.
My HTML looks like this:
<md-list>
<md-list-item *ngFor="let team of teams">
<button md-raised-button>{{team.teamName}}</button>
</md-list-item>
</md-list>
But the result is a list of each team in each match, so appearantly the push part is working because the array teams: Team[]; is being populated and printed correctly, but the verification is not.
Please don't kill me for my code cause I have 0 idea on this, I'm just learning on the fly, ot only Angular but to code.
So my Question Is:
How do I build an array of unique teams from that promise of matches?
Furthermore, how do you usually split arrays for these calculations? Every research I found points me to a "Custom Pipe" but it seems to be filtering the view only, After I have my array of teams, I have to make calculations on each of the matches for Goals, points, etc, so it does not seem that it will work for me.
Any ideas or suggestions?
I'd add a new method to delegate the teams array population:
addToSet(team): void {
let teamNames: string[] = this.teams.map((team) => team.teamName.toLocaleLowerCase());
if (teamNames.lastIndexOf(team.teamName.toLocaleLowerCase()) === -1) {
this.teams.push(team);
}
}
And then, substitute this part
var found = this.teams.find(team => {
return team.teamName.toLocaleLowerCase().includes(home.teamName)
});
if(!found) {
this.teams.push(home);
}
for
this.addToSet(home);
Also, to be sure that both promises are fulfilled before going on with the array loading, you could wrap both getTeam calls with Promise.all.
Promise.all([
this.teamService.getTeam(match._links.awayTeam.href),
this.teamService.getTeam(match._links.homeTeam.href)
]).then((teams) => {
match.away = teams[0];
match.home = teams[1];
});
EDIT: putting it all together
addToSet(team): void {
let teamNames: string[] = this.teams.map((team) => team.teamName.toLocaleLowerCase());
if (teamNames.lastIndexOf(team.teamName.toLocaleLowerCase()) === -1) {
this.teams.push(team);
}
}
ngOnInit() {
// rest of the initialization
matches.forEach(match => {
Promise.all([
this.teamService.getTeam(match._links.awayTeam.href),
this.teamService.getTeam(match._links.homeTeam.href)
]).then(teams => {
match.away = teams[0];
match.home = teams[1];
this.addToSet(away);
this.addToSet(home);
});
});
}
EDIT 2: added toLocaleLowerCase() in addToSet() for check. I'm assuming the getTeam returns a single object, otherwise, you'll need to enforce the returned object schema.

Categories

Resources