Related
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));
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);
I have array objects:
[
{
id: 1,
name: ABC,
age: 10
},
{
id: 2,
name: ABCXYZ,
age: 20
},
{
id: 3,
name: ZYXCNA,
age: 30
},
...
...
....more
]
and array is value of id in above array object:
[ 1, 2]
then i want a array objects not have value of id in above array objects.
Ex: Array objects i will receive in here is
[
{
id: 3,
name: ZYXCNA,
age: 30
},
{
id: 4,
name: ABCDX,
age: 30
}
]
i have using 3 for loop in here to slove this problem but i think that can have smarter way.
Like using reduce in here, but i still not resolved. Can anyone help me? Thanks so much.
You can do this using Array.filter & Array.includes.
const source = [
{
id: 1,
name: 'ABC',
age: 10
},
{
id: 2,
name: 'ABCXYZ',
age: 20
},
{
id: 3,
name: 'ZYXCNA',
age: 30
},
{
id: 4,
name: 'ABCDX',
age: 30
}
];
const input = [ 1, 2 ];
const output = source.filter(({ id }) => !input.includes(id));
console.log(output);
Yo don't need any explicit loops at all, you're just filtering
var input = [
{
id: 1,
name: "ABC",
age: 10
},
{
id: 2,
name: "ABCXYZ",
age: 20
},
{
id: 3,
name: "ZYXCNA",
age: 30
},
];
var ids = [1,2];
var result = input.filter(x => ! ids.includes(x.id));
console.log(result);
I am working with some json returned via an API call and trying to figure if it's possible to filter one json object based on the values from another. I came across set and map which is close, but not sure how to handled the nested attributed... here is a simplified example:
var teachers = [
{id: 1, name : 'Bob',studentid: []},
{id: 2, name : 'Sue',studentid: []},
{id: 3, name : 'Jean',studentid: []},
{id: 4, name : 'Jim',studentid: [
"500zz"
]},
{id: 5, name : 'Paul',studentid: [
"100zz",
"120zz",
"130zz"
]}
];
var students = [
{_id: "100zz", name : 'Betty'},
{_id: "120zz", name : 'Bob'},
{_id: "500zz", name : 'Billy'}
];
console.log(
teachers.filter(
(set => item => set.has(item.studentid))(new Set(students.map(item => item._id)))
)
)
I would like the result to include only the teachers Jim and Paul ( with all associated data )... not sure what I need to add or do? The array of studentid's is throwing me for a loop.
const teachers = [
{ id: 1, name: "Bob", studentid: [] },
{ id: 2, name: "Sue", studentid: [] },
{ id: 3, name: "Jean", studentid: [] },
{ id: 4, name: "Jim", studentid: ["500zz"] },
{ id: 5, name: "Paul", studentid: ["100zz", "120zz", "130zz"] }
];
const students = [
{ _id: "100zz", name: "Betty" },
{ _id: "120zz", name: "Bob" },
{ _id: "500zz", name: "Billy" }
];
// Filter teachers with students greater than 0.
const filtered = teachers.filter(x => x.studentid.length > 0);
// Map teachers with Students
const teachersWithStudents = filtered.map(x => {
const stu = students
.map(student => {
// Determine whether or not teacher 'studentid' contains student '_id'
const hasStudent = x.studentid.includes(student._id);
// Teacher has student
if (hasStudent) {
return {
id: student._id,
name: student.name
};
}
// Teacher does not have student
return false;
})
.filter(x => x); // filter objects that are not false
// Return teacher object with student (stu) objects
return {
...x,
studentid: stu
};
// Return those with matches only
}).filter(x => x.studentid.length > 0);
console.log(teachersWithStudents);
I have the following object:
[
{ name: "Peter", id: 25, job: "carpenter" },
{ name: "Peter", id: 25, job: "shelf maker" },
{ name: "John", no: 20, job: "student" },
{ name: "John", id: 20, job: "university student" },
{ name: "John", id: 20, job: "student at uni still" },
{ name: "Jack", id: 20, job: "university student" }
]
I would like to go through this array and whenever name and id are identical I would like to only keep one entry, namely the one appearing the latest in the array, and discard all the rest. How would I do this?
I have tried
for(let i=0;i<people.length;i++) {
const person = people[i];
const result = people.filter(person => person.id === id && person.name === name);
people[i] = person;
}
... but this doesn't work. Any ideas what I'm doing wrong? How would you approach this?
You could use reduceRight to build new array starting the iteration from the end of the array, and also Map as accumulator value to store key - value pairs.
const data = [{"name":"Peter","id":25,"job":"carpenter"},{"name":"Peter","id":25,"job":"shelf maker"},{"name":"John","no":20,"job":"student"},{"name":"John","id":20,"job":"university student"},{"name":"John","id":20,"job":"student at uni still"},{"name":"Jack","id":20,"job":"university student"}]
const map = data.reduceRight((r, e) => {
const key = `${e.name}|${e.id}`;
if (!r.has(key)) r.set(key, e);
return r;
}, new Map);
const uniq = [...map.values()];
console.log(uniq)
I'd reduce into an object, whose keys are the ID and name put together, and whose values are the latest object with a particular ID and name found so far, and then get the object's values:
const input=[{name:"Peter",id:25,job:"carpenter"},{name:"Peter",id:25,job:"shelf maker"},{name:"John",no:20,job:"student"},{name:"John",id:20,job:"university student"},{name:"John",id:20,job:"student at uni still"},{name:"Jack",id:20,job:"university student"}];
const output = Object.values(
input.reduce((a, obj) => {
const { name, id } = obj;
const key = `${name}_${id}`;
a[key] = obj;
return a;
}, {})
);
console.log(output);
Computational complexity is O(N), since there are no nested loops.
You can use reduceRight with a Map to check if the object already exists in the accumulator - if it does, ignore it, else push the new object, and new key + index pair to the map:
const arr = [
{ name: "Peter", id: 25, job: "carpenter" },
{ name: "Peter", id: 25, job: "shelf maker" },
{ name: "John", no: 20, job: "student" },
{ name: "John", id: 20, job: "university student" },
{ name: "John", id: 20, job: "student at uni still" },
{ name: "Jack", id: 20, job: "university student" }
];
const m = new Map([])
const output = arr.reduceRight((a, o, i) => (m.has(o.name + o.id) || a.push(o) && m.set(o.name + o.id, i), a), [])
console.log(output)
However, a regular for loop is the fastest solution here:
const arr = [
{ name: "Peter", id: 25, job: "carpenter" },
{ name: "Peter", id: 25, job: "shelf maker" },
{ name: "John", no: 20, job: "student" },
{ name: "John", id: 20, job: "university student" },
{ name: "John", id: 20, job: "student at uni still" },
{ name: "Jack", id: 20, job: "university student" }
];
const m = new Map([])
const out = []
for (var i = arr.length - 1; i >= 0; i--) {
const o = arr[i]
if (!m.has(o.name + o.id)) {
out.push(o)
m.set(o.name + o.id, i)
}
}
console.log(out)
See the performance test here with a larger, shuffled array.