Dealing with looping through nested arrays - javascript

I have data array, which has nested arrays inside (level1arr, leve21arr ...)
const data = [
{
level1arr: [
{
level2arr: [{ id: 1, isValid: true }, { id: 2, isValid: true }, { id: 3, isValid: true }],
},
{
level2arr: [{ id: 4, isValid: true }, { id: 5, isValid: true }, { id: 6, isValid: true }],
},
],
},
{
level1arr: [
{
level2arr: [{ id: 7, isValid: true }, { id: 8, isValid: true }, { id: 9, isValid: true }],
},
{
level2arr: [{ id: 10, isValid: true }, { id: 11, isValid: true }, { id: 12, isValid: true }],
},
],
},
];
I also have another array:
const invalidIds = [2,5]
I want to find elements with apecyfic id and change isValid property to false.
Is it better way than iteratinf over multiple nested arrays, like that:
data.forEach(lvl1 => {
lvl1.level1arr.forEach(lvl2 => {
lvl2.level2arr.forEach(element => {
// further nesting
});
});
})
Such iterating over multiple arrays is not good for performance. What is the best way to handle such case with nested arrays?

If it were nested arrays, you could use Array.prototype.flat(). However, you have a mix of nested objects and arrays. You will have to write a custom "flattener" for this data structure. Check this answer for details: how to convert this nested object into a flat object?

You can use recursion until you reach the level you need. Here's one way to do it.
const data = [{
level1arr: [{
level2arr: [{
id: 1,
isValid: true
}, {
id: 2,
isValid: true
}, {
id: 3,
isValid: true
}],
},
{
level2arr: [{
id: 4,
isValid: true
}, {
id: 5,
isValid: true
}, {
id: 6,
isValid: true
}],
},
],
},
{
level1arr: [{
level2arr: [{
id: 7,
isValid: true
}, {
id: 8,
isValid: true
}, {
id: 9,
isValid: true
}],
},
{
level2arr: [{
id: 10,
isValid: true
}, {
id: 11,
isValid: true
}, {
id: 12,
isValid: true
}],
},
],
},
];
const invalidIds =[2,5]
const findId = (object, key, value) => {
if (Array.isArray(object)) {
for (const obj of object) {
findId(obj, key, value);
}
} else {
if (object.hasOwnProperty(key) && object[key] === value) {
object.isValid = false;
return object
}
for (const k of Object.keys(object)) {
if (typeof object[k] === "object") {
findId(object[k], key, value);
}
}
}
}
invalidIds.forEach(id => findId(data, "id", id))
console.log(data)

Related

filter through multiple arrays and add objects from them to a single array

