reactjs - access previous element from created data function? - javascript

I want to generate some data:
function createData(stock, purchase, percentdown) {
const dataarray=[]
for(let i = 1; i < 59; i++) {
const startdate = new Date()
const enddate = new Date(startdate.setMonth(startdate.getMonth(), 1))
const date = new Date(startdate.getTime() + Math.random() * (enddate.getTime() - startdate.getTime()))
const dateFormat = date.getMonth() + 1 + '/' + date.getDate() + '/' + date.getFullYear()
// not getting the right dates from the above
// If its the second element being created, other than initial I want to use the last generated stockprice
const stockprice = stock * Math.floor(Math.random() * 10);
const percent = Math.floor(Math.random() * 10)
const percentDiff = (stockprice*percent)/100
const purchaseprice = parseInt(stockprice) - percentDiff
const message = MessageFilter(purchaseprice, stock, percent)
dataarray.push(
{
id:i,
date:dateFormat,
stock_price:stockprice,
purchase_price:purchaseprice,
message:message
}
)
}
return dataarray
}
What I want to get as output:
//Using example dates and id's
[
{
id:1,
date:11/06/2022,
stock_price: 12000,
purchase_price: 11400,
message:message
},
{
id:2,
date:11/07/2022, //date increased by 1 day
stock_price: 12600, //price increased from last stock_price,id:1, by a random percentage
purchase_price: 11400, //calculated using this stock price
message:message
}
{
id:3,
date:11/07/2022, //date increased by 1 day
stock_price: 12300, //price increased/decreased from last stock_price,id:2, by a random percentage
purchase_price: 11400, //calculated using this stock price
message:message
}
]
How do I access the previous generated stockprice?
Edit:
Parameters which need to be passed to the function to get the desired output:
1- stock i.e. stockprice from a form denoted by stock in the function for 1st calculation, after which the created stockprice will be used.
2- date - not required to be passed in as it will be todays date.
3- percentdown - required for calculation of purchaseprice
4- Initial purchase price denoted by purchase for 1st element and subsequent calculation will be done by purchaseprice generated in fucntion.
Edit 2:
if (i === 1) {
const stockprice = stock * Math.floor(Math.random() * 10);
} else {
const { stock } = dataarray[dataarray.length - 1];
const stockprice = stock * Math.floor(Math.random() * 10);
}
It gives me a stockprice is not defined error.

To access the last element of an array just use arr[arr.length - 1]. In this case, it would be:
const { stock_price } = dataarray[dataarray.length - 1]
Note: {...} is for destructuring, in order to extract the property stock_price of the object returned by the above expression.

Related

Extracting monthly values from data with irregular dates

