I am trying to write a code with JS and React.
In the App.js file i have set the state for some components, like this:
class App extends Component {
state = {
leafs: [
{
id: "1",
percent: "",
win: false,
}
],
result: [
{
winProbability: "0",
}
]
};
It is possible to add many "leafs" and add a different "percent" in each of them. When a button is clicked, I want to multiply the percent (if it is true) with the percent in each "leaf". So if the percent is 3 in one leaf, 4 in another, and 5 in the last, I want the answer to be 60 (3*4*5).
Here is the function:
calculate = () => {
leafs: this.state.leafs.map(leaf => {
if (leaf.win === true) {
var sum = 1;
for (var i = 0; i < leaf.percent.length; i++) {
sum = sum * +leaf.percent[i];
}
console.log(sum);
this.setState({
result: this.state.result.map(result => {
result.winProbability = sum;
return result;
})
});
}
});
};
Whit the numbers as described above, the console shows 3 in one line, then 4, and then 5. So what am I doing wrong? I wold really appreciate som help!
This worked, but there is probably a better way:
calculate = () => {
var sum = [];
leafs: this.state.leafs.map(leaf => {
if (leaf.win === true) {
sum.push(leaf.percent);
}
});
var sumPrint = 1;
for (var i = 0; i < sum.length; i++) {
sumPrint = sumPrint * +sum[i];
}
this.setState({
result: this.state.result.map(result => {
result.winProbability = sumPrint;
console.log(sumPrint);
return result;
})
});
};
What you are looking for is a reduce function, instead of mapping. You are iterating over the array with map there and resetting the sum to 1 at the beginning of every iteration. I assume you want to achieve something like this:
calculate = () => {
this.setState({
result: Object.assign({}, this.state.result, {
winProbability: this.state.leafs.reduce(
(winProb, leaf) => (leaf.win ? winProb * leaf.percent : winProb),
1
)
})
});
};
Related
I have below code to extract the ID and username of a unique list of authors.
let authorsList = await Poem.find({ 'author.id': {$nin: community.busAdmins}}).where('communities').equals(community._id).populate('authors').sort({"author.username": 1});
let uniqueAuthorsList = [];
authorsList.forEach(details => {
if(!uniqueAuthorsList.some(code => code.username == details.author.username)){
uniqueAuthorsList.push({username: details.author.username, id: details.author.id});
}
});
For each of those authors, I want to count how many blogs they have written. So far I have this code:
const counts = {};
uniqueAuthorsList.forEach((el) => {
counts[el] = counts[el] ? (counts[el] += 1) : 1;
});
console.log(counts);
But this only returns:
{ '[object Object]': 7 }
How can I count the records using only the first element of the array (username), so I can return a list like this?
Dave: 4
Emily: 7
Mark: 2
Put the counts in uniqueAuthorsList when you're creating it.
let uniqueAuthorsList = [];
authorsList.forEach(details => {
let author = uniqueAuthorsList.find(code => code.username == details.author.username);
if (author) {
author.count++;
} else {
uniqueAuthorsList.push({
username: details.author.username,
id: details.author.id,
count: 1
});
}
});
You might want to just make uniqueAuthors an object rather than an array.
let uniqueAuthors = {};
authorsList.forEach(details => {
if (uniqueAuthors[details.author.username]) {
uniqueAuthors[details.author.username].count++;
} else {
uniqueAuthors[details.author.username] = {
username: details.author.username,
id: details.author.id,
count: 1
};
}
});
I was working on a chart where i need my data to be sliced into small object based for better visibility. My array that i have is
{
"size":[
{
"timestamp":"1641329889000",
"size":12345,
"fees":123456,
"cost":168
},
{
"timestamp":"1641387032",
"size":456789,
"fees":4567891,
"cost":249
},
{
"timestamp":"1641435786",
"size":98765,
"fees":987654,
"cost":987
},
{
"timestamp":"1641435786",
"size":98765,
"fees":987654,
"cost":987
},
{
"timestamp":"1641435786",
"size":98765,
"fees":987654,
"cost":987
}
]
}
in which i want the array to be in this form
{
"size":{
"timestamp": ["1641329889000","1641387032","1641435786"],
"size": [12345,456789,98765],
"fees": [123456,4567891,987654],
"cost": [168,249,987]
}
}
i can achieve this using foreach and push like this
result.forEach(element => {
this.state.timestamp.push(element.timestamp);
this.state.size.push(element.size);
});
But i want this array to have the items only from the 10,20,30,40th index alone
I want not all the value. the values should be chosen only in the basis of x+10
Could anyone help me on this
Instead of forEach why not just use a for loop, and on the condition use the modulus % operator with 10? Like if (i % 10 == 0) inside of the for loop, or just increment i by 10 like i+=10.
You could take a for loop and a step for incrementing the index.
const
step = 10,
keys = ["timestamp", "size", "fees", "cost"],
result = Object.fromEntries(keys.map(k => [k, []]));
for (let i = 0; i < size.lenght; i += step) {
keys.forEach(key => result[key].push(size[i][key]));
}
Using forEach is a waste of resources.
You can use for instead:
for(let i=0;i<result.length;i+10){
this.state.timestamp.push(result[i].timestamp);
this.state.size.push(result[i].size);
}
For setting the state you should use setState not just push to it.
let tmp = {
...this.state
}
for(let i=0;i<result.length;i+10){
tmp.size.timestamp.push(result[i].timestamp);
tmp.size.push(result[i].size);
}
this.setState(tmp)
As mentioned by #cybercoder, you probably don't want to change the state variable within the forEach, as that will cause render to be called excessively.
You could simply use a counter and only push elements when the index is divisible by 10:
let i = 0;
let {timestamp, size} = this.state;
result.forEach(element => {
if (i % 10 === 0) {
timestamp.push(element.timestamp);
size.push(element.size);
}
i++;
});
this.setState({
...this.state,
timestamp,
size
});
If you do not want to include the very first (index 0) element:
let i = 0;
let {timestamp, size} = this.state;
result.forEach(element => {
// Exclude very first element
if (i % 10 === 0 && i !== 0) {
timestamp.push(element.timestamp);
size.push(element.size);
}
i++;
});
this.setState({
...this.state,
timestamp,
size
});
I have a table. When I click on an element I return its index. How can I (starting from this element) display all elements with index + 9. i.e. if the selected element has index 0, then I also need to display elements with indexes 9, 18, 27, etc.
let newCell = [td#2, td#0, td#2, td#0, td#2, ..... td#1, td#0]
newCell.forEach((item, i) => {
item.addEventListener('click', () => {
console.log(i)
})
})
I have not included the code to "display" those elements that will come in the series, but below is the code that logs them out. You can update it to display them.
const DIFF = 9;
newCell.forEach((item, i) => {
item.addEventListener('click', ()=>logAllItemsInSeries(i));
}
const logAllItemsInSeries = (i) => {
console.log(i);
const series = [];
while (i < newCell.length) {
series.push(i);
i += DIFF;
}
series.forEach((index) => {
console.log(index);
console.log(newCell[index]);
});
};
What you need to do in this case is keep track of a count of what elements should be filtered out and then update that count as you filter through it. Unfortunately your cell data is unclear to me so I went ahead and made my own to explain.
let data = [];
for (let i = 0; i < 100; i++) {
data.push({ id: i });
}
function getByNine(id) {
let count = id + 9
return data.filter(i => {
if (i.id === count) {
count = count + 9
return i
} else {
return
}
})
}
console.log(getByNine(3))
Inside your event listener you would put a reference to the getByNine(i) function (or whatever you want to call it) and pass its index.
Inside this function you set a default count of 9 + whatever the id of the element clicked
Run a filter through your array of objects, when the count is correct return that item and then increment the count by 9 for the next correct element
function getByNine(i) {
// code here
}
newCell.forEach((item, i) => {
item.addEventListener('click', () => {
getByNine(i)
})
})
I have the following forEach statement:
reviews.forEach(review => {
if (review.timestamp >= beforeThreeMonthsDate) {
lastThreeMonths.push(review);
}
if (review.timestamp >= beforeSixMonthsDate) {
lastSixMonths.push(review);
}
if (review.timestamp >= beforeOneYearDate) {
lastYear.push(review);
}
if (review.timestamp >= beforeTwoYearsDate) {
lastTwoYear.push(review);
}
});
Is there any way to get the same result with looping just one time through the reviews list using filter or any new ES6 functionality
It changes the output a little but, you could reduce to an object. instead of 4 separate variables.
const {
lastThreeMonths,
lastSixMonths,
lastYear,
lastTwoYear
} = reviews.reduce(
(groups, review) => {
if (review.timestamp >= beforeThreeMonthsDate) {
groups.lastThreeMonths.push(review);
}
if (review.timestamp >= beforeSixMonthsDate) {
groups.lastSixMonths.push(review);
}
if (review.timestamp >= beforeOneYearDate) {
groups.lastYear.push(review);
}
if (review.timestamp >= beforeTwoYearsDate) {
groups.lastTwoYear.push(review);
}
},
{ lastThreeMonths: [], lastSixMonths: [], lastYear: [], lastTwoYear: [] }
);
This is really similar to a groupBy function.
But I'd think about how this info is used. If it's for a view layer I would think about sorting and then using a takeUntil type function for flexibility. If you needed lastWeek, lastDay and more then the previous and original solutions could get unwieldy.
const takeUntil = (pred, list) =>
list.reduce((taken, next) => (pred(next) ? taken.concat(next) : taken), []);
const sinceTwoDays = takeUntil(review => review.timestamp >= twoDaysAgo, reviews);
This way loops at minimum 2 times but it gives a lot of future flexibility. You can pass any date in and return it.
You can use Array.reduce. You also can make this function rather versatile by reducing to an array and (optionally) destructuring to extract each nested array as a separate variable. This way, you don't need to define each key name within the function. The result may or may not be considered "cleaner" depending on what your goals are, though. For example, see the getReviewsSince() function in the snippet below:
// Don't mind these functions, they're just for the sake of a working example snippet
const writeLine = (() => {
const preEl = document.querySelector('.js-pre')
return (s = '') => preEl.textContent += `${s}\n`
})()
const writeArr = (name, arr) => {
writeLine(name)
arr.forEach(r => writeLine(JSON.stringify(r)))
writeLine()
}
const getExampleReviews = () => {
let i = 0
return [
{ id: i++, timestamp: 1562166556565 },
{ id: i++, timestamp: 1514985756565 },
{ id: i++, timestamp: 1514995756565 },
{ id: i++, timestamp: 1562165556565 },
{ id: i++, timestamp: 1451837356565 },
{ id: i++, timestamp: 1451837356565 },
]
}
// ---
// Get an array - each element is a nested array for the matching timestamp
const getReviewsSince = (reviews, timestamps) =>
reviews.reduce((arr, review) => {
timestamps.forEach((ts, idx) => {
if (review.timestamp >= ts) {
arr[idx].push(review)
}
})
return arr
}, timestamps.map(() => []))
// Example usage of getReviewsSince() w/ destructuring
const reviews = getExampleReviews()
const [
lastThreeMonths,
lastSixMonths,
lastYear,
lastTwoYears,
] = getReviewsSince(reviews, [
1562166556565,
1546531756565,
1514995756565,
1451837356565,
])
// Output the results for us to see
writeArr('lastThreeMonths', lastThreeMonths)
writeArr('lastSixMonths', lastSixMonths)
writeArr('lastYear', lastYear)
writeArr('lastTwoYears', lastTwoYears)
<pre class="js-pre"></pre>
You can do something like this to remove the if statements and make it more compact.
reviews.forEach(review => {
review.timestamp >= beforeThreeMonthsDate && lastThreeMonths.push(review);
review.timestamp >= beforeSixMonthsDate && lastSixMonths.push(review);
review.timestamp >= beforeOneYearDate && lastYear.push(review);
review.timestamp >= beforeTwoYearsDate && lastTwoYear.push(review);
});
Your approach is great but if you want cleaner i ocurred something like this:
let lastThreeMonths = [];
let lastSixMonths = [];
let lastYearMonths = [];
let lastTwoYears = [];
reviews.forEach(review => {
if (review.timestamp >= beforeThreeMonthsDate) {
lastThreeMonths = [...lastThreeMonths, review];
}
else if (review.timestamp >= beforeSixMonthsDate) {
lastSixMonths = [...lastSixMonths, review];
}
else if (review.timestamp >= beforeOneYearDate) {
lastYear = [...lastYear, review ];
}
else if (review.timestamp >= beforeTwoYearsDate) {
lastTwoYear = [...lastTwoYear, review];
}
});
You can check the docs of destructing assignment Here:
Since your code already have only one loop, I am not sure what you need for "with looping just one time".
With the filter, you can do this.
lastThreeMonths = reviews.filter(review => review.timestamp >= beforeThreeMonthsDate);
lastSixMonths = reviews.filter(review => review.timestamp >= beforeSixMonthsDate);
lastYear = reviews.filter(review => review.timestamp >= beforeOneYearDate);
lastTwoYear = reviews.filter(review => review.timestamp >= beforeTwoYearsDate);
for example: I have two values in an array.
arr = [1,2]
I want api calls in such a way that at time T, I call a function with parameter 1, and at T + 20, with parameter 2, and again after 20 sec, with parameter 1 and so on.
my solution
export default class MyComponent {
arr = [1, 2];
paramIndex = 0;
T = 1000;
ngOnInit() {
setTimeout(() => {
this.toggleParam();
}, T);
/// notice that T is integer you should assign at first as example i will set it equal 1 second
}
toggleParam() {
if(paramIndex == 0)
paramIndex = 1;
else
paramIndex = 0;
this.apiCall(arr[paramIndex]);
setTimeout(()=> {
this.toggleParam();
}, 20)
}
apiCall(myParam) {
}
}
Thank you guys for your help, i found a way out using counters for the above scenario and it goes as follows:
arr=[1,2]
counter = 0
apiCall(){
this.timerSub = Observable.interval(20000).subscribe(() => {
const displayCond = counter % 2;
if (displayCond === 0) {
// call API using 1st item in array
}
else{
// call API using 2nd item in array
}
}