I have an array with nested objects that looks like the one below.
What I'd like to do is loop through it calculate the sum of each item per date.
For example pc + screen = ?
I cannot seem to figure out how to do it properly. I have found this solution, it works great in console.log() but I cannot figure out how to output the result in a div. Should I use a map function ?
const amountPerDate = data.forEach(function (i) {
const sum = i.item.reduce(function (sum, elem) {
return sum + elem.price;
}, 0);
console.log("the total sum is " + sum);
});
The array:
The code you have posted doesn't seem quite right since forEach won't return anything, and the inner variable sum is not actually available for React to render since it is not in scope (in JavaScript, variables can not escape their containing function, which is function (i) { -- nothing outside of that function can see it).
You were roughly on the right tracks with needing map since that will return an array that represents an accumulation of the return values in the nested callback.
const amountsPerDate = data.map((i) => {
return i.item.reduce(function (sum, elem) {
return sum + elem.price;
}, 0);
});
amountsPerDate will now be an array of the sums. However, in this process, youve lost the info about which sum correlates to which date. So we need more. We can modify to return both the sum alongside the date (an array of objects, each with a sum and date inside).
const amountsPerDate = data.map((i) => {
return {
sum: i.item.reduce(function (sum, elem) {
return sum + elem.price;
}, 0),
date: i.date
});
Now, you should have something in amountsPerDate that looks like this:
[
{ date: '01/01/2022', sum: 200 },
{ date: '02/01/2022', sum: 30},
]
To display in your react component, it's just a case of rendering it, which will require you to map over this new data and return an element for each entry. You haven't posted your full component, but it will be something like this in your JSX:
<div>
{amountsPerDate.map(sum =>
<div>Date: {sum.date}. Total: {sum.sum}</div>
)}
</div>
Of course you can play with this and move it around as you see fit so it fits however you want it laid out.
It's really worth your time understanding map and the differences with foreach since it's so ubiquitous in functional programming. Foreach and map both loop over each item. But map allows you to return a value within the loop callback, and that value goes on to be part of a new array returned from map that represents that item. You can think of it as a transformation from one array to another -- both with the same length -- but with each item replaced with something of your choosing, calculated from each items original contents.
I'm attempting to add an object at a specific point in my 'data' array which is this components state. The following isn't working, the array simply gets emptied.
addNewBulletAfterActive = () => {
const array = this.state.data;
const newBulletPoint = {
id: this.state.data.length += 1,
title: 'Click to add'
};
const newData = array.splice(this.state.activeBulletPointId, 0, newBulletPoint);
this.setState({
data: newData
});
}
The idea is that if I have a list of 10 bullet points, the user can click on the 4th bullet point and press enter to add a new bullet point directly after. I've not had any issues adding items to the end of the array but it looks like .splice is causing issues.
I believe this should do what you're after.
function addAfter(array, index, newItem) {
return [
...array.slice(0, index),
newItem,
...array.slice(index)
];
}
This function returns a new array with a new item inserted in the middle. It doesn't mutate your original array and so will play nicely with component's state and Redux.
You can then assign the output from this function to your state.
splice returns spliced items (which is empty since you splice 0 items) and mutates original array.
const newData = array.slice(0); // copy
newData.splice(this.state.activeBulletPointId, 0, newBulletPoint);
this.setState({
data: newData
});
I think this could be an easier and faster method to do this
/*Just plain JS*/
function AddAfter(array, newObject){
array.unshift(newObject);
}
/*In react If updating state*/
var _prev = this.state.your_array; //getting the current value for the state object
var newInfo = {id: 1, more: 'This is a new object'};
_prev.unshift(newInfo);
How do i traverse the child book table when i pass the grandparent_id or parent_id to look up all the related books both in parent book and child book tables. I'm guessing it is more of a data structure problem. I'm using Activerecord to fetch the data from the database.
Tables structure
grand_parent_category
Id name
2 Math
parent_category
Id name
1 Algebra book
child_category
Id name parent_id. grandparent_id.
1. Calculus book 1 2
The normal way is to traverse the entire child books array and search the grandparent_id in the book column.
it would be the same if i choose parent_id
Example
#child_books = child_books.all()
Im passing the #child_books object to the frontend via Gon
Javascript
gon.child_books.forEach((book) => {
If (book.grandparent_id == chosen_grandparent_id) {
// do something
} else if book.parent_id == chosen_parent_id {
// do something
} else if book.parent_id == children_id
});
The result would be. If I choose grandparent_id book. Math
Grandparent = Math
Parent = Algebra, Calculus
Children = Additional Math, Discrete Math
Math
/
Algebra
/ \
Additional Math Discrete math
But this approach is really slow, if the dataset for child book category is huge let say 5000. Then in order to find the relationship I have to traverse one by one
Another approach I was thinking is to use hash
#child_books = child_books.all()
#child_books.index_by(&:id)
This will give this result
{
1: {id: 1, name: “additional mathematics”, parent_id: 1, grandparent_id: 2 }
2: {id: 2, name: “discrete mathematics”, parent_id: 1, grandparent_id: 2 }
}
But this approach can’t be done if I pass the grandparent_id to search for both parent and children books.
What approach should tackle this relationship problem.
TLDR; You problem is not likely searching through the data but in // do something part of your code.
Like #jad said it's very difficult to tell what you are trying to do. What got my attention was when you said if the dataset for child book category is huge let say 5000 5000 is tiny. It's barely even a spec in most cases.
What follows is likely not a direct solution to your problem.
// Let setup some test data, nothing too crazy
// 100K Child Books
// 10K Parent Books
// 100 Grand Parent Books
const getRandomInt = (max) => Math.floor(Math.random() * Math.floor(max))
let grand_parent_category = ''.padStart(100, ' ').split('').map((e, id) => ({id, name: `math:${id}`}))
let parent_category = ''.padStart(10000, ' ').split('').map((e, id) => ({id, name: `additional mathematics:${id}`, parent_id: getRandomInt(grand_parent_category.length)}))
let child_books = ''.padStart(100000, ' ').split('').map((e, id) => ({id, name: `child mathematics:${id}`, parent_id: getRandomInt(parent_category.length)}))
// Not counting the setup in the processing time but it's' near instant
let times = {}
// Map our data and create child buckets for easy lookup
times.startSetup = new Date()
const grandParentMap = new Map(grand_parent_category.map(ele => [ele.id, {...ele, children: []}]))
// While mapping parent add the parent to the grandParent for o(1) lookup
const parentMap = new Map(parent_category.map(ele => {
if (grandParentMap.has(ele.parent_id)) grandParentMap.get(ele.parent_id).children.push(ele.id)
return [ele.id, {...ele, children: []}]
}))
// While mapping child add the child to the parent for o(1) lookup
const childMap = new Map(child_books.map(ele => {
if (parentMap.has(ele.parent_id)) parentMap.get(ele.parent_id).children.push(ele.id)
return [ele.id, ele]
}))
// get a Grand Parent and all his children and grandchildren
const getGrandParent = (grandParentId) => {
if (!grandParentMap.has(grandParentId)) return
let grandParent = {...grandParentMap.get(grandParentId)} // shallow clone - safe based on the data given
grandParent.grandChildren = []
grandParent.children = grandParent.children.map(parentID => {
if (!parentMap.has(parentID)) return parentID
let parent = {...parentMap.get(parentID)} // shallow clone - safe based on the data given
// iterate over the children and map them into grandParent.grandChildren
parent.children.forEach(childID => grandParent.grandChildren.push(childMap.has(childID) ? {...childMap.get(childID)} : childID)) // shallow clone - safe based on the data given
return parent
})
return grandParent
}
times.finishSetup = new Date()
let grandParentId = 42
times.startSearch = new Date()
let results = getGrandParent(grandParentId)
times.finishSearch = new Date()
console.log(`grandParent ${grandParentId} has ${results.children.length} children and ${results.grandChildren.length} grandchildren!`)
console.log(`Setup took ${times.finishSetup - times.startSetup}ms`)
console.log(`Search took ${times.finishSearch - times.startSearch}ms`)
// Sample run
// grandParent 42 has 97 children and 991 grandchildren!
// Setup took 60ms
// Search took 1ms
If our data set gets bigger we may want to optimize the setup. I tried 10B children on my local system and it does take around 6 seconds. For sub 1M children this quick solution works well.
Given an example input:
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
]
Output all possible states of the input where currentBlack and currentWhite can have a value anywhere in the range from their initial value up to the maximum value.
Correct output for this example:
[
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":1,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":1,"max":1},
{"id":2,"currentBlack":1,"currentWhite":1,"max":1},
],
[
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":1,"currentWhite":1,"max":1},
]
]
The real input will have max anywhere between 1 and 8 and there will be far more objects within the input array. My attempt is below (heavily commented):
function allPossibleCounts(pieceCounts) {//pieceCounts is the input
var collection = []; //used to collect all possible values
recursiveCalls(pieceCounts); //runs recursive function
return collection; //returns result
function recursiveCalls(pieceCounts) {
//if pieceCounts is already in collection then return, not yet implemented so duplicates are currently possible
collection.push(pieceCounts);//inputs a potential value
console.log(JSON.stringify(pieceCounts));//this is successfully logs the correct values
console.log(JSON.stringify(collection));//collection isn't correct, all values at the top of the array are copies of each other
for (let n in pieceCounts) {//pieceCounts should be the same at the start of each loop within each scope, aka pieceCounts should be the same at the end of this loop as it is at the start
subBlackCall(pieceCounts);
function subBlackCall(pieceCounts) {
if (pieceCounts[n].currentBlack < pieceCounts[n].max) {
pieceCounts[n].currentBlack++;//increment
recursiveCalls(pieceCounts);
subBlackCall(pieceCounts);//essentially you're either adding +1 or +2 or +3 ect all the way up to max and calling recursiveCalls() off of each of those incremented values
pieceCounts[n].currentBlack--;//decrement to return pieceCounts to how it was at the start of this function
}
}
subWhiteCall(pieceCounts);
function subWhiteCall(pieceCounts) {
if (pieceCounts[n].currentWhite < pieceCounts[n].max) {
pieceCounts[n].currentWhite++;
recursiveCalls(pieceCounts);
subWhiteCall(pieceCounts);
pieceCounts[n].currentWhite--;
}
}
}
}
}
But currently my attempt outputs as this ungodly mess of copied arrays
[[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}],[{"id":1,"currentBlack":1,"currentWhite":1,"max":1},{"id":2,"currentBlack":1,"currentWhite":1,"max":1}]]
Edit: working code: https://pastebin.com/qqFTppsY
The pieceCounts[n] always reference to the one object. You should recreate the pieceCount for saving in to the collection as different object. For example, you can add
pieceCounts = JSON.parse(JSON.stringify(pieceCounts)); // just clone
at the start of recursiveCalls function.
To avoid conversion to JSON and back, I would suggest using Object.assign to perform a deeper copy in combination with map on the array:
function allPossibleCounts(pieceCounts) {
var result = [],
current = deeperCopy(pieceCounts);
function deeperCopy(arr) {
return arr.map( row => Object.assign({}, row) );
}
function recurse(depth) {
// depth: indication of which value will be incremented. Each "row" has
// 2 items (black/white), so when depth is even, it refers to black, when
// odd to white. Divide by two for getting the "row" in which the increment
// should happen.
var idx = depth >> 1, // divide by 2 for getting row index
prop = depth % 2 ? 'currentWhite' : 'currentBlack', // odd/even
row = pieceCounts[idx];
if (!row) { // at the end of the array
// Take a copy of this variation and add it to the results
result.push(deeperCopy(current));
return; // backtrack for other variations
}
for (var value = row[prop]; value <= row.max; value++) {
// Set the value of this property
current[idx][prop] = value;
// Collect all variations that can be made by varying any of
// the property values that follow after this one
recurse(depth+1);
// Repeat for all higher values this property can get.
}
}
recurse(0); // Start the process
return result;
}
// Sample input
var pieceCounts = [
{"id":1,"currentBlack":1,"currentWhite":0,"max":1},
{"id":2,"currentBlack":0,"currentWhite":1,"max":1},
];
// Get results
var result = allPossibleCounts(pieceCounts);
// Output
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
The idea is to use recursion: imagine the problem can be solved for all variations that can be made for all properties, except the first one. Produce those, and then change the first property value to the next possible value. Repeat again the production of all variations, etc. The combination of all those results together will be the solution for when the first property value should also be varied.
This is an ideal situation for recursion. The recursion stops when there are no more property values remaining: in that case there is only one solution; the one with all the values set as they are. It can be added to the list of results.
The properties can be enumerated like this:
row currentBlack currentWhite
---------------------------------
0 0 1
1 2 3
2 4 5
3 6 7
...
n 2n-2 2n-1
We could call that number depth, and increase it at every step of deeper recursion. Given a depth, the property to vary is defined by:
depth is even => currentBlack
depth is odd => currentWhite
row number = depth / 2 (ignoring the remainder)
I have react component with a array of a shoppinglist in state.
The shoppinglist have multiple arrays and object to reflect which ingredients matches each day and meal.
This functions loops through each ingredient push it to an empty array which I later maps over and returns the value. When I find an ingredient which already exists in the ingredient-array, then it modifies the value instead of pushing a new similar ingredient. (the reason for this is that a breakfast on monday and tuesday can both have the ingredient "apple", and then I just want to show "apple" in the list once, but have the amount of values to reflect both breakfasts.
The problem:
The value of the ingredient I modify is changed on state-level. Which means when I run the function again (I do because the user can filter days) the value increases.
Scenario
Breakfast in both monday and tuesday has the ingredient Apple. Apple has a value of 2, since the recipe needs two apple. This means my loop should return 4 apples. And it does the first time. But when I change som filters so the loops run again to reflect the new filter, it now shows 6 apples.
Can someone tell me how this is possible? How can this code modify my global state?
loopIngredients (items) {
const ingredients = []
const newArr = items.slice()
newArr.map((data, key) => {
data.map((data, index) => {
const valid = ingredients.find((item) => item.ingredient.id === data.ingredient.id)
if (valid) {
const parentValue = parseInt(ingredients[index].value)
const localValue = parseInt(data.value)
ingredients[index].value = (parentValue + localValue).toString()
} else {
ingredients.push(data)
}
})
})
} // END loopIngredients
Found the solution:
The ingredients.push(data) pushed the reference from the state.
I thought I removed the reference by doing .slice(), but apparently not.
By doing ingredients.push(Object.create(data)) it worked as intended.