I have bunch of electricity meter readings which have irregular dates. See below :
ReadingDate Meter
19/01/2021 5270
06/03/2021 5915
11/05/2021 6792
08/07/2021 7367
9/9/2021 8095
8/11/2021 8849
02/12/2021 9065
17/01/2022 9950
Now I'd like to transform this into monthly readings, using just this data, to end up with a table like this
Month Usage
2021-01 452
2021-02 393
2021-03 416
2021-04 399
2021-05 341
2021-06 297
2021-07 347
2021-08 358
2021-09 369
2021-10 389
2021-11 295
2021-12 586
2022-01 308
Now, I have a working solution, but I'm sure there's a more beautiful concise way of doing it.
What I do is to create an intermediate array that has one line for each date between first and last meter readings.
Each item in the array has 3 values :
the date
the average value for that date (calculated by counting the days between meter readings and dividing that by change in the meter.
the corresponding month
The last step then is to loop over this intermediate array and sum the values for each different month.
Here's the working code (its taken from Google Apps Script so please ignore the spreadsheet specific stuff:
var DailyAveragesArray = [['Date','Usage','Month']];
var monthlyObject = {};
var monthlyArray = [['Month','Usage']];
function calculateAverageDailyFigures() {
// give indices for the useful columns, 0 numbered
var ReadingDateColumn = 0;
var MeterReading = 1;
// Read into an array
var MeterReadingData = ss.getDataRange().getValues() // Get array of values
const sortedReadings = MeterReadingData.slice(1).sort((a, b) => a[0] - b[0]);
// from https://flaviocopes.com/how-to-sort-array-by-date-javascript/
// First calculate the number of days and average daily figure for each row
// Note we don't do this for the last row
for(i=0; i < sortedReadings.length - 1 ; i++){
var NumberOfDays = (sortedReadings[i+1][0] - sortedReadings[i][0])/(1000*3600*24);
sortedReadings[i].push(NumberOfDays);
var MeterDifference = sortedReadings[i+1][1] - sortedReadings[i][1];
var AverageDailyFigure = MeterDifference/NumberOfDays;
sortedReadings[i].push(AverageDailyFigure);
}
BuildDailyArray(sortedReadings);
}
function BuildDailyArray(sortedReadings){
// For each row in sorted , loop from the date to the next date-1 and create columns date and Usage
for(i=0; i<sortedReadings.length -1 ;i++){
for (var d = sortedReadings[i][0]; d < sortedReadings[i+1][0]; d.setDate(d.getDate() + 1)) {
var newDate = new Date(d);
var month = newDate.getFullYear() + '-' + ('0' + (newDate.getMonth() + 1)).slice(-2);
DailyAveragesArray.push([newDate,sortedReadings[i][3],month]);
// Check if the month is in the object and add value, otherwise create object an add value
if(month in monthlyObject){
monthlyObject[month] = monthlyObject[month] + sortedReadings[i][3];
} else {
Logger.log('Didnt find month so create it');
monthlyObject[month] = sortedReadings[i][3];
}
}
}
Logger.log(DailyAveragesArray.length);
Logger.log(monthlyObject);
var DailyUsageData = ss.getRange('D1:F'+DailyAveragesArray.length);
DailyUsageData.setValues(DailyAveragesArray);
BuildMonthlyArray();
}
function BuildMonthlyArray(){
const keys = Object.keys(monthlyObject);
Logger.log(keys);
keys.forEach((key, index) => {
monthlyArray.push([key,Math.round(monthlyObject[key])]);
});
var MonthlyUsageData = ss.getRange('H1:I'+monthlyArray.length);
MonthlyUsageData.setValues(monthlyArray);
}
So, my question is, how would I do this nicer, more beautifully, not so verbose ?
I'm not sure what the correct term is for what I want to do. I don't think it's resampling .
I'd appreciate any comments.
Thanks / Colm
Here is my shot on this.
The way i'm doing it:
Initializing all days and its value
Grouping by month
Calculating the average per month
Explanation a bit more precise
initDateFromString
The method initDateFromString takes a dates with the format DD/MM/YYYY and return the associated js date object
initAllDates
The method initAllDates will split the data into day and add the average value of the difference for each day
for example, for the first two readings, it will result to an array of dates looking like :
date
value
19/01/2021
14.02
20/01/2021
14.02
....
....
05/03/2021
14.02
06/03/2021
14.02
The value 14.02 comme from the following calcul :
(newReadingMeter - oldReadingMeter)/nbDaysBetweenDates
Which in this example is (5915 - 5270)/46 = 14.02
joinToMonth
The joinToMonth method will then group the days into month with all the days value summed !
const data = [{
ReadingDate: '19/01/2021',
Meter: 5270
},
{
ReadingDate: '06/03/2021',
Meter: 5915
},
{
ReadingDate: '11/05/2021',
Meter: 6792
},
{
ReadingDate: '08/07/2021',
Meter: 7367
},
{
ReadingDate: '9/9/2021',
Meter: 8095
},
{
ReadingDate: '8/11/2021',
Meter: 8849
},
{
ReadingDate: '02/12/2021',
Meter: 9065
},
{
ReadingDate: '17/01/2022',
Meter: 9950
}
]
function initDateFromString(dateString){
let dateParts = dateString.split("/");
return new Date(+dateParts[2], dateParts[1] - 1, +dateParts[0]);
}
function initAllDates(data){
let dates = []
let currentValue = data.shift()
const currentDate = initDateFromString(currentValue.ReadingDate)
data.forEach(metric => {
const date = initDateFromString(metric.ReadingDate)
const newDates = []
while(currentDate < date){
newDates.push({date: new Date(currentDate)})
currentDate.setDate(currentDate.getDate() + 1)
}
dates = dates.concat(newDates.map(x => {
return {Usage: (metric.Meter - currentValue.Meter) / newDates.length, date: x.date}}
))
currentDate.setDate(date.getDate())
currentValue = metric
})
return dates
}
function joinToMonth(dates){
return dates.reduce((months, day) => {
const month = day.date.getMonth()
const year = day.date.getFullYear()
const existingObject = months.find(x => x.month === month && x.year === year)
if (existingObject) {
existingObject.total += day.Usage
} else {
months.push({
month: day.date.getMonth(),
year: day.date.getFullYear(),
total: day.Usage,
})
}
return months;
}, []);
}
const dates = initAllDates(data)
const joinedData = joinToMonth(dates)
console.log(joinedData)

JS - Calculate an interval for a given number

I am trying to generalize the following function that I have implemented:
/**
* Calculates an interval for the given age.
*
* #memberof module:Users/Functions
* #function getAgeInterval
* #param {number} age - The age of the user.
* #param {number} [minimumAge=18] - The minimum age.
* #param {number} [range=10] - The range.
* #throws {Error} The given age must be greater or equal than the minimum age.
* #returns {string} The age interval.
*/
export default (age, minimumAge = 18, range = 10) => {
if (age < minimumAge) {
throw new Error(
"The given age must be greater or equal than the minimum age.";
);
}
const start = Math.floor((age - 1) / range) * range + 1;
const end = start + range - 1;
const interval = `${Math.max(start, minimumAge)}-${end}`;
return interval;
};
Basically, in this method, I group the age of my users using a minimum age and a range. Here is an example:
const getAgeInterval = (age, minimumAge = 18, range = 10) => {
if (age < minimumAge) {
throw new Error(
"The given age must be greater or equal than the minimum age."
);
}
const start = Math.floor((age - 1) / range) * range + 1;
const end = start + range - 1;
const interval = `${Math.max(start, minimumAge)}-${end}`;
return interval;
};
//
// MAIN
//
for (let age = 18; age < 100; age += Math.round(Math.random() * 10)) {
console.log(`${age}: ${getAgeInterval(age)}`);
}
For now, the method is only working for "ages". But I suppose it is possible to make it work with any type of numbers, (i.e. the total followers counter of a user).
Users might have different number of followers, and I need to group it reusing the method I implemented. The output should look like:
0: "0-10"
100: "11-100"
999: "101-1000"
1117: "1001-10000"
9999: "1001-10000"
15201: "10001-100000";
1620620: "1000001-10000000"
As you can see, the only difference, in order to make it work, is the "dynamic" range. If you take a look at the output, the range goes from 10 to millions.
Any ideas? Any generic implementation to allow dynamic ranges?
UPDATE
Here is the generic method:
const calculateInterval = (counter, minimumCounter = 0, range = 10) => {
if (counter < minimumCounter) {
throw new Error(
"The given counter must be greater or equal than the minimum counter."
);
}
const start = Math.floor((counter - 1) / range) * range + 1;
const end = start + range - 1;
const interval = `${Math.max(start, minimumCounter)}-${end}`;
return interval;
};
//
// MAIN
//
const counters = [0, 100, 999, 1117, 9999, 15201, 1620620];
counters.forEach((totalFollowers) => {
console.log(`${totalFollowers}: ${calculateInterval(totalFollowers)}`);
});
//
// Q: HOW DO I MAKE THE RANGE DYNAMIC (BASED ON THE AMOUNT OF FOLLOWERS)?
//
OUTPUT MUST BE:
0: "0-10"
100: "11-100"
999: "101-1000"
1117: "1001-10000"
9999: "1001-10000"
15201: "10001-100000";
1620620: "1000001-10000000"
What you're looking for is called a logarithmic scale. In this case, the interval is not incremented but multiplied by the range in each step.
You can find the beginning of the range by raising r to the floor of the r-base logarithm of n-1, where r is the range and n is the number.
To get the edge cases right though, you need to make some adjustments (add one to the start of the range, add a default for values smaller or equal to the range, etc):
const baseNlog = (base, x) => Math.log(x) / Math.log(base)
const logarithmicInterval = (n, range = 10) => {
if(n <= range)
return `0-${range}`
const start = range ** Math.floor(baseNlog(range, n-1));
const end = start * range;
const interval = `${start + 1}-${end}`;
return interval;
};
//
// MAIN
//
console.log([
0,
1,
10,
11,
100,
999,
1117,
9999,
15201,
1620620
].map(e => `${e}: ${logarithmicInterval(e)}`))
What you can simply do is counting the number of digit in the number and creating your range using this.
For the low range it will be 10 ** (nbDigits - 1) + 1 (or 0 if the number is 0)
For the high range it will be 10 ** (nbDigits)
const calculateInterval = (number, minimumCounter = 0) => {
if (number < minimumCounter) {
throw new Error(
"The given counter must be greater or equal than the minimum counter."
);
}
const nbDigits = number > 0 ? (number - 1).toString().length : 1
const start = number > 0 ? 10**(nbDigits - 1) + 1 : 0
const end = 10 ** (nbDigits)
const interval = `${Math.max(start, minimumCounter)}-${end}`;
return interval;
};
//
// MAIN
//
const counters = [0, 100, 999, 1117, 9999, 15201, 1620620];
counters.forEach((totalFollowers) => {
console.log(`${totalFollowers}: ${calculateInterval(totalFollowers)}`);
});
One simple implementation could use a switch block to return a range string based on the length of the passed number.
Working snippet:
console.log(getRange(100))
console.log(getRange(999))
function getRange(num) {
numdigits= parseInt(num-1).toString().length;
switch(numdigits) {
case 1:
return "0..10"
case 2:
return "11..100"
case 3:
return "101..1000"
case 4:
return "1001..10000"
case 5:
return "10001..100000"
case 6:
return "100001..1000000"
case 7:
return "1000001..10000000"
default:
return "too large"
}
}// end function
Another approach could build the string by catenating zeros to the upper and lower limits in a loop iterating the length of the passed number times.
Like this:
console.log(getRange(100))
console.log(getRange(999))
function getRange(num) {
numdigits= parseInt(num-1).toString().length;
if (numdigits == 0) {return "0..10"}
else {
return `1${"0".repeat(numdigits-2)}..1${"0".repeat(numdigits)}`;
}
} // end function

Compounding interest monthly with a deposit

I want to compound interest on a weekly/fortnightly/monthly/annual basis.
I also want an option to have a deposit amount that can be added in.
I have already tried the standard formula of calculating the final amount accrued, as seen here:
(source: gstatic.com)
For example here is my method for calculating the interest compounding weekly:
function calculateWeekly(state: any) {
const { savings, deposit ,interest, timePeriodSelector, timePeriodLength } = state;
let numberOfYears = 0;
if (timePeriodSelector === "weekly") {
numberOfYears = timePeriodLength / weeksInAYear;
} else if (timePeriodSelector === "fortnightly") {
numberOfYears = (timePeriodLength / weeksInAYear) * 2;
} else if (timePeriodSelector === "monthly") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAMonth;
} else if (timePeriodSelector === "annually") {
numberOfYears = (timePeriodLength / weeksInAYear) * weeksInAYear;
}
const weeklyRate = interest / 100 / weeksInAYear;
const lengthOfCompunding = numberOfYears * weeksInAYear;
let startingFigure = parseInt(savings) + parseInt(deposit);
//total gets added on for every time cycle of week
let total =
(startingFigure * (Math.pow(1 + weeklyRate, lengthOfCompunding) - 1)) / weeklyRate;
return roundToTwoDP(total);
}
The issue with the above code is that the deposit gets added into the calculation every time the interest accrues. So a deposit of $10 weekly for 10 weeks will actually get added up to $100.
I attempted a method to accrue the interest using a loop for each week here:
// loops how many times to compound the interest
for(let i = numberOfYears - (1/weeksInAYear); i > 0; i-= (1/weeksInAYear)){
let interestGained = (total * (Math.pow((1 + weeklyRate), lengthOfCompunding))) - total;
total += interestGained + savings;
}
Thanks for any help!
This should do what you want:
const range = (min, max) => {
const size = 1 + max - min
return [...Array(size).keys()].map(n => n + min)
}
const weeksInAYear = 52
const addWeeklyInterest = interestRatePerWeek => (savings, _) => savings + savings * interestRatePerWeek
const calculateTotal = (savings, numberOfYears, interestRatePerWeek) => {
const numberOfWeeks = numberOfYears * weeksInAYear
return range(1, numberOfWeeks).reduce(addWeeklyInterest(interestRatePerWeek), savings)
}
console.log(calculateTotal(1000.00, 1, 0.02))
Output is 2800.328185448178. You might want to round that for display purposes, but also keep in mind that if accuracy is important, you can't use floating-point numbers.

