Compare 2 immutable lists for equality - javascript

Im looking to do something that I would have thought was done so many times with Immutable.js Lists already but i can't seem to find this case. I have 2 lists. One with Items. And one that contains keys of the items selected as data. users who have selected things from items basically. I am storing the key from items as data in the profile of the user that selected the item (in a firebase db, not that it matters). So I want to simply filter the items in my app using a selector. In the selector Im looking to compare the key from items with the key I stored in the users profile.
If it's a match, then give me the matching data from items as state to use. I believe this should be a filter on Items of some kind?? To do the compare i also need to get the stored key data in the users profile to compare which is also a immutable list.
Im trying
const selectedItems = items.filter(
item => {
userItems.forEach(userItem => {
if (userItem.itemKey === item.key) {
return item <-------pretty sure this is where Im doing it wrong
}
})
});
If i hard code a record from my users profile and filter, I get what I expect which is new filtered list with one item matching the id below.
const selectedItems = items.filter(
item => {
return item.get('key') === '-LDR5cNZPjthN6nK9tzJ'
});

filter will create a new array with every element in the original array that returned a truthy value. With this in mind you could use a combination of filter and some to filter out all the objects you have a key for in userItems:
const selectedItems = items.filter(item =>
userItems.some(userItem => userItem.itemKey === item.itemKey)
);

Related

Performant way of finding unique values of keys from a list of objects in typescript

I am working on an angular app that has a
a set of filters
and records present in a table
Columns in the table correspond to filters. At any time filters contain unique values from corresponding columns as options.
For a record, a column can contain more than 1 values (i.e. more than 1 options from the corresponding filter)
When a user selects an option from a filter, the records in the table are filtered and the filtered results (as per user the selection) are shown to the user.
Once the set of filtered records is derived, unique values for each filter are derived from the set of filtered records by finding unique values for each column.
Key of Filter objects correspond to columns of Record objects I have a list of records and a list of filters. I want to iterate over both these lists and find the unique value of columns for each key.
I am using the below logic to find unique options for filters from my messages.
export function filterOptionsService(records: Record[], filters: RecordFilter[]): RecordFilter[] {
const newFilters: RecordFilter[] = filters.map(filter => {
//logic to find all values for a column from the set of records
const filterOptions = records.reduce((uniqueList, record) => uniqueList.concat(record[filter.key]), []);
//logic to find unique values for a column from the set of records
//which act as options of corresponding filters.
const uniqueOptions = uniqBy(filterOptions, (opt) => filter.valueFunction ? filter.valueFunction(opt) : opt);
const filterOptions: FilterOption[] = uniqueOptions.map(value => {
return {
label: filter.labelFunction ? filter.labelFunction(value) : value,
value: filter.valueFunction ? filter.valueFunction(value) : value,
};
});
filter.options = orderBy(dropListOptions, 'label');
//here is my logic to find the count of each option, present in the filtered records
filter.options = filter.options.map(option => ({
...option,
count: filter.valueFunction
? filterOptions.filter(value => filter.valueFunction(value) === option.value).length
: filterOptions.filter(value => value === option.value).length
}));
return filter;
});
return newFilters;
}
interface Filter {
key: string;
labelFunction?: Function;
valueFunction?: Function;
order: number;
multiple: boolean;
options: DropListOption[];
values: DropListOption[];
}
interface Record {
column1: string;
column2: string;
column3: string | string[];
.
.
columnN: string;
}
The below logic takes the most time in my code. It takes around 7 seconds for 8k records.
const filterOptions = records.reduce((uniqueList, record) =>
uniqueList.concat(record[filter.key]), []);
I am unable to make my code perform better. Please can you suggest where am I going wrong.
Here is sample code on typescrip playground
https://www.typescriptlang.org/play?#code/FASwdgLgpgTgZgQwMZQAQDEQBtowPIAOEIA9mKgN7Co201YIBGUWAXKgM4QzgDmA3NTq0AbgiwBXKOwRgAnoOG1kxEVAD87RiRJYosxUtQEEcrCQQATTalkKhSpCQmQbYCQFtmMQzQC+-LTA1AD0IRwIHgR6qGJ6HKhwJDCoANZQcrYwaAQwJASwEHIAjAA0xnkFMEUATKigkLCIKKgASlBOMJaYOLCUDsLpcuxcPGAC9UaoDMxY6C5IxGQ282CLpGC+wnFSq+vL7HtLmwN0yZaw7O5esFt0HhI4INHSqNq6+idT+ccch9i4QjHADaAF07qJxFI-hgAbAgRswVsAkFgGEKvlqiAoAkSHA2h1zqgkAwOAkENlOJE0AgEkNUHiCZ1unCUiTaRw0SFUBAABYgBICzggKJYEBwbGWVDZKo4yAIY6oDyZFQScTE0kJXm0pV2JXJHKVQrYzngXDNNCtEgAd36dFy+UKJRG3D4SIGDqqtRdYwEwACwWATjAXFQ4BAxHEVutMOjYNQAF5UMDTlNhBRPU7iqxgQAiMhQXPlXN87KF0GlTPVOQ1Vi5phIXN+UqptO0DNG6vZvMStRF1C5sDgcuVzve3MXOBNlttqYdx1dnP5sCF4ulqCrge9zeD4e5itV8cNputtPg4JBMIASVsHgZYCwKrJIF4Q-GsShOJ5JDSGQxXsyJIUg4EVonFEAkAVDYgzIUNw0jOZWVjQkuh6XB4yTFNZyUCghjrQ8Sn7c5LjKJVHmIF5WEQLAOCgcofg2P4wXKHYcXYMFm1PNNcIyfCxxrIiukuGpygeJ5KOo2j6KIRicwrD9JDY5NQT8GguKMc9gglXp8Bk2CAGVYBECCoAACngkAoxtDhSgs8Q0NgDgAEpBGCdEb21NRUGKAAWRJWQZRgACsOggTl0WtXkoHIDyEC8gAOVJpRtQKQsWBI+TQWYUuySwJBaTEICgsgeQQdIEgANgAWgAdk4QkwEsDgADpgDgBZFW0wE9JDQyYGMlBTLyGN2DjeSusc0aUJZHSwSc-pTgmmAWo8BACFMpbEwAPjtIwwnMXgIO-fzGtsLAsAU6FEmSWxiV0TxyDgPI70y+qIAZfFsmZBJT2DUMloRWDE2SmNmty-KzNMlwQAARykAAZAUIHKYb5oTHbUGhuGoERrhmuDSCICGm1gSW5qhlBJzyjmwIuN+2CPmag7TIAcgBnqOBZ0p2d+FzsNbPwXP9QMgA
I think the performance problem you're having is that Array.prototype.concat() does not modify an existing array, but instead returns a new array. Immutability is nice, but it doesn't seem relevant to your use case: every uniqueList array you create except for the very last one will be discarded. Object creation is fairly fast in JavaScript, but creating thousands of array objects only to immediately throw them away is slowing things down.
My suggestion would be to replace concat() with something that modifies the existing array, such as Array.prototype.push():
That is, you could change
const filterOptions = rows.reduce(
(uniqueList, row) => uniqueList.concat(row[filter.key]), []
);
to
const filterOptions: string[] = [];
for (let row of rows) filterOptions.push(...row[filter.key]);
When I run a simulation where I create 6000 rows and 14 filters (see playground link below), the concat() version takes about 7.5 seconds, whereas the push() version takes about 38 milliseconds. Hopefully that factor of ~200 improvement holds in your environment and makes enough of an impact to be sufficient for your needs.
Note that I'm not dealing with any "uniquifying" or "function" aspects that your original problem seems to have, since your reproducible example code doesn't touch that either. Unique strings might be more easily tallied via an object key than an array, or maybe even via a Set. But again, hopefully changing concat() to push() will be enough for you.
Playground link to code