Example of an object in the accounts array:
const accounts = [
{
id: "5f446f2ecfaf0310387c9603",
picture: "https://api.adorable.io/avatars/75/esther.tucker#zillacon.me",
age: 25,
name: {
first: "Esther",
last: "Tucker",
},
company: "ZILLACON",
email: "esther.tucker#zillacon.me",
registered: "Thursday, May 28, 2015 2:51 PM",
},
Example of an object in the books array:
const books = [
{
id: "5f447132d487bd81da01e25e",
title: "sit eiusmod occaecat eu magna",
genre: "Science",
authorId: 8,
borrows: [
{
id: "5f446f2e2cfa3e1d234679b9",
returned: false,
},
{
id: "5f446f2ed3609b719568a415",
returned: true,
},
{
id: "5f446f2e1c71888e2233621e",
returned: true,
},
{
id: "5f446f2e6059326d9feb9a68",
returned: true,
},
{
id: "5f446f2ede05a0b1e3394d8b",
returned: true,
},
{
id: "5f446f2e4081699cdc6a2735",
returned: true,
},
{
id: "5f446f2e3900dfec59489477",
returned: true,
},
{
id: "5f446f2e6059326d9feb9a68",
returned: true,
},
{
id: "5f446f2e409f8883af2955dd",
returned: true,
},
{
id: "5f446f2e3900dfec59489477",
returned: true,
},
{
id: "5f446f2eae901a82e0259947",
returned: true,
},
{
id: "5f446f2ef2ab5f5a9f60c4f2",
returned: true,
},
{
id: "5f446f2ea6b68cf6f85f6e28",
returned: true,
},
{
id: "5f446f2eed18105706d6ca19",
returned: true,
},
{
id: "5f446f2eae901a82e0259947",
returned: true,
},
{
id: "5f446f2e91c2af00cb74e82b",
returned: true,
},
{
id: "5f446f2e5aa2bb5545a0f8a6",
returned: true,
},
{
id: "5f446f2ea508b6a99c3e42c6",
returned: true,
},
{
id: "5f446f2e50cc2da9cd80efdb",
returned: true,
},
{
id: "5f446f2e0b3e2ff72fc503e7",
returned: true,
},
{
id: "5f446f2e91c2af00cb74e82b",
returned: true,
},
{
id: "5f446f2ef795e593cd3cd19d",
returned: true,
},
{
id: "5f446f2e2f35653fa80bf490",
returned: true,
},
{
id: "5f446f2e7b9cd304fed3a8bc",
returned: true,
},
{
id: "5f446f2ed9aac23c0340aab2",
returned: true,
},
],
},
Example of objects in the authors array:
const authors = [
{
id: 0,
name: {
first: "Lucia",
last: "Moreno",
},
},
{
id: 1,
name: {
first: "Trisha",
last: "Mathis",
},
},
{
id: 2,
name: {
first: "Arnold",
last: "Marks",
},
},
I need to write the function function getBooksPossessedByAccount(account, books, authors) {} that does the following: It returns an array of books and authors that represents all books currently checked out by the given account. Look carefully at the object below, as it's not just the book object; the author object is embedded inside of it.
Output example:
getBooksPossessedByAccount(account, books, authors);
[
{
id: "5f447132320b4bc16f950076",
title: "est voluptate nisi",
genre: "Classics",
authorId: 12,
author: {
id: 12,
name: {
first: "Chrystal",
last: "Lester",
},
},
borrows: [
{
id: "5f446f2e6059326d9feb9a68",
returned: false,
},
...
],
},
]
Here's what I have so far:
function getBooksPossessedByAccount(account, books, authors) {
const accId = account.id;
const result = [];
for (let idxBks = 0; idxBks < books.length; idxBks++) {
if (
books[idxBks].borrows.id === accId &&
books[idxBks].borrows.returned === false
) {
result.push(books[idxBks]);
}
for (let idxAuth = 0; idxAuth < authors.length; idxAuth++) {
let authorIdx = authors[idxAuth];
if (authorIdx.id === result.authorId) {
return [result, { author: authorIdx }];
}
}
}
return result;
}
You need to search all the borrows, not just borrows[0]. You can use the some() method to check all of them.
Since the author information needs to be added as a property to the book object, you shouldn't be pushing it onto the booksOut array.
function getBooksPossessedByAccount(account, books, authors) {
const accId = account.id;
const booksOut = books.filter(
(book) => book.borrows.some(borrow => !borrow.returned && borrow.id === accId)
);
booksOut.forEach(book => book.author = authors.find(author => book.authorID == author.id))
return booksOut;
}
Using some should do the trick..
function getBooksPossessedByAccount(account, books, authors) {
let borrowedBooks=books.filter(book=>
book.some(borrow=>borrow.id===account.id)
)
return borrowedBooks //array of book objects
//return borrowedBooks.map(book=>book.id) //to show array of book ids
}

Update nested object within Redux store

I am trying to toggle a particular element in an array that's in an array that's in an array in redux state, and then have it reflect the change in the component that is using that state.
I am trying to change the complete state in set to true but nothing happens with the way my code is written right now.
export const initialState: WorkoutList = {
workouts: [
{
id: "newId",
name: "Leg Day",
date: new Date(),
duration: 60,
started: false,
completed: false,
exercises: [
{
id: "e1",
name: "Squats",
completed: false,
sets: [
{
id: "e1s1",
reps: 12,
weight: 60,
completed: false,
},
{
id: "e1s2",
reps: 8,
weight: 60,
completed: false,
},
{
id: "e1s3",
reps: 10,
weight: 60,
completed: false,
},
],
},
{
id: "e2",
name: "Leg Press",
completed: false,
sets: [
{
id: "e2s1",
reps: 12,
weight: 60,
completed: false,
},
{
id: "e2s2",
reps: 8,
weight: 60,
completed: false,
},
{
id: "e2s3",
reps: 10,
weight: 60,
completed: false,
},
],
},
],
},
],
selectedWorkout: {
id: "newId",
name: "Leg Day",
date: new Date(),
duration: 60,
started: false,
completed: false,
exercises: [
{
id: "e1",
name: "Squats",
completed: false,
sets: [
{
id: "e1s1",
reps: 12,
weight: 60,
completed: false,
},
{
id: "e1s2",
reps: 8,
weight: 60,
completed: false,
},
{
id: "e1s3",
reps: 10,
weight: 60,
completed: false,
},
],
},
{
id: "e2",
name: "Leg Press",
completed: false,
sets: [
{
id: "e2s1",
reps: 12,
weight: 60,
completed: false,
},
{
id: "e2s2",
reps: 8,
weight: 60,
completed: false,
},
{
id: "e2s3",
reps: 10,
weight: 60,
completed: false,
},
],
},
],
},
selectedExercise: {
id: "e1",
name: "Squats",
completed: false,
sets: [
{
id: "e1s1",
reps: 12,
weight: 60,
completed: false,
},
{
id: "e1s2",
reps: 8,
weight: 60,
completed: false,
},
{
id: "e1s3",
reps: 10,
weight: 60,
completed: false,
},
],
},
};
const workoutListReducer = (
state = initialState,
action: WorkoutActionTypes
): WorkoutList => {
switch (action.type) {
case TOGGLE_SET_COMPLETE:
const updatedWorkoutState = state.workouts.map((workout) => {
if (workout.id === state.selectedWorkout?.id) {
workout.exercises?.map((exercise) => {
if (exercise.id === state.selectedExercise?.id) {
exercise.sets?.map((set) => {
if (set.id === action.payload.id) {
// console.log(set, !set.completed);
return { ...set, completed: true };
}
return set;
});
}
return exercise;
});
}
return workout;
});
const updatedSelectedWorkoutState = updatedWorkoutState.find(
(workout) => workout.id === state.selectedWorkout?.id
);
const updatedSelectedExerciseState = updatedSelectedWorkoutState?.exercises?.find(
(workout) => workout.id === state.selectedExercise?.id
);
console.log(updatedSelectedExerciseState);
return {
...state,
workouts: updatedWorkoutState,
selectedWorkout: updatedSelectedWorkoutState,
selectedExercise: updatedSelectedExerciseState,
};
default:
return state;
}
};
export default workoutListReducer;
I believe the issue is that you're returning the same instances of workout objects. You should be cloning the "selected workout" object within the workouts so react can detect that something has been changed.
There are 2 usages of the map function that are not being properly applied. The map should be used to generate a new array that is returned by the map function. But in 2 cases you use the map ignoring its result.
Your code should be doing something like this (the condition has been inverted to make the problematic code clearer):
const updatedWorkoutState = state.workouts.map((workout) => {
// Happy path, returns same instance
if (workout.id !== state.selectedWorkout?.id) {
return workout;
}
// Obtain the updated exercises for the selected workout
const updatedExercises = workout.exercises?.map((exercise) => {
// just return the exercise we don't care about as-is:
if (exercise.id !== state.selectedExercise?.id) {
return exercise;
}
// get the updated array of sets
const updatedExerciseSets = exercise.sets?.map((set) => {
if (set.id === action.payload.id) {
// console.log(set, !set.completed);
return { ...set, completed: true };
}
return set;
});
// Clone the selected exercise setting the updated array of sets:
return {
...exercise,
sets: updatedExerciseSets
};
});
// Now the selected workout should be cloned and its exercises array replaced
// with the updated one:
return {
...workout,
exercises: updatedExercises
};
});
This makes things a bit more verbose, but there are a few libraries that you could use to help you with the immutability like Immer
Your code used the map function which returns a value, however, your code has no provision for this. Also, removed all the question marks(?). With just a little tweak to your code see a working option below
...
// The initialState part of your code goes here
...
const workoutListReducer = (
state = initialState,
action: WorkoutActionTypes
): WorkoutList => {
switch (action.type) {
case TOGGLE_SET_COMPLETE:
// create a copy of workout state
const tempWorkoutState = [...state.workouts];
const updatedWorkoutState = tempWorkoutState.map((workout) => {
if (workout.id === state.selectedWorkout?.id) {
// Update workout.exercises with the result of the map function
workout.exercises = workout.exercises.map((exercise) => {
if (exercise.id === state.selectedExercise.id) {
// Update exercise.sets with the result of the map function
exercise.sets = exercise.sets.map((set) => {
if (set.id === action.payload.id) {
// console.log(set, !set.completed);
return { ...set, completed: true };
}
return set;
});
}
return exercise;
});
}
return workout;
});
const updatedSelectedWorkoutState = updatedWorkoutState.find(
(workout) => workout.id === state.selectedWorkout.id
);
const updatedSelectedExerciseState = updatedSelectedWorkoutState.exercises.find(
(workout) => workout.id === state.selectedExercise.id
);
console.log(updatedSelectedExerciseState);
return {
...state,
workouts: updatedWorkoutState,
selectedWorkout: updatedSelectedWorkoutState,
selectedExercise: updatedSelectedExerciseState,
};
default:
return state;
}
};
export default workoutListReducer;

Push objects to new array with checked: true, if in mapped array

Can I please get some help with this scenario?
An array of strings
A function that maps the array of strings and applies to each one of them a name, key and adds one more object "checked: false".
A function that takes the mapped array and transforms it according to the argument passed, storing the value in to another array and changing the "checked" to value "true"
Ex:
const defaultProducts = [
"Laptop",
"Tablet",
"Phone",
"Ram",
"SSD",
"RasberyPi",
"Desktop",
"TV",
"Monitor"
];
const getDefaultProducts = () => {
return defaultProducts.map(products => {
return {
name: products,
checked: false
};
});
};
console.log(getDefaultProducts())
let forSale = []
function useProduct(product){
if(product in getDefaultProducts()) {
return{
product: forSale.push(product),
checked: true
};
};
return {product};
}
console.log(useProduct("Laptop"))
console.log(forSale)
returns
[ { name: 'Laptop', checked: false },
{ name: 'Tablet', checked: false },
{ name: 'Phone', checked: false },
{ name: 'Ram', checked: false },
{ name: 'SSD', checked: false },
{ name: 'RasberyPi', checked: false },
{ name: 'Desktop', checked: false },
{ name: 'TV', checked: false },
{ name: 'Monitor', checked: false } ]
{ product: 'Laptop' }
[]
Should return:
[ { name: 'Laptop', checked: false },
{ name: 'Tablet', checked: false },
{ name: 'Phone', checked: false },
{ name: 'Ram', checked: false },
{ name: 'SSD', checked: false },
{ name: 'RasberyPi', checked: false },
{ name: 'Desktop', checked: false },
{ name: 'TV', checked: false },
{ name: 'Monitor', checked: false } ]
{ product: 'Laptop' }
[{name:"Laptop", checked: true}]
In the part where you checked the condition as if product in getDefaultProducts () will not work since getDefaultProducts is an array of objects. You are comparing strings with each object such as:
"Laptop" === { name: "Laptop", checked: false }
which will return false always. Instead you can use find function:
function useProduct(product){
getDefaultProducts().find(el => {
if (el.name === product) {
el.checked = true
forSale.push(el)
}
});
return product;
}
Try with this:
function useProduct(product){
const found = getDefaultProducts().find(p => p.name === product)
if (found) {
found.checked = true
forSale.push(found)
}
return {product}
}

Is there a way to "merge" two object that are inside an array?

I have two arrays that i want two merge, something like this:
const arr1 = [{
id: 1,
isAvailable: true
},
{
id: 2,
isAvailable: true
},
{
id: 4,
isAvailable: true
},
{
id: 6,
isAvailable: false
}
]
const arr2 = [{
id: 1,
isAvailable: false
},
{
id: 2,
isAvailable: false
},
{
id: 6,
isAvailable: false
}
]
The outcome that I'm looking for is somethig like:
const arr3 = [{
id: 1,
isAvailable: false
},
{
id: 2,
isAvailable: false
},
{
id: 4,
isAvailable: true
},
{
id: 6,
isAvailable: false
}
]
I need to update the values of the first array with the values of the second array so I can have a new array with what's truly available and what's not.
You could first transform arr2 into a map object that maps the IDs to their availability, something like { "1": true, "4": false, ... }. And then map arr1 objects into a new array of objects while updating the isAvailable property using the map object:
let map = arr2.reduce((acc, o) => (acc[o.id] = o.isAvailable, acc), {});
let arr3 = arr1.map(o => ({
id: o.id,
isAvailable: map.hasOwnProperty(o.id) ? map[o.id] : o.isAvailable
}));
Each new object will have the same id as the original. Its isAvailable property will either be the value from the map object or its original value depending on whether or not it has an entry in the map object (whether or not it has an object in arr2)
Here is my sample program according to your example. Hope it solves your problem
const arr1 = [{
id: 1,
isAvailable: true
},
{
id: 2,
isAvailable: true
},
{
id: 4,
isAvailable: true
},
{
id: 6,
isAvailable: true
}
]
const arr2 = [{
id: 1,
isAvailable: false
},
{
id: 2,
isAvailable: false
},
{
id: 6,
isAvailable: false
}
]
const arr3 = []
for (let i = 0; i < arr1.length; i++) {
let found = arr2.find(item => item.id === arr1[i].id)
if (found)
arr3.push(found)
else
arr3.push(arr1[i])
}
console.log(arr3)
Output is
[ { id: 1, isAvailable: false },
{ id: 2, isAvailable: false },
{ id: 4, isAvailable: true },
{ id: 6, isAvailable: false } ]
You can filter arr1 based on arr2 and then just merge filtered arr2 with arr1
const arr1 = [{
id: 1, isAvailable: true
},
{
id: 2, isAvailable: true
},
{
id: 4, isAvailable: true
},
{
id: 6, isAvailable: false
}
];
const arr2 = [{
id: 1, isAvailable: false
},
{
id: 2, isAvailable: false
},
{
id: 6, isAvailable: false
}
];
const result = arr1.filter(a => !arr2.some(s => s.id == a.id)).concat(arr2);
console.log(result);
Or using map, some and find methods:
const arr1 = [{
id: 1, isAvailable: true
},
{
id: 2, isAvailable: true
},
{
id: 4, isAvailable: true
},
{
id: 6,
isAvailable: false
}
];
const arr2 = [{
id: 1, isAvailable: false
},
{
id: 2, isAvailable: false
},
{
id: 6, isAvailable: false
}
];
const result = arr1.map(a=> arr2.some(f=> f.id == a.id) ?
arr2.find(f=> f.id == a.id) : a
);
console.log(result);
You can do this with Array.map() by overwriting the value if it exists in the second array.
const newArray = arr1.map(({ id, isAvailable}) => {
const matchingObjects = arr2.filter(({ id: id2 }) => id === id2);
return matchingObjects.length > 0
? { id, isAvailable: matchingObjects[0].isAvailable } // replace the original value
: { id, isAvailable } // do not overwrite this value
})

Chai-related error message: "AssertionError: expected undefined to deeply equal"

I wrote a function that when given a list of objects and an
id, returns the same list, but with the corresponding object marked active
(all other objects should not be active).
const list = [
{ id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: true },
{ id: 4, active: false }
];
function markActive(list, value) {
list.forEach((id) => {
if (id.active = (id.id === value)) {
return true;
} else {
return false;
}
});
}
markActive(list, 2);
console.log(list)
Returns:
[ { id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: false },
{ id: 4, active: true } ]
It's working like a charm, except when I run "npm run [filename]" I get an error message:
Running Tests for [filename].
------------
[ { id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: false },
{ id: 4, active: true } ]
markActive
1) Case 1 (Given Sample)
2) Case 2 (String IDs)
0 passing (16ms)
2 failing
1) markActive Case 1 (Given Sample):
AssertionError: expected undefined to deeply equal [ { id: 1,
active: false },
{ id: 2, active: true },
{ id: 3, active: false },
{ id: 4, active: false } ]
at Function.assert.deepEqual
(node_modules/chai/lib/chai/interface/assert.js:216:32)
at Context.it (tests/test_02.js:23:12)
2) markActive Case 2 (String IDs):
AssertionError: expected undefined to deeply equal [ { id: '1',
active: false },
{ id: '2', active: true },
{ id: '3', active: false },
{ id: '4', active: false } ]
at Function.assert.deepEqual
(node_modules/chai/lib/chai/interface/assert.js:216:32)
at Context.it (tests/test_02.js:40:12)
Any idea what I'm doing wrong? Here's the code that sets up the tests:
const chai = require("chai");
const sinon = require("sinon");
const assert = chai.assert;
const markActive = require("../answers/02.js");
describe("markActive", () => {
it("Case 1 (Given Sample)", () => {
var list = [
{ id: 1, active: false },
{ id: 2, active: false },
{ id: 3, active: true },
{ id: 4, active: false }
];
var newList = markActive(list, 2);
var targetList = [
{ id: 1, active: false },
{ id: 2, active: true },
{ id: 3, active: false },
{ id: 4, active: false }
];
assert.deepEqual(newList, targetList);
});
it("Case 2 (String IDs)", () => {
var list = [
{ id: "1", active: false },
{ id: "2", active: false },
{ id: "3", active: true },
{ id: "4", active: false }
];
var newList = markActive(list, "2");
var targetList = [
{ id: "1", active: false },
{ id: "2", active: true },
{ id: "3", active: false },
{ id: "4", active: false }
];
assert.deepEqual(newList, targetList);
});
});
Your function isn't returning anything, so any variables you try to set to the result will be set as undefined.
To fix this, simply add a return statement to the end of your function.
function markActive(list, value) {
list.forEach((id) => {
if (id.active = (id.id === value)) {
return true;
} else {
return false;
}
});
return list; // return the updated list
}
NOTE: It's worth mentioning that because the array is referenced, you're modifying the values in-place. This is why the array you defined outside the function still had updated results even though you weren't logging the returned value. This can have unintended side effects if you were to run the markActive() function several times on the same list. If you want a new list to be returned, look into ways of copying and deep copying arrays in Javascript.

Categories

Resources