Related
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)
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 want to loop over the array and make my loop return function or one time string to say the name exist?
this is the error
TypeError: Cannot read property 'name' of undefined
var x = arr[i].name;
var arr = [
{id: 1, name: "php"},
{id: 2, name: "mysql"},
{id: 3, name: "laravel"},
{id: 4, name: "codeigniter"},
{id: 5, name: "wordpress"},
{id: 6, name: "sql"},
{id: 7, name: "jquery"},
{id: 8, name: "javascript"},
];
var string;
function checkemail(arr, string) {
var i;
for (i = 0; i < arr.length; i++) {
let newname = arr[i].name;
if (newname !== string) {
return storename();
} else {
console.log("name found")
}
}
}
}
console.log(checkemail(arr, "javascript"));
You should loop through the entire array before making a decision on the existence of the element in the array
You are calling storename function on the first mismatch of the name and parameter. This will make the function checking the first element and return after calling storename function.
My implementation
function storename() {
return 'storename function';
}
var arr = [
{ id: 1, name: "php" },
{ id: 2, name: "mysql" },
{ id: 3, name: "laravel" },
{ id: 4, name: "codeigniter" },
{ id: 5, name: "wordpress" },
{ id: 6, name: "sql" },
{ id: 7, name: "jquery" },
{ id: 8, name: "javascript" },
];
var string;
function checkemail(arr, string) {
var i;
let isFound = false;
for (i = 0; i < arr.length; i++) {
let newname = arr[i].name;
if (newname === string) {
isFound = true;
i = arr.length; // Item found, you can exit the loop now
return "name found";
}
}
if(!isFound) {
return storename()
}
}
console.log(checkemail(arr, "javascript"));
Array.find implementation:
You can implement the same logic without using a manual loop by using Array.find as below.
var arr = [
{ id: 1, name: "php" },
{ id: 2, name: "mysql" },
{ id: 3, name: "laravel" },
{ id: 4, name: "codeigniter" },
{ id: 5, name: "wordpress" },
{ id: 6, name: "sql" },
{ id: 7, name: "jquery" },
{ id: 8, name: "javascript" },
];
var string;
function storename() {
return 'storename function';
}
function checkemail(arr, string) {
const node = arr.find(item => item.name === string);
return node ? "name found" : storename();
}
console.log(checkemail(arr, "javascript"));
Assign at least one unique item to each users at random. e.g if 5 items & 5 users, each users get 1 items. if 4 items & 5 users, only 4 users get 1 items each. if 7 items & 4 users, then 3 users get 2 items each & 1 user get 1 item and so on. The script below was meant to achieved this but got a wrong result.
assignItemsToUsers() {
let items = [
{
id: 1,
name: "item1",
tag: 1900
},
{
id: 2,
name: "item2",
tag: 1876
},
{
id: 3,
name: "item3",
tag: 1575
},
{
id: 4,
name: "item4",
tag: 4783
},
{
id: 5,
name: "item5",
tag: 67894
},
{
id: 6,
name: "item6",
tag: 66789
},
{
id: 7,
name: "item7",
tag: 67890
},
{
id: 8,
name: "item8",
tag: 87654
},
{
id: 9,
name: "item9",
tag: 94948
}
];
let users = [
{
id: 1,
name: "user1",
userID: 38494
},
{
id: 2,
name: "user2",
userID: 84844
},
{
id: 3,
name: "user3",
userID: 47483
},
{
id: 4,
name: "user4",
userID: 83735
}
];
let results = [];
let tempResults = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const randomUser = users[Math.floor(Math.random() * users.length)];//pick user at random
randomUser.tempitem = item;//asign item to this user
tempResults.push(randomUser);//push to tempResults
}
//Group assigned items to their respective user
for (let i = 0; i < users.length; i++) {
const user = users[i];
let myluckyitems = [];
for (let index = 0; index < tempResults.length; index++) {
//check where tempResults[index].id == user.id
const assigneduser = tempResults[index];
if (user.id == assigneduser.id) {
//True: this was the assigned user.
myluckyitems.push(assigneduser.tempitem);
}
}
//all items assigned, then append to user object & push to results
if (myluckyitems.length > 0) {
user.items = myluckyitems;
results.push(user);
}
}
console.log(results);
}
Actual Result
results = [
{
id: 1,
name: user1,
userID: 38494,
items: [
{
id: 1,
name: item1,
tag: 1900
},
{
id: 1,
name: item1,
tag: 1900
}
],
tempitem: {
id: 1,
name: item1,
tag: 1900
},
},
{
id: 2,
name: user2,
userID: 84844,
items: [
{
id: 2,
name: item2,
tag: 1876
},
{
id: 2,
name: item2,
tag: 1876
},
{
id: 2,
name: item2,
tag: 1876
},
],
tempitem: {
id: 2,
name: item2,
tag: 1876
},
},
{
id: 3,
name: user3,
userID: 47483,
items: [
{
id: 3,
name: item3,
tag: 1575
},
{
id: 3,
name: item3,
tag: 1575
}
],
tempitem: {
id: 3,
name: item3,
tag: 1575
},
},
{
id: 4,
name: user4,
userID: 83735,
items: [
{
id: 4,
name: item4,
tag: 4783
},
{
id: 4,
name: item4,
tag: 4783
}
],
tempitem: {
id: 4,
name: item4,
tag: 4783
},
}
];
Expected Results
results = [
{
id: 1,
name: user1,
userID: 38494,
items: [
{
id: 1,
name: item1,
tag: 1900
},
{
id: 5,
name: item5,
tag: 67894
},
{
id: 9,
name: item9,
tag: 94948
}
],
},
{
id: 2,
name: user2,
userID: 84844,
items: [
{
id: 2,
name: item2,
tag: 1876
},
{
id: 6,
name: item6,
tag: 66789
},
],
},
{
id: 3,
name: user3,
userID: 47483,
items: [
{
id: 3,
name: item3,
tag: 1575
},
{
id: 7,
name: item7,
tag: 67890
},
],
},
{
id: 4,
name: user4,
userID: 83735,
items: [
{
id: 4,
name: item4,
tag: 4783
},
{
id: 8,
name: item8,
tag: 87654
},
],
}
];
The difference is that, the actual results returns repeated items for each users and not all items were assigned to users, it also appends the tempitem property to each user which is not needed.
The expected results, assigns unique items to each users without repetition and also ensure that an item must be assigned to a user.
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function assign() {
const itemsLength = items.length;
for (var i=itemsLength+1, shuffledArray=[]; i--;) shuffledArray.push(i);
shuffledArray = shuffleArray(shuffledArray);
const eachUserItems = itemsLength/users.length;
Object.entries(users).map(([key, value]) => {
for(i = 0; i < Math.round(eachUserItems); i++) {
users[key]['items'] = users[key]['items'] || [];
var popNewIndex = shuffledArray.pop();
var objectToInsert = items.find((i) => i.id == popNewIndex + 1);
users[key]['items'].push(objectToInsert);
}
});
return users;
}
This would give you expected items always assigned randomly to users
ps.
Also simple idea here is that rather than randomising on every loop we would make a random list of values that we would pop one by one. We can use randomise value to fetch value out of list by id and assign it. As list is supposed to have unique values which are randomised it would make sure no value is assigned again to any other user.
You can do something like that (excuse me for possible mistakes, I didnt test it)
const users = [...]
const items = [...]
const results = []
const random_func = () => {
let available_users = [...users]
items.forEach(item => {
// if available users are zero, re-populate the array (it means that everyone has a item
if(available_users.length === 0) available_users = [...users]
//get a random user from available users array
const random_user_index = Math.floor(Math.random() * available_users.length)
const temp_user = available_users(random_user_index)
//remove user from available users
available_users.splice(i, 1);
//check if user already exists into results array
if(results.length > 0) {
objIndex = results.findIndex((obj => obj.id == temp_user.id));
if(objIndex === -1) results.push({...temp_user, items: [{...item}]}
else results[index].items.push(item)
}else {
results.push({...temp_user, items: [{...item}]}
}
})
}
Check the following code. It's tested and produces the expected result.
const minItems = Math.round(items.length / users.length);
const assignedItems = [];
const usersWithItems = users.map((user) => {
user.items = new Array(minItems).fill(0).map(() => {
let isNew = false;
let item;
do {
item = items[Math.floor(Math.random() * items.length)];
if(!assignedItems.includes(item.id))
isNew = true;
} while(isNew == false);
if(item){
assignedItems.push(item.id);
return item;
}
});
return user;
})
const balanceItems = items.filter(item => !assignedItems.includes(item.id));
balanceItems.forEach((item) => {
const randomUserNo = Math.floor(Math.random() * users.length);
usersWithItems[randomUserNo].items.push(item);
})
console.log(usersWithItems);
Lets's try to simplify your task, lets imagine you have unknown number of elements
const items = [1, 5, 6, 7, 3, 4, 2, 3, 2];
and unknown number of users
const users = [{ name: "Tom", items: []}, { name: "John", items: [] }, name: "Steve", items: [] }];
and you have to assign those items to user so your code would be
const items = [1, 5, 6, 7, 3, 4, 2, 3, 2];
const users = [{ name: "Tom", items: []}, { name: "John", items: [] }, { name: "Steve", items: [] }];
let currentUserIndex = 0;
while (items.length) {
const randomIndex = Math.floor(Math.random() * items.length);
users[currentUserIndex].items.push(items[randomIndex]);
items.splice(randomIndex, 1);
if (currentUserIndex === users.length - 1) {
currentUserIndex = 0;
} else {
currentUserIndex++;
}
}
I'm trying to strip the duplicate array values from my current array. And I'd like to store the fresh list (list without duplicates) into a new variable.
var names = ["Daniel","Lucas","Gwen","Henry","Jasper","Lucas","Daniel"];
const uniqueNames = [];
const namesArr = names.filter((val, id) => {
names.indexOf(val) == id; // this just returns true
});
How can I remove the duplicated names and place the non-duplicates into a new variable?
ie: uniqueNames would return...
["Daniel","Lucas","Gwen","Henry","Jasper"]
(I'm using react jsx) Thank you!
You can do it in a one-liner
const uniqueNames = Array.from(new Set(names));
// it will return a collection of unique items
Note that #Wild Widow pointed out one of your mistake - you did not use the return statement. (it sucks when we forget, but it happens!)
I will add to that that you code could be simplified and the callback could be more reusable if you take into account the third argument of the filter(a,b,c) function - where c is the array being traversed. With that said you could refactor your code as follow:
const uniqueNames = names.filter((val, id, array) => {
return array.indexOf(val) == id;
});
Also, you won't even need a return statement if you use es6
const uniqueNames = names.filter((val,id,array) => array.indexOf(val) == id);
If you want to remove duplicate values which contains same "id", You can use this.
const arr = [
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 2, name: "ra one" },
{ id: 3, name: "alex" },
{ id: 1, name: "devid" },
{ id: 7, name: "sam" },
];
function getUnique(arr, index) {
const unique = arr
.map(e => e[index])
// store the keys of the unique objects
.map((e, i, final) => final.indexOf(e) === i && i)
// eliminate the dead keys & store unique objects
.filter(e => arr[e]).map(e => arr[e]);
return unique;
}
console.log(getUnique(arr,'id'))
Result :
[
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 7, name: "sam" }
]
you forgot to use return statement in the filter call
const namesArr = duplicatesArray.filter(function(elem, pos) {
return duplicatesArray.indexOf(elem) == pos;
});
Since I found the code of #Infaz 's answer used somewhere and it confused me greatly, I thought I would share the refactored function.
function getUnique(array, key) {
if (typeof key !== 'function') {
const property = key;
key = function(item) { return item[property]; };
}
return Array.from(array.reduce(function(map, item) {
const k = key(item);
if (!map.has(k)) map.set(k, item);
return map;
}, new Map()).values());
}
// Example
const items = [
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 2, name: "ra one" },
{ id: 3, name: "alex" },
{ id: 1, name: "devid" },
{ id: 7, name: "sam" },
];
console.log(getUnique(items, 'id'));
/*Output:
[
{ id: 2, name: "sumit" },
{ id: 1, name: "amit" },
{ id: 3, name: "rahul" },
{ id: 4, name: "jay" },
{ id: 7, name: "sam" }
]
*/
Also you can do this
{Array.from(new Set(yourArray.map((j) => j.location))).map((location) => (
<option value={`${location}`}>{location}</option>
))}