Compare two arrays values, and populate if no match

I need help with matching two arrays. I have TypeScript in the example code, but it can be understood since its about array operations more or less.
In short:
I have two arrays; myItems[] and allItems[]. myItems[] can only hold maximum 4 values.
I want to first check if the items in myItems[] is 4, AND/OR exist in the other array allItems[].
If NOT: populate myItems[] with values from allItems[] (until it contains 4 values) and/or replace the items that is missing (relative to allItems[]) with other items in allItems[] (I'm trying to use default values instead of randomly taking values in my example code).
Description:
I have a widgets (quick links) module that show 4 links at a time, but there are in total 20 different links (or more). All links is stored in a list and each has its own unique ID. In code, all links is extracted and returned in an array (like the allItems[] in above example).
The user can save the links he/she wants to show in the widget. The user settings is stored and returned as an array with the ID of the links that the user have saved. Like the myItems[] above,
Problem:
I have a solution that check the length of the myItems[], and if needed populates items from the allItems[] one. However, it does NOT check if the items in the user array exist in allItems[] and then populates it with the default links. In practical it means that the user can save links and it will be shown in the widget as intended. BUT if a link is removed in the list (which will then be removed in the allItems array) only 3 items will be shown as the myItems[] doesn't check with the allItems[] array to see if it exists there.
Code:
public async getUserWidgets(): Promise<Widget[]> {
let allWidgets = await this.getAllWidgets(); //Array with all the links ID from the list
let userRepository = new UserProfileRepository(this.absoluteWebUrl);
let userSettings = await
userRepository.getUserExtensionValues(this.context); //Extracting the user Settings which contains the ID of the saved linksvar
result:Widget[] = []; //the array where the result will go in
//if the user doesnt have any saved links, or if the user have less than 4 saved links
if (userSettings == null || userSettings.QuickLinksWidgets == null ||
userSettings.QuickLinksWidgets.length < 4)
{result = allWidgets.filter((w) => {return w.defaultWidget;}).slice(0,4);
}
else {var ids =userSettings.QuickLinksWidgets;
for (let i = 0; i < 4; i++) {
let id = '' + ids[i];let w = allWidgets.filter((e) => { return e.id == id;});
if (w.length == 0) {
continue;}
result.push(w[0]);}}
return new Promise<Widget[]>(async (resolve) => {resolve(result);});}
A simple way to check if an array holds a value is using the includes() method.
for(let value of myitems){
if(allitems.includes(value)){
console.log("Duplicate")
}
}
The above code will loop through each value in your myitems array and test if that value is in the allitems array.

Array filter method to match IDs and check properties exist

I'm currently using a method to try to filter some arrays, the method is almost working but I can't seem to access the exact values -
I make a call and add each returned array into a bigger array, these arrays will then be assigned a productId and maybe some data, i am appending the productIds using this:
data.push({'productId': product.id});
Which unfortunately adds a new object to the array which means my function below doesnt work unless the productId is in the first object of each array:
let matchedArray = data.flatMap(arr => arr.filter(obj => obj.productId == id))
What I need to do is filter the array down to the subarray that matches the productId and ID and also that has some of the fields of data such as 'name' - so it checks that the name isnt empty.
The data set looks like this (array of subarrays)
id = 12345
data = [[],[],[],[],[],[],[],[],[{"id":"123","name":"africa soul
2019","startDate":null,"endDate":null,"country":null,"city":null,"type":"Ev
ent","members":null},{"productId":"12345"}],[],[],[],[],[],[],
[],[],[],[],[]]
As you can see the productId is appended to the array but isnt now working with my filter method, i need to filter for the right array that has matching ID's and at least one of the fields are also existing. I either need to change the way the productId is manually appended, or change the filter method?
Thanks so much if you can help
If you want to filter all the arrays which have some object which have a productId equal to a given value:
let data = [[],[],[],[],[],[],[],[],[{"id":"123","name":"africa soul 2019","startDate":null,"endDate":null,"country":null,"city":null,"type":"Event","members":null},{"productId":"12345"}],[],[],[],[],[],[],[],[],[],[],[]],
id = "12345";
let filtered = data.filter(arr => arr.some(a => a.productId === id))
console.log(filtered)
If you want to get the first match, use find instead of filter

Push to an array if id value is not found

I've a little array problem I'm going round and round in loops with.
I have a number of check boxes, each checkbox has a specific object.
On checking a checkbox I want to iterate over the array and if the id of the checkbox (object) does not match any other items in the array, push to the array.
I have the following, which pushes the checkbox object for every item that doesn't match it's id. So I end up with multiple objects of the same ID.
mapMarkers.map(marker => {
if(markerID !== marker[0].id) {
mapMarkers.push(markerObject)
};
});
Any help to get my thinking on this straight would be appreciated.
For context here's the project its from. Lines 281
https://codepen.io/sharperwebdev/pen/PQvMqR?editors=0011
The Array#filter method would be more appropriate for this. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
const filteredMarkers = mapMarkers.filter(marker => markerID !== marker.id);
Then use filteredMarkers (mapMarkers isn't mutated, which is a better practice).

compare two lost and filter equals in ramda

I have an array named navLinks which has a property named requiredPermissions; also and an array of permission's which named userPermisssion.
Now I want to filter all navLink item based on that are them requiredPermission prop equal to requiredPermission.
My approach was simple, but I confused when it's come to list.
R.filter(R.equal(R.prop('requiredPermossions',x), ??? ))
EDIT:
for example, assume we have an array of objects named is NavLinks which has a property userPermisssion =['FIN'] and have a list requiredPermission=['FIN','ADMIN']. now we can say this user has permission for matching permissions navlinks. both variables are the list of string which indicates role and permissions.
I think I got what you need. It is cleaner to pipe the commands. Here goes an example that does what you want:
const navLinks = [
{ requiredPermissions: ['FIN'] },
{ requiredPermissions: ['FIN', 'ADMIN'] },
];
const userPermissions = ['FIN'];
const result = R.filter(
R.pipe(
R.prop('requiredPermissions'),
R.all(R.flip(R.contains)(userPermissions)),
), navLinks);
The explanation goes like this:
The function used in filter is called with each object in navLinks
Inside the pipe we keep only the requiredPermissions list
For each item in the list we check if it is included in the userPermissions list. Only if all the permissions are included in userPermissions do we return true.

Categories