Javascript findindex performance issue - javascript

I am having an array as like this
var projectItemList=[{
"id":1,
"projectid":"a0I3l00001YiS67EAF",
"date":"2021-06-18",
"task":"",
"starttime":"",
"endtime":"",
"description":"",
"hours":"",
"projectname":"MyCOmp1",
"computedclass":"lookupclass1"
}, {
"id":1,
"projectid":"a0I3l00001YiS67EAF",
"date":"2021-06-18",
"task":"",
"starttime":"",
"endtime":"",
"description":"",
"hours":"",
"projectname":"Myproject",
"computedclass":"lookupclass1"
}];
and this array grows with n number of rows and I am pushing the dynamic constructed rows to the above array by incrementing the id.
and depending on keystroke I am changing the description of objects by finding the index and I am using this method to find the index
findIndex(id) {
return this.projectItemList.findIndex((obj => obj.id == id));
}
this.projectItemList[this.findIndex(id)].description ='xxxxxxx'
If the array length is 5 then things are fine. If the array grows more than 10, say 50 or 100
then its taking some delay to find the index and causing the issue.
I tried with many options but still same issue
return this.projectItemList.map(function(x) {
return (x.id);
}).indexOf(id);
How to overcome this performance issue?

If you can't change the structure of the array, you can run find method for the array.
var projectItemList = [{
"id":1,
"projectid":"a0I3l00001YiS67EAF",
"date":"2021-06-18",
"task":"",
"starttime":"",
"endtime":"",
"description":"",
"hours":"",
"projectname":"MyCOmp1",
"computedclass":"lookupclass1"
}, {
"id":2,
"projectid":"a0I3l00001YiS67EAF",
"date":"2021-06-18",
"task":"",
"starttime":"",
"endtime":"",
"description":"",
"hours":"",
"projectname":"Myproject",
"computedclass":"lookupclass1"
}];
const findIndex = id => projectItemList.find(i => i.id === id);
findIndex(1).description = 'bar';
console.log(projectItemList);
The complexity will be n, where n is the number of items in the array.
But the best solution, in my opinion, is changing your data to something like:
var list = {
1: {
name: 'name for 1',
description: 'description for 1',
},
2: {
name: 'name for 2',
description: 'description for 2',
}
};
from where, you can find the item like
list[1];
in 1 step.

Related

How to use 'reduce' function to filter and display products in cart

I am trying to train myself with Javascript and am a bit confused with reduce and would appreciate some guidance in understanding it. I've done a lot of research and have arrived at a stage where some clarity beyond just googling is needed. So here's my basic sample code..
the ids are the cart ids and the products are the products in a store
const ids = [4, 3, 1];
const products = [
{ id: 1, product: "Product 1" },
{ id: 2, product: "Product 2" },
{ id: 3, product: "Product 3" },
{ id: 4, product: "Product 4" },
{ id: 5, product: "Product 5" },
{ id: 6, product: "Product 6" },
{ id: 7, product: "Product 7" },
];
I need to display the products which are in the cart so I went with 3 options
The for loop, the map and filter and finally the reduce
FOR LOOP
// SOLUTION WITH FOR LOOP
const selectedProducts = [];
for (id of ids) {
const selectedProduct = products.filter((product) => product.id === id);
selectedProducts.push(selectedProduct);
}
console.log("selectedProducts", selectedProducts);
Question 1 : Currently I have just 7 products. but in an actual store there would be thousands of products. So is filtering thousands of products for each id a good idea. Is there a better way to do this?
MAP and FILTER
// SOLUTION WITH MAP
const mappedProducts = ids.map((id) => {
const [obj] = products.filter((product) => product.id === id);
return obj;
});
console.log("mappedProducts", mappedProducts);
Question 2 : As filter creates an array I ended up with an array of arrays and had to destructure the array and return the object. Is there a better way where I could directly destructure/pass the object without explicitly declaring return.
REDUCE
// SOLUTION WITH REDUCE
const initialArray = [];
const reducedProducts = products.reduce(function (acc, product) {
const productId = product.id;
if (ids.includes(product.id)) acc.push(product);
return acc;
}, initialArray);
console.log("reducedProducts", reducedProducts);
console.log("initialArray", initialArray);
Question 3 : What am I doing wrong with reduce?
This is my first time with reduce and I am sure I am doing something wrong here.. as reduce is supposed to be more compact than the for and map-filter combination but in my case it seems to be the opposite. Also I thought that with reduce the initialValue does not get mutated. But in my case it is getting mutated.
Any help and advice would be appreciated.
So is filtering thousands of products for each id a good idea. Is there a better way to do this?
From the products array, allow for easy lookup by restructuring it into an object or map indexed by ID, so you just need to use ordinary bracket notation or .get to get the matching object (O(1)).
const ids = [4, 3, 1];
const products = [
{ id: 1, product: "Product 1" },
{ id: 2, product: "Product 2" },
{ id: 3, product: "Product 3" },
{ id: 4, product: "Product 4" },
{ id: 5, product: "Product 5" },
{ id: 6, product: "Product 6" },
{ id: 7, product: "Product 7" },
];
const productsById = Object.fromEntries(products.map(
obj => [obj.id, obj]
));
const result = ids.map(id => productsById[id]);
console.log(result);
Is there a better way where I could directly destructure/pass the object without explicitly declaring return.
You could .find instead, which returns the matching object instead of returning an array - but that's still an O(n ^ 2) process. Indexing each product by its ID is better.
as reduce is supposed to be more compact than the for and map-filter combination but in my case it seems to be the opposite. Also I thought that with reduce the initialValue does not get mutated. But in my case it is getting mutated.
Not at all - .reduce, when not used in the appropriate circumstance, is indeed more verbose than more standard loops, as you're seeing. See this video.
Also I thought that with reduce the initialValue does not get mutated.
Sure it can, if the initial value is an object (and not a primitive) - objects can be mutated. If you mutate the accumulator parameter and return it, the next iteration (and the next, and the final return value) is the same exact object.
Just for note, a couple of examples with high-order functions:
reduce
products.reduce((acc, product) => {
if (ids.includes(product.id)) acc.push(product);
return acc;
}, []);
filter
products.filter((product) => ids.includes(product.id));
// or with destructuring
products.filter(({ id }) => ids.includes(id));
map and flat
products.map((product) => (ids.includes(product.id)) ? product : []).flat();
flatMap
products.flatMap((product) => (ids.includes(product.id)) ? product : []);

Javascript arrays how to print sorted elements

I have a question about arrays. I'm new to javascript and I'm writing a program that has a function where it filters 20 elements of an array by category. That is, I have 3 buttons where, by clicking on one of them, the function is turned on and it starts displaying the filtered elements. Please tell me how can this be done? I have already tried a bunch of ways, but in the end nothing happened, although I think that I made a mistake somewhere.
array:
window.products = [
{
id: "i8",
title: "Iphone 8",
description:
"The iPhone 8 ",
price: 19900,
discontinued: false,
categories: ["c1"]
},
{
id: "i10",
title: "Iphone X",
description: "Iphone 10",
price: 39900,
discontinued: false,
categories: ["c1"]
},
{
id: "i11",
title: "Iphone 11",
description: "The iPhone 11 ",
price: 69900,
discontinued: false,
categories: ["c1"]
};
my function
function myFunction() {
document.getElementById("selected-category").innerHTML = "Iphones";
document.getElementById("category-products").innerHTML = "";
for (i = 0; i < 20; i++) {
document.getElementById("category-products").innerHTML = window.id;
const found = window.products.find(window.products.categories == "c1");
console.log(found);
}
}
part html code with button
<button onclick="myFunction()">Iphones</button>
First, you have syntax error in your windows.products = [ ... };. It's not closed properly (you need a ] before the };.
Then, the find method needs to be passed a function that processes an element of the Array. What you tried window.products.categories == "c1" evaluates to false, because the property categories does not exist in the window.products array. You get undefined on the left hand side and a string on the right hand side, so it's always false. You'd get "false is not a function".
Examples of using find() with a function:
const found = window.products.find( element => element.categories == "c1" );
or
const found = window.products.find( function(element) {
return element.categories == "c1"
});
But then:
The above == "c1" isn't what you should use, because it only matches due to type coercion from an array to a string, and only matches when the categories has only "c1" and no other elements. You should use includes method.
"find" will only give you one matching product. Use "filter" to find all matching ones.
If you only need to search for one key "c1", you can use
const found = window.products.filter( product=> product.categories.includes("c1")); and for two keys "c1" and "c2": const found = window.products.filter( product => product.categories.includes("c1") && product.categories.includes("c2"));
But I don't think you should use the above, because you should handle the case where the user searches for multiple keys.
const searchList = ['c1', 'c2', 'c3']
const found = window.products.filter ( function( product) {
//returns true if element.categories contains all of 'c1', 'c2' and 'c3'
return searchList.every( searchKey => product.categories.includes(searchKey)) ;
})
You can also do the search in one line, but may be harder to read :
const searchList = ['c1', 'c2', 'c3']
const found = window.products.filter ( product => searchList.every( searchKey => product.categories.includes(searchKey)) );

Filter array of dictionaries with array of dictionaries

I have two arrays of dictionaries, both contain an ID property, one is 'id' and the other is '_id'.
I am trying to filter an array that contains all books available, and grab data from it based on what books a user has.
const books = allbooks.filter(({ id }) =>
userBooks.findIndex((book) => book._id === id)
)
This is the code I am using right now, but it's not doing what I expect.
What the data looks like
allbooks [
{
id: 1,
name: 'Book 1'
},
{
id: 2,
name: 'Book 2'
}
]
userBooks [
{
_id: 1
}
]
Should I create an array from the 2nd array's _ids and filter with that? Trying to find the best practice way of doing this, I am using arrays of dictionaries a lot and find it tough sometimes.
You are sooo close my friend. The problem is Array.prototype.findIndex is returning the first index in which your lambda function returns true. The index of the matching book is index 0, which is falsy, which makes your filter ignore it because the result of the filter lambda function is false.
Instead, you can check if the result of your findIndex is > -1 since Array.prototype.findIndex returns -1 if the element is not found.
const allbooks = [
{
id: 1,
name: 'Book 1'
},
{
id: 2,
name: 'Book 2'
}
];
const userBooks = [
{
_id: 1
}
];
const books = allbooks.filter(({ id }) =>
userBooks.findIndex((book) => book._id === id) > -1
)
console.log(books);

Javascript object as a type of mini database?

Is it possible to use a JavaScript object as a type of mini database? I often find myself needing a kind of database structure when I'm coding in JS but it feels like overkill to use an actual database like MySQL (or similar).
As an example, let's say I need to structure this data as a JS object:
Object idea: Stuff to sell
Items to sell: The junk in the garage
Object structure: List all items including item name, item condition, and item value
In order to make this into a JS object I would maybe write:
var stuffToSell = {};
Then my first item would maybe look like:
var stuffToSell = {
item : "Coffee Maker",
condition : "Good",
price : 5
};
Now to me this seems like I'm on the right track, until I come to add another item and I end up having to use the properties item, condition, and price again in the same JS object — which feels wrong? — or is it?? At this point my brain keeps shouting the word "ARRAY!" at me but I just can't see how I can use an array inside the object, or an object inside an array to achieve what I want.
My end goal (in this simplified example) is to be able to then use object-oriented syntax to be able to access certain items and find out specific information about the item such as price, condition etc. For example if I want to know the price of the "coffee maker" I would like to write something like:
stuffToSell["coffee maker"].price
...and then the result above should be 5.
I feel like I'm on the right track but I think I'm missing the array part? Could someone please tell me what I'm missing or maybe what I'm doing completely wrong! And also if it is wrong to have duplicate property names in the same JS object? For example, is it okay to have:
var stuffToSell = {
item : "Coffee Maker",
price : 5,
item : "Mountain Bike",
price : 10,
item : "26 inch TV",
price : 15
};
...it seems wrong because then how does JS know which price goes with which item??
Thanks in advance :)
You're definitely on the right track!
A lot of people will refer to what you're talking about as a hash.
Here's my suggested structure for you:
var store = {
coffee_maker: {
id: 'coffee_maker',
description: "The last coffee maker you'll ever need!",
price: 5,
},
mountain_bike: {
id: 'mountain_bike',
description: 'The fastest mountain bike around!',
price: 10,
},
tv: {
id: 'tv',
description: 'A big 26 inch TV',
price: 15,
},
}
Having a structure like that will let you do this:
store.mountain_bike.price // gives me 10
Need an array instead, say for filtering or looping over?
Object.keys gives you an Array of all the object's keys in the store ['coffee_maker', 'mountain_bike', 'tv']
// Now we just have an array of objects
// [{id: 'coffee_maker', price: 5}, {id: 'mountain_bike', price: 10} ...etc]
var arr = Object.keys(store).map(el => store[el])
Need to just filter for items that are less than 10?
This will give us an array of products less than 10:
// gives us [{id: 'coffee_maker', price: 5}]
var productsUnder10 = arr.filter(el => el.price < 10)
These techniques can be chained:
var productsOver10 = Object.keys(store)
.map(el => store[el])
.filter(el => el.price > 10)
Need to add a product?
store['new_product'] = {
id: 'new_product',
description: 'The Newest Product',
price: 9000,
}
Here's another way, which would be good to start getting used to.
This is a 'safe' way to update the store, read up on immutability in javascript to learn about it
store = Object.assign({}, store, {
'new_product': {
id: 'new_product',
description: 'The Newest Product',
price: 9000,
}
})
...and another way, that you should also read up on and start using:
This is the object spread operator, basically just an easier way to work with immutable structures
store = {
...store,
'new_product': {
id: 'new_product',
description: 'The Newest Product',
price: 9000,
}
}
Resources
JavaScript Arrow Functions
Object and Array Spread Syntax
Immutable Javascript using ES6 and beyond
You can actually use json or create an array of objects.If using a separate file to store the objects, first load the file. Use array filter method to get an new array which matches the filter condition , like you want to get the item with id 1. This will return an array of objects.
var dict = [{
'id': 1,
'name': 'coffee-mug',
'price': 60
},
{
'id': 2,
'name': 'pen',
'price': 2
}
]
function getItemPrice(itemId) {
var getItem = dict.filter(function(item) {
return item.id === itemId
});
return getItem[0].price;
}
console.log(getItemPrice(1))
JSON objects don't support repeated keys, so you need to set unique keys.
Put an id as your key to group your items:
var stuffToSell = {
'1': {
item: "Coffee Maker",
price: 5
},
'2': {
item: "Mountain Bike",
price: 10
}
.
.
.
}
Now you can access the item's price very fast.
Look at this code snippet (Known Ids)
var stuffToSell = {
'1': {
item: "Coffee Maker",
price: 5
},
'2': {
item: "Mountain Bike",
price: 10
},
'3': {
item: "26 inch TV",
price: 15
}
};
let getPrice = (id) => stuffToSell[id].price;
console.log(getPrice('1'));
See? the access to your items it's fast and your code follows a readable structure.
Look at this code snippet (Item's name as key)
var stuffToSell = {
'Coffee Maker': {
price: 5
},
'Mountain Bike': {
price: 10
},
'26 inch TV': {
price: 15
}
};
let getPrice = (id) => stuffToSell[id].price;
console.log(getPrice('Coffee Maker'));
Look at this code snippet (Item's name: price)
var stuffToSell = {
'Coffee Maker': 5,
'Mountain Bike': 10,
'26 inch TV': 15
};
let getPrice = (id) => stuffToSell[id];
console.log(getPrice('Coffee Maker'));

can I filter a countBy in lodash?

For a list like (with many many entries):
-
id: cs13157
name: 'Current Load'
-
id: cs1085
name: 'Check CPU'
-
id: cs1070
name: Uptime
-
id: cs1070
name: 'Total Processes'
I've got a .countBy for ids that is returning some counts that are greater than 1 and some equal to 1. I only care about the greater than 1 values, since that indicates a problem. But since countBy is returning a big object, I have to loop the hash table values:
counts = ld(list).countBy('id').value();
for (k in counts) {
v = counts[k];
if (!(v > 1)) {
continue;
}
logger.error("found duplicate value for ID " + k);
errored = true;
}
// throw if errored is true
This seems a like too much code but I can't see a way to quickly do this with lodash. I could pluck/map/filter to find the errant values. There's plenty of ways to do this but I'm wondering if there's a way to only get the list of IDs with a count greater than one with lodash and not with a loop.
SSCE:
var arr, keys, ld, s;
s = "- {id: 2240errorapp, name: 'app error count'}\n- {id: 2240errorinf, name: 'infrastructure error count'}\n- {id: '2112', name: calls}\n- {id: '2112', name: calls}\n- {id: 2112v, name: violations}\n- {id: 2112avg, name: 'average response time'}\n- {id: 2112errorapp, name: 'app error count'}\n- {id: 2112errorinf, name: 'infrastructure error count'}";
ld = require('lodash');
arr = (require('yamljs')).parse(s);
keys = ld(arr).countBy('id').filter(function(k, v) {
return k[v] > 1;
}).value();
console.dir(keys);
Expected value: ['2112'] (the duplicate). Actual value: [].
counts.pick(function(v) { return v > 1; });
pick, not filter. Also v > 1 not k[v] > 1 as in your code.
filter wants an array of objects, not an object with many uniform keys. pick likes to work with object properties and filter out keys.
My answer is a duplicate of this question but I wouldn't recommend closing this question since there's more than one way to solve this problem.

Categories

Resources