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));
I have written a function which will return a name with the greatest number of children from a contact list (array of objects), but it is limited to the number of elements I include in the if statement. How can I make it go over all the elements in this contact list, regardless of size? I tried to make a for of loop, but it won't let me sort through elements using a counter "I", returning an error.
Thanks in advance!
let contacts = [
{
name: 'John',
children:
[
{ name: 'Mary', age: 11 }
]
}, {
name: 'Franklin',
children:
[
{ name: 'Susy', age: 7 },
{ name: 'Tom', age: 5 }
]
}
];
function p_w_m_children(arr){
let person_w_most_children = "";
arr.sort((elem_1, elem_2) => {
if (elem_1.children > elem_2.children){
person_w_most_children = elem_1.name;
}else{
person_w_most_children = elem_2.name;
}
});
return person_w_most_children;
}
console.log("The person who has most children is " + p_w_m_children(contacts)+ ".");
To find the person with the most children using arr.sort, you can use the following:
arr.sort((elem_1, elem_2) => {
return elem_1.children.length - elem_2.children.length
}
The key points being that you compare the .length of the arrays (rather than just comparing the arrays themselves) and that the .sort callback returns a value that can be sorted. The return is positive when elem_1.children.length > elem_2.children.length, negative when elem_1.children.length < elem_2.children.length and 0 when they are equal. This means the sort function can order the array correctly.
Then, once the array is sorted you can simply get the last element in the sorted array (the element with the largest value).
Edit for Clarification
let sortedArr = arr.sort((elem_1, elem_2) => {
return elem_1.children.length - elem_2.children.length;
};
return sortedArr[sortedArr.length - 1].name;
You can loop through the array and keep a record of the contact who has the most children. Replace that record with the current one in the loop if the number of children is greater.
Then return that contact when the loop is finished.
let contacts = [{
name: 'John',
children: [{
name: 'Mary',
age: 11
}]
}, {
name: 'Franklin',
children: [{
name: 'Susy',
age: 7
},
{
name: 'Tom',
age: 5
}
]
}];
function p_w_m_children(arr) {
let person_w_most_children;
contacts.forEach(contact => {
if (!person_w_most_children || contact.children.length > person_w_most_children.children.length) {
person_w_most_children = contact;
}
})
return person_w_most_children;
}
console.log("The person who has most children is " + p_w_m_children(contacts).name + ".");
Same as above without using forEach :
let contacts = [{
name: 'John',
children: [{
name: 'Mary',
age: 11
}]
}, {
name: 'Franklin',
children: [{
name: 'Susy',
age: 7
},
{
name: 'Tom',
age: 5
}
]
}];
function p_w_m_children(arr) {
let person_w_most_children;
for (let i = 0; i < contacts.length; i++) {
if (!person_w_most_children || contacts[i].children.length > person_w_most_children.children.length) {
person_w_most_children = contacts[i];
}
}
return person_w_most_children;
}
console.log("The person who has most children is " + p_w_m_children(contacts).name + ".");
I have an array of objects and I want to create a view tree. But I have a problem with creating. My name Test1/Test2 and Test1/Test2/Test3 (id 147) was missed in final tree.
My snippet:
let arr = [{id: 145, name: "Test1/Test2", public: false},
{id: 146, name: "Test1/Test2/Test3", public: false},
{id: 147, name: "Test1/Test2/Test3", public: false},
{id: 148, name: "Test1/Test2/Test4", public: false}];
let result = [];
let level = { result };
arr.forEach((path) => {
path.name.split("/").reduce((r, name, i, a) => {
if (!r[name]) {
r[name] = { result: [], id: path.id };
r.result.push({ name, children: r[name].result });
}
return r[name];
}, level);
});
console.log(result)
Expected result:
[
{
name: "Test1",
children: [
{
name: "Test2",
children: [],
id: 145
},
{
name: "Test2",
children: [
{
name: "Test3",
children: [],
id: 146
},
{
name: "Test3",
children: [],
id: 147
},
{
name: "Test4",
children: [],
id: 148
},
],
},
],
},
];
If I understand well, the number of leaves in your tree should equal the number entries in the input array. So a leaf would never get any children. This is what made you give "Test1" two children, even though all paths have "Test2" as the next part: one child for a leaf, and another functioning as internal node.
By consequence, leaves don't really need a children property, as that children array would always remain empty.
It is clear that the last element of a path needs to be processed a bit differently. That part should always result in the creation of a new node in the tree. The other parts can reuse a (non-leaf) node, if one is available.
This leads to the following change in your code:
let arr = [
{id: 145, name: "Test1/Test2", public: false},
{id: 146, name: "Test1/Test2/Test3", public: false},
{id: 147, name: "Test1/Test2/Test3", public: false},
{id: 148, name: "Test1/Test2/Test4", public: false}
];
let result = [];
let level = { result };
arr.forEach(({name, id}) => { // destructure
let parts = name.split("/");
name = parts.pop(); // don't pass the last part through reducer
parts.reduce((r, name, i) => {
if (!r[name]) {
r[name] = { result: [] };
r.result.push({ name, children: r[name].result });
}
return r[name];
}, level).result.push({ name, id }); // add last part here
});
console.log(result);
Here's the code. I left the function block blank because I'm stumped
function getSummedAge(people) {
}
const examplePeopleArray = [
{ name: 'John', age: 10 },
{ name: 'Jack', age: 20 },
{ name: 'Jane', age: 25 }
];
console.log(getSummedAge(examplePeopleArray));
Checkout the Array.reduce method! It's purpose is to basically reduce an array into a single value, which I believe is what you're trying to do here: Array.reduce() MDN
Here's a little example of how you might use it in this context
function sumAllApplesIn(baskets){
return baskets.reduce((accumulator, currentBasket) => accumulator + currentBasket.apples, 0)
}
const baskets = [
{ basket: "one", apples: 10},
{ basket: "two", apples: 15},
{ basket: "three", apples: 30}
];
console.log(sumAllApplesIn(baskets));
You simply need to loop through the people array, grab the 'person' object, and then access the age key. Sum those up and voila
function getSummedAge(people) {
let summedAge = 0;
for (let pdx = 0, plen = people.length; pdx < plen; pdx++) {
let person = people[pdx];
summedAge += person.age;
}
return summedAge
}
const examplePeopleArray = [
{ name: 'John', age: 10 },
{ name: 'Jack', age: 20 },
{ name: 'Jane', age: 25 }
];
console.log(getSummedAge(examplePeopleArray));
In my Javascript program, I have a list of Person objects.
For example
[
"Michael": {
"age": 45,
"position": "manager",
...
},
"Dwight": {
"age": 36,
"position": "assistant manager",
...
},
....
]
I want to find the youngest Person.
I've accomplished this by creating two arrays: one of all the Persons and one of all their ages, and getting the index of the lowest age and applying it to the first array. Like:
var arrayOfPersons = [persons[0], persons[1], ....];
var arrayOfAges = [persons[0].age, persons[1].age, ....];
var min = arrayOfAges.indexOf(Math.max.apply(Math, arrayOfAges));
var youngestPerson = arrayOfPerson[min];
The problem with this is it is inefficient doesn't seem like the best way. Also it doesn't deal with the fact that there may be a tie for youngest.
Does anyone know of a more native, simpler way to do this?
You can sort the persons array by age property and pick first one:
var persons = [
{ name: 'Michael', age: 45, position: 'manager' },
{ name: 'Dwight', age: 36, position: 'assistant manager' },
{ name: 'Foo', age: 99, position: 'foo' },
{ name: 'Bar', age: 37, position: 'bar' }
];
persons.sort(function(a, b) {
return a.age > b.age;
});
console.log('Youngest person: ', persons[0]);