How to improve time complexity of this snippet? - javascript

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));

Related

How to change value in array with objects in JS

I have an array and want to change name in object { id: 4, name: 'name4' } to 'name6'
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' }
]
},
{
id: '3456',
desc: 'sample2',
items: [
{ id: 4, name: 'name4' },
{ id: 5, name: 'testItem5' }
]
},
I try in this way but it isn't working
const name = 'name4';
const result = example?.forEach((group) =>
group.items.forEach((item) =>
if (item.name === name) {
return item.name === 'name6';
}
return null;
})
);
The for...of statement is my recommendation for readability and loop optimisation.
const example = [
{
id: '1234',
desc: 'sample1',
items: [
{ id: 1, name: 'name1' },
{ id: 2, name: 'testItem2' },
],
},
{
id: '3456',
desc: 'sample2',
items: [
{ id: 4, name: 'name4' },
{ id: 5, name: 'testItem5' },
],
},
];
const oldName = 'name4';
const newName = 'name6';
for (const group of example) {
for (const item of group.items) {
if (item.name === oldName) {
item.name === newName;
break
}
}
}
You could even go a step further and terminate the outer loop with a label if you only need to change the name in a single group.
outerLoop: for (const group of example) {
for (const item of group.items) {
if (item.name === oldName) {
item.name === newName;
break outerLoop;
}
}
}
Hope this helps.
You could either change the value by simply assigning a new value.
example[1].items[0].name = 'name6'
But you can also iterate through all items and search for the name you want to change. I created a function that goes through an array and loops over its nested items arrays searching for any given name (targetName) and replacing it with a new one (newName):
function changeName(array, targetName, newName) {
// Loop through the elements of array
array.forEach((element) => {
// Check each item: change the name if it matches the target
element.items.forEach((item) => {
if (item.name === targetName) item.name = newName;
});
});
}
// This function will check example array and change
// every name that has a value 'name4' into 'name6'
changeName(example, "name4", "name6");
forEach doesn't return any value.
Instead of return item.name === 'name6' you can simply set new value to item.name.
Why not like this?
const example = [{
id: '1234',
desc: 'sample1',
items: [{
id: 1,
name: 'name1'
},
{
id: 2,
name: 'testItem2'
}
]
},
{
id: '3456',
desc: 'sample2',
items: [{
id: 4,
name: 'name4'
},
{
id: 5,
name: 'testItem5'
}
]
},
]
example[1].items[0].name = 'name6'
console.log(example)

Running a mapping task what is the correct 2nd array method for processing one of the newly created item's values?

I have an array containing objects:
let sportTag = [
{ id: 1, name: 'FOOTBALL', found: false },
{ id: 2, name: 'TENNIS' , found: false },
{ id: 3, name: 'BASKET' , found: false },
]
I have also have another array containing objects and for every object a field (sports) that is an array:
let person = [{
id: 1,
name: "Mark",
age: 23,
sports: ["volleyball", "rugby", "tennis"],
}, {
id: 2,
name: "Rupert",
age: 40,
sports: ["golf"],
}, {
id: 3,
name: "John",
age: 31,
sports: ["football", "golf", "rugby", "tennis"],
}]
I would like to change sportTag found field to true when sportTag name is equal to every person sport.
I tried with a nested map
const result = sportTag.map(st => {
person.map(p => {
p.sports.map(s => {
if (st.name.toLocaleUpperCase() === s.toLocaleUpperCase()) {
return {
...st, found: true
}
}
return s
})
return p
})
return st
})
console.log(sportTag)
//OUTPUT
// { id: 1, name: 'FOOTBALL', found: false },
// { id: 2, name: 'TENNIS' , found: false },
// { id: 3, name: 'BASKET' , found: false }
console.log(result)
//OUTPUT
// { id: 1, name: 'FOOTBALL', found: false },
// { id: 2, name: 'TENNIS' , found: false },
// { id: 3, name: 'BASKET' , found: false }
Why are the changes not reflected in the result? I expect the output to be:
{ id: 1, name: 'FOOTBALL', found: true },
{ id: 2, name: 'TENNIS' , found: true },
{ id: 3, name: 'BASKET' , found: false }
The problem with your code is that you are always returning st for each iteration of the first map, so you get the original values.
You probably want something like this:
const result = sportTag.map(st => {
const foundSport = person.find(p =>
p.sports.find(s => st.name.toLocaleUpperCase() === s.toLocaleUpperCase())
);
return foundSport
? { ...st, found: true }
: st;
});
console.log(sportTag)
// { id: 1, name: 'FOOTBALL', found: false },
// { id: 2, name: 'TENNIS', found: false },
// { id: 3, name: 'BASKET', found: false }
console.log(result)
// { id: 1, name: 'FOOTBALL', found: true },
// { id: 2, name: 'TENNIS', found: true },
// { id: 3, name: 'BASKET', found: false }
From the above comment ...
The OP already mentions in the description of the problem the correct way of achieving what the OP wants ... "I would like to change [the] sportTag's found field to true when [the] sportTag's name [value] is equal to every [any/some] person's sport [item]." ... thus the OP does not need to implement a nested, twice map but a map/some task.
But (especially for a bigger amount of data) instead of following the above suggested approach which within every map iteration additionally iterates again with every nested some task, one could choose a lookup based approach which works with a Map instance. The mapping task itself will be very simple. For the latter one could even choose an implementation which makes the mapping agnostic to the current lookup's variable/constant name since one would provide its reference as the map method's 2nd thisArg parameter.
One of cause could implement the lookup creation with less iteration cycles. But since it is done once it will never become the performance bottleneck.
function createLookupOfAnyPracticedSport(persons) {
return new Map(
// (5) create a map as lookup for unique sport items.
Array
// (3) create array from set of step (2)
.from(
new Set(
// (2) create a set of unique sport
// items/values as of step (1)
persons
// (1) concatenate array of all `sports`
// practiced by any person.
.reduce((result, { sports }) =>
result.concat(sports), []
)
)
)
// (4) sanitize and map the unique sport items/values
// in order to qualify as entries for step (5) ...
.map(sport => [sport.toLocaleUpperCase(), true])
);
}
function createUpToDateSportTagFromBoundSports(tagItem) {
const allSportsLookup = this;
// create (updated) shallow copy of the original
// sport tag item in order to not directly mutate
// such an item's original reference.
return {
...tagItem,
found: allSportsLookup
.has(tagItem.name.toLocaleUpperCase())
};
}
const personList = [{
id: 1, name: "Mark", age: 23,
sports: ["volleyball", "rugby", "tennis"],
}, {
id: 2, name: "Rupert", age: 40,
sports: ["golf"],
}, {
id: 3, name: "John", age: 31,
sports: ["football", "golf", "rugby", "tennis"],
}];
const sportTagList = [{
id: 1, name: 'FOOTBALL', found: false,
}, {
id: 2, name: 'TENNIS', found: false,
}, {
id: 3, name: 'BASKET', found: false,
}];
const mappedTagList = sportTagList
.map(
createUpToDateSportTagFromBoundSports,
createLookupOfAnyPracticedSport(personList),
);
console.log({
mappedTagList,
sportTagList,
personList,
});
console.log(
'entries of any practiced sport ...',
[...createLookupOfAnyPracticedSport(personList).entries()],
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
let person = [{
id: 1, name: "Mark", age: 23,
sports: ["volleyball", "rugby", "tennis"],
}, {
id: 2, name: "Rupert", age: 40,
sports: ["golf"],
}, {
id: 3, name: "John", age: 31,
sports: ["football", "golf", "rugby", "tennis"],
}];
let sportTag = [{
id: 1, name: 'FOOTBALL', found: false,
}, {
id: 2, name: 'TENNIS', found: false,
}, {
id: 3, name: 'BASKET', found: false,
}];
sportTag.forEach((elem, index, array) => {
person.forEach((el, i, arr) => {
if (person[i].sports.indexOf(sportTag[index].name.toLocaleLowerCase()) != -1) {
sportTag[index].found = true;
}
});
});
console.log(sportTag);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Conditional copying properties and values from one array of objects to another

Got two arrays of objects and my goal is to check if the value under property id from array1 matches the value under the property categoryId of array2. When find a match want to add the missing property amount to the relevant member of array1 or create a new array containing all the properties and values I need - id, name, amount
Here are the two arrays:
const array1 = [{
id: 8,
name: 'Online Shopping',
},
{
id: 12,
name: 'Subscriptions',
},
{
id: 5,
name: 'Patreon donations',
}]
and
const array2 = [
{
expence: {
amount: -66.66,
},
categoryId: 5,
},
{
expence: {
amount: 100018.85,
},
categoryId: 0,
},
{
expence: {
amount: -43340.9,
},
categoryId: 12,
},]
Tried to combine different approaches from answers to similar but simpler cases already posted in the community but didn't managed to make them work in my case.
Loop through each item in array1, then loop through each item in array2 inside the loop and check whether the categoryId is equal to the id.
const array1 = [{
id: 8,
name: 'Online Shopping',
},
{
id: 12,
name: 'Subscriptions',
},
{
id: 5,
name: 'Patreon donations',
}
]
const array2 = [{
expence: {
amount: -66.66,
},
categoryId: 5,
},
{
expence: {
amount: 100018.85,
},
categoryId: 0,
},
{
expence: {
amount: -43340.9,
},
categoryId: 12,
},
]
array1.forEach((e) => {
array2.forEach((f) => {
if (f.categoryId == e.id) {
e.amount = f.expence.amount;
}
})
})
console.log(array1);
You can also make use of Array.filter to find the item where the categoryId is equal to the id:
const array1 = [{
id: 8,
name: 'Online Shopping',
},
{
id: 12,
name: 'Subscriptions',
},
{
id: 5,
name: 'Patreon donations',
}
]
const array2 = [{
expence: {
amount: -66.66,
},
categoryId: 5,
},
{
expence: {
amount: 100018.85,
},
categoryId: 0,
},
{
expence: {
amount: -43340.9,
},
categoryId: 12,
},
]
array1.forEach((e) => {
var arr = array2.filter(f => f.categoryId == e.id);
if(arr.length > 0) e.amount = arr[0].expence.amount;
})
console.log(array1);

Filter inside filter in JavaScript

I need to filter some data inside an array of objects which is contained in another array of objects. Here is the sample structure of my data. I need to filter on categories.
[
{
id: 540,
name:'Makeup kit'
slug:'makeup-kit',
status:'publish',
categories: [
{
id: 42, name:'Fashion',slug:'fashion'
},
{
id: 43, name:'Beauty',slug:'beauty'
}
]
},
{
id: 541,
name:'Silicon gloves'
slug:'silicon-gloves',
status:'publish',
categories: [
{
id: 44, name:'Health',slug:'health'
}
]
},
{
id: 650,
name:'Julep Mask'
slug:'julep-mask',
status:'publish',
categories: [
{
id: 43, name:'Beauty',slug:'beauty'
}
]
}
]
Here is how I'm trying
beautyProducts=temp1.filter(product=>product.categories.filter(cat=>cat.id===43))
but my solution doesn't seem to work.
Array#filter() expects the function you give it to return a truthy or falsy value. Elements for which the function returns a truthy value are kept in the new array, and those that give a falsy value are removed.
You want to keep only elements for which one of the categories has an id of 43. Using a second filter, then, makes no sense here: it returns an array, and arrays are always truthy; therefore the first filter will always receive an array for each element and all elements are kept in the new array.
Instead of a second filter, you should use Array#some() - you want to know if any of the categories have id===43, and if none of them do, then you want a falsy value so that the product gets excluded from the results.
Simple change:
beautyProducts = temp1.filter(product => product.categories.some(cat => cat.id === 43))
Here is a working sample:
let temp1 = [{id:540,name:'Makeup kit',slug:'makeup-kit',status:'publish',categories:[{id:42,name:'Fashion',slug:'fashion'},{id:43,name:'Beauty',slug:'beauty'}]},{id:541,name:'Silicon gloves',slug:'silicon-gloves',status:'publish',categories:[{id:44,name:'Health',slug:'health'}]},{id:650,name:'Julep Mask',slug:'julep-mask',status:'publish',categories:[{id:43,name:'Beauty',slug:'beauty'}]}];
let beautyProducts = temp1.filter(product => product.categories.some(cat => cat.id === 43));
console.log(beautyProducts);
Try like this.
beautyProducts = temp1.map(({categories, ...others}) => {
const filteredCategories = categories.filter(cat => cat.id === 43);
return {
filteredCategories,
...others
};
}).filter(product => product.categories.length > 0)
So first, you should do the inner filter first and map the inner filtered data to the current one and do the main filter after that like above.
let data = [
{
id: 540,
name: 'Makeup kit',
slug: 'makeup-kit',
status: 'publish',
categories: [
{
id: 42, name: 'Fashion', slug: 'fashion'
},
{
id: 43, name: 'Beauty', slug: 'beauty'
}
]
},
{
id: 541,
name: 'Silicon gloves',
slug: 'silicon-gloves',
status: 'publish',
categories: [
{
id: 44, name: 'Health', slug: 'health'
}
]
},
{
id: 650,
name: 'Julep Mask',
slug: 'julep-mask',
status: 'publish',
categories: [
{
id: 43, name: 'Beauty', slug: 'beauty'
}
]
}
];
let beautyProducts = data.map(product => {
const categories = product.categories.filter(cat => cat.id === 43);
if (categories.length) {
return { ...product, categories };
}
return null;
}).filter(p => p);
console.log("Prod:", beautyProducts);
console.log(">>>>>>>>>>>>>>>>>>>>>>>>>");
let beautyProductsTwo = data.filter(product => product.categories.some(cat => cat.id === 43));
console.log("Prod ans two:", beautyProductsTwo);

How to check if an id in one array of json objects exists in another nested array of json objects?

I am making a call to two end points and need to display all the companies with their funds,name and factory that produces for that company.
here is the response from one end point
let factories = [
{
id: 1,
name: "Xintang",
short: "xin",
companies: [0, 4, 101,198]
},
{
id: 2,
name: "Ohio Plant",
short: "OHP",
companies: [22, 27]
},
{
id: 3,
name: "Cincy",
short: "Cin",
companies: []
}
];
Here is the response from the second
let companies = [
{
id: 0,
fund: "79588.96",
name: "Microsoft"
},
{
id: 1,
fund: "166727.06",
name: "Comcast"
},
{
id: 2,
fund: "131206.88",
name: "Apple"
},
{
id: 3,
fund: "74095.75",
name: "HP"
},
{
id: 4,
fund: "142556.86",
name: "Dell"
}
];
the dataset is much bigger, but here is just a sample. So I want be able to create a new object that links the factory with the specific company. Is there a way I can map over the companies and check which factory has the company id in that nested array so that I can add a new property factory to the company, and have a new array of objects that would look like this.
let returnedArr = [
{
id: 0,
fund: "79588.96",
name: "Microsoft",
factory: "Xintang"
},
{
id: 4,
fund: "142556.86",
name: "Dell",
factory: "Xintang"
}
];
You can do the following using reduce and Map.
Get the company-id and factory-name Map -> Then loop through the companies and create the output
let factories = [{id:1,name:"Xintang",short:"xin",companies:[0,4,101,198]},{id:2,name:"Ohio Plant",short:"OHP",companies:[22,27]},{id:3,name:"Cincy",short:"Cin",companies:[]}],
companies = [{id:0,fund:"79588.96",name:"Microsoft"},{id:1,fund:"166727.06",name:"Comcast"},{id:2,fund:"131206.88",name:"Apple"},{id:3,fund:"74095.75",name:"HP"},{id:4,fund:"142556.86",name:"Dell"}]
/*Get the company id: factory name mapping*/
const map = factories.reduce((m, f) =>
(f.companies.forEach(c => m.set(c, f.name)), m)
, new Map);
const output = companies.map(c => ({...c, factory: map.get(c.id) || ''}));
console.log(output)
Try This.... It may help u...
let result = [];
companies.forEach(company => {
let tempCompany = {...company};
factories.forEach(factory => {
let tempArray = factory.companies.filter(item => item === company.id);
if(tempArray.length > 0) {
tempCompany.factory = factory.name;
}
});
result.push(tempCompany);
});
One way to do this is to create a map of company ids to factory ids, then just iterate through your companies array and add the corresponding factory to the company object, like so:
The big advantage of this is that your factoryid lookups will be O(1), and it is O(n) to build the map. Your entire algorithm will be O(n). This makes this extremely fast even for very large data sets.
let factories = [
{
id: 1,
name: "Xintang",
short: "xin",
companies: [0, 4, 101,198]
},
{
id: 2,
name: "Ohio Plant",
short: "OHP",
companies: [22, 27]
},
{
id: 3,
name: "Cincy",
short: "Cin",
companies: []
}
];
let companies = [
{
id: 0,
fund: "79588.96",
name: "Microsoft"
},
{
id: 1,
fund: "166727.06",
name: "Comcast"
},
{
id: 2,
fund: "131206.88",
name: "Apple"
},
{
id: 3,
fund: "74095.75",
name: "HP"
},
{
id: 4,
fund: "142556.86",
name: "Dell"
}
];
var factoryMap = factories.reduce((res, curr) => {
return Object.assign(res, curr.companies.reduce((_res, _curr) => (_res[_curr] = curr.name, res), {}))
}, {});
var mappedCompanies = companies.map(company => Object.assign(company, {factory: factoryMap[company.id] || ""}));
console.log(mappedCompanies);
Assuming that a company can have more than one factory.
Try the following
let factories = [{
id: 1,
name: "Xintang",
short: "xin",
companies: [0, 4, 101, 198]
},
{
id: 2,
name: "Ohio Plant",
short: "OHP",
companies: [22, 27]
},
{
id: 3,
name: "Cincy",
short: "Cin",
companies: []
}
];
let companies = [{
id: 0,
fund: "79588.96",
name: "Microsoft"
},
{
id: 1,
fund: "166727.06",
name: "Comcast"
},
{
id: 2,
fund: "131206.88",
name: "Apple"
},
{
id: 3,
fund: "74095.75",
name: "HP"
},
{
id: 4,
fund: "142556.86",
name: "Dell"
}
];
let returnedArr = companies.map(company => {
company.factories = factories
.filter(factory => factory.companies.includes(company.id))
.map(factory => factory.name);
return company;
});
console.log(JSON.stringify(returnedArr, null, 4));

Categories