How to convert a date into a random index in a list?

I need something that generates a random item from a list, but that item is randomised every day and is consistent for all users. This is on a website, and thus using JavaScript (although really I just need the algorithm you'd follow, not necessarily the code itself).
I've got day, month, and year stored in variables, but what could I do to convert this into an integer between 0 and list length?
Thanks!
Simple algorithm:
Pair 3 integers to 1:
seed = pair(day, pair(month, year))
use this int to seed a random number generator, for desired randomness
seed -> [0, 1, 2, ..., array.length - 1]
index = Math.round(randomOf(seed) * (array.length - 1));
element = array[index]
Here's a basic javascript implementation of the aforementioned pairing function:
function pair(x, y) { return ((x + y) * (x + y + 1)) / 2 + y; }
implementing randomOf (check the link above for "random number generator"):
randomOf(seed){
Math.seedrandom(seed);
return Math.random();
}
You can use this https://github.com/davidbau/seedrandom. So the idea is to change the seed for the random function to the date (maybe just the month, day, and year). For example:
Math.seedrandom('August 19, 1975');
console.log(Math.random()); // 0.8213442237794714
console.log(Math.random()); // 0.9082914871756658
and then to convert it to an integer you can use the function describe here:
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
edit:
Then for example to use it to get something in an array
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}
let arr = ['firsEl', 'secondEl', 'thirdEl', 'fourthEl'];
let d = new Date(); //get todays date
let month = d.getMonth();
let day = d.getDate();
let year = d.getFullYear();
//make the random with seed
Math.seedrandom(`${month} ${day}, ${year}`);
//and finally using the function for int
let item = arr[getRandomInt(0, arr.length)];
console.log(item);
<script src="http://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.4/seedrandom.min.js"></script>

How to sum values over a given date range

I'm using LocalStorage to save an array of Dates and Costs.
When I'm writing localStorage.getItem("todos"); into the console, the format will be like this:
"[{"due":"28/10/2017","task":"80"},{"due":"06/10/2017","task":"15"}]"
Where due is the Date, and TASK is the AMOUNT.
I managed to get the TOTAL of AMOUNTS by:
total: {
type: String,
value: () => {
var values = localStorage.getItem("todos");
if (values === undefined || values === null) {
return "0";
}
var data = JSON.parse(values);
var sum = 0;
data.forEach(function(ele){ sum+=Number(ele.task)}); return sum;
}
}
Now I'm trying to get the TOTAL of last 6 MONTHS.
I have no idea on how to approach this.
How should I be able to do this?
During your iteration you need to add a check to make sure the sum is only including values where the due date is within your range. If you can use a library like moment, this would greatly simplify your logic.
const data = [
{ due: '28/10/2017', task: 80 },
{ due: '06/10/2017', task: 15 },
{ due: '10/05/2000', task: 3000 }
];
const sixMonthsAgo = moment().subtract(6, 'months');
const total = data.reduce((acc, item) => {
const dueDate = moment(item.due, 'DD/MM/YYYY');
return acc + (dueDate.isAfter(sixMonthsAgo) ? item.task : 0);
}, 0);
console.log('total should equal 95: ', total);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>
Here is a solution for your issue :
make a test in the forEach loop :
I've put 4 dates : 2 under 6 months and 2 older
The result is 80+15 = 95
// After JSON.parse
var todos=[{"due":"28/10/2017","task":"80"},{"due":"06/10/2017","task":"15"},{"due":"06/04/2017","task":"15"},{"due":"06/02/2017","task":"15"}];
var sum = 0;
var minDate = new Date();
var month = minDate.getMonth()+1-6; // get month minus 6 months
var year = minDate.getFullYear(); // get year
if(month < 1){ // if month is under January then change year
month+=6;
year-= 1;
}
minDate.setMonth(month); // Replace our min date with our - 6 m
minDate.setYear(year); // set year in case we have changed
todos.forEach(function(ele){
var arr = ele.due.split("/"); // split french string date into d,m,y
if(arr.length==3){
var dueDate = new Date(arr[2],arr[1],arr[0]); // get the task date
if(dueDate>minDate){ // if task is not to old then
sum+=parseInt(ele.task); // sum it
}
}
});
console.log(sum);

Categories