Im trying to calculate the total subscription fee for the penalty jar at my workplace. Every month everyone pays a certain fee on top of their penalties. It started out being 20DKK and now it's 25DKK. I have the tdata in two json objects - one with persons and one with the subscription fees
It looks like this:
subscriptionFees = [
{
"id":2,
"date":"1900-01-01T00:00:00",
"amount":20.0
},
{
"id":1,
"date":"2018-05-01T00:00:00",
"amount":25.0
}
]
persons = [
{
"id":11,
"name":"Camilla",
"active":true,
"startDate":"2017-01-01",
"endDate":"1900-01-01"
},
{
"id":6,
"name":"Cathrine",
"active":true,
"startDate":"2019-03-01",
"endDate":"1900-01-01"
},
{
"id":1,
"name":"John",
"active":true,
"startDate":"2020-03-01",
"endDate":"2021-03-01"
}
]
I'm using jquery for most of my js functions. I imagine a function running through the persons-object and calculating the total subscription fee for each of them.
Maybe something like this:
$.each(persons, function (id, obj) {
totalSubscriptionfee = calculateSubscriptionfee(obj.startDate, obj.endDate);
})
function calculateSubscriptionfee(startDate, endDate){
???
}
Can someone help me with the calculateSubscriptionfee-function? The subscription fee might get changed again in the future, so the function needs to be able to adjust for that.
Thanks,
Peter
I may have made this too complicated, but wasn't sure how else to approach it. This will
first create a ranges array with start, end and amount using reduce
map the persons array, iterating through ranges to get the amount due from that range (if any)
get the total duration, in milliseconds, convert to a rough approximation of number of months, then round down using a modulus.
In the end you get a new persons array (npersons) with the total due in it.
const subscriptionFees = [{
"id": 2,
"date": "1900-01-01T00:00:00",
"amount": 20.0
},
{
"id": 1,
"date": "2018-05-01T00:00:00",
"amount": 25.0
}
]
const persons = [{
"id": 11,
"name": "Camilla",
"active": true,
"startDate": "2017-01-01",
"endDate": "1900-01-01"
},
{
"id": 6,
"name": "Cathrine",
"active": true,
"startDate": "2019-03-01",
"endDate": "1900-01-01"
},
{
"id": 1,
"name": "John",
"active": true,
"startDate": "2020-03-01",
"endDate": "2021-03-01"
}
]
let ranges = subscriptionFees.reduce((acc, a) => {
if (acc.length === 0 || Object.hasOwnProperty(acc[acc.length - 1].end)) {
let tmp = {
start: a.date,
amount: a.amount
};
acc.push(tmp)
} else {
acc[acc.length - 1].end = a.date;
acc.push({
start: a.date,
amount: a.amount
})
}
return acc;
}, [])
ranges[ranges.length - 1].end = new Date();
//console.log('ranges', ranges);
const npersons = persons.map(person => {
let ttl = 0;
// fix endDate
if (new Date(person.endDate).getTime() < new Date(person.startDate).getTime()) person.endDate = new Date();
// iterate ranges
ranges.forEach(a => {
let end = Math.min(new Date(a.end).getTime(), new Date(person.endDate).getTime())
let start = Math.max(new Date(a.start).getTime(), new Date(person.startDate).getTime())
// console.log('calculating', person.name, 'start', new Date(start), 'end', new Date(end));
let interval = end - start;
if (interval > 0){
let tmpttl = Math.floor( (interval / 1000 / 60 / 60 / 24 / 30) * +a.amount)
tmpttl -= tmpttl % a.amount
ttl += tmpttl;
}
})
person.total = ttl
return person
})
console.log(persons)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Related
i'm having a bit of a problem here, i have an array that looks like this :
const input = {
name: 'xy',
lastname: 'yx',
history: [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" }]
}
but it have more data and more dates than this exemple, i want to sort it into a new array and put only the newest value of each day
so it will look like this :
const input = {
name: 'xy',
lastname: 'yx',
history: [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" }],
newHistory:
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" }],
}
i currently did the sorting of all the dates but i'm having trouble in doing the rest.
Since you've already sorted your data you could use a single reduce to iterate over your array of sorted data while keeping track of a new output array.
For each data point, you compare its date by the last element of the output. If the year-month-day part is not the same, you add the data point to the output array.
Here are these steps written out in code:
const input = [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" } ];
const output = input.reduce(
(out, current) => {
const lastDate = out.at(-1)?.date.slice(0, 10);
const currentDate = current.date.slice(0, 10);
if (lastDate !== currentDate) {
out.push(current);
}
return out;
},
[]
);
console.log(output);
If you like one-liners, you could also write this as:
const input = [
{ "value": 0.02, "date": "2022-08-02T23:03:22.895Z" },
{ "value": 0.04, "date": "2022-08-02T22:03:16.603Z" },
{ "value": 0.08, "date": "2022-08-02T21:03:20.378Z" },
{ "value": 0.02, "date": "2022-08-01T23:03:32.584Z" },
{ "value": 0.04, "date": "2022-08-01T22:03:30.311Z" } ];
const output = input.reduce(
(out, current) => out.at(-1)?.date.slice(0, 10) === current.date.slice(0, 10) ? out : out.concat(current),
[]
);
console.log(output);
I think this is definitely a problem where splitting different tasks into functions helps make the code easier to understand.
You said in a comment that you wanted to group the calendar dates according to UTC time, but (if you change your mind) the code below will also allow you to optionally use the system time zone (as well as some other options).
I've included lots of comments to explain as you read, but feel free to ask for clarity in a comment if something still isn't clear.
'use strict';
/** #returns a stringified number with a minimum length */
function padN (n, maxLength = 2, fillString = '0') {
return String(n).padStart(maxLength, fillString);
}
/**
* #returns a default sorting algorithm function
* for an array of objects each having a specific key
*/
function createDefaultSortByObjKey (key, {reverse = false} = {}) {
return reverse
? (oA, oB) => oA[key] > oB[key] ? -1 : oA[key] < oB[key] ? 1 : 0
: (oA, oB) => oA[key] < oB[key] ? -1 : oA[key] > oB[key] ? 1 : 0;
}
/**
* Set the `utc` option to `true` to use UTC
* #returns a date string format like `"20220703"`
*/
function getSerializedYMD (date, {utc = false} = {}) {
const year = utc ? date.getUTCFullYear() : date.getFullYear();
const month = utc ? date.getUTCMonth() : date.getMonth();
const dayOfMonth = utc ? date.getUTCDate() : date.getDate();
return `${year}${padN(month)}${padN(dayOfMonth)}`;
}
/** #pure */
function transform (array, {
newestFirst = false,
parseDates = false,
utc = false,
} = {}) {
// Create a function for sorting dates, sorting by oldest/newest
const sortFn = createDefaultSortByObjKey('date', {reverse: newestFirst});
const sorted = array
// Create actual date objects for every date property value
.map(o => ({...o, date: new Date(o.date)}))
// Sort them
.sort(sortFn);
// This will be used to compare if the current object's date occurred
// on the same date as the previous
let lastKey = '';
// The objects will be stored in inner arrays, grouped by calendar dates
const grouped = [];
for (const obj of sorted) {
const key = getSerializedYMD(obj.date, {utc});
// Create a new inner array group if a new calendar date is encountered
if (key !== lastKey) grouped.push([]);
// Add the object to the current date group
grouped.at(-1).push(obj);
// Update the last key
lastKey = key;
}
// Now just pick one date from each group
return grouped.map(group => {
// Pick the oldest/newest object in the group
const obj = group.at(newestFirst ? 0 : -1);
return parseDates
// Return it directly with the date value as an actual date object
? obj
// Or convert the date back to an ISO string first
: {...obj, date: obj.date.toISOString()};
});
}
const input = {
name: 'xy',
lastname: 'yx',
history: [
{ value: 0.02, date: '2022-08-02T23:03:22.895Z' },
{ value: 0.04, date: '2022-08-02T22:03:16.603Z' },
{ value: 0.08, date: '2022-08-02T21:03:20.378Z' },
{ value: 0.02, date: '2022-08-01T23:03:32.584Z' },
{ value: 0.04, date: '2022-08-01T22:03:30.311Z' },
],
};
const result = {
// Spread in all of the other properties
...input,
// And add a new transformed array from the `history` property
newHistory: transform(input.history, {newestFirst: true, utc: true}),
// You can even use the sorting function to sort the original history if you want:
// history: [...input.history].sort(createDefaultSortByObjKey('date', {reverse: true})),
};
console.log(result);
// You could have also used the options to get a different result variation:
// transform(input.history, {
// newestFirst: false,
// parseDates: true,
// utc: false,
// });
I have this array in JSON format:
var result=[
{
"index": 13,
"id": 1122,
*
The approach below:
get a unique list of only the dates from the array i.e. dd/mm/yyyy
for each date in the unique list, create a sorted array per the times for that date
return the 0th item from that sorted array for that date
Example code:
var result = [
{
"index": 13,
"id": 1122,
"price": 100,
"dateTime": "11/12/2020 1:59"
},
{
"index": 14,
"id": 1122,
"price": 300,
"dateTime": "11/12/2020 3:15"
},
{
"index": 15,
"id": 1122,
"price": 314,
"dateTime": "11/13/2020 2:20"
},
{
"index": 16,
"id": 1122,
"price": 280,
"dateTime": "11/13/2020 2:23"
}
];
// get a list of the dates in result
// nothing fancy - the date is just a key
var dates = result.map(k => k.dateTime.substr(0, 10));
// get unique dates from this array
var uniqueDates = Array.from(new Set(dates));
// for each unique date, sort the times descending
// return the first item (latest) for that date
var filtered = uniqueDates.map(ud => {
var dateItems = result.filter(d => d.dateTime.substr(0, 10) == ud);
dateItems.sort((a, b) => (new Date(b.dateTime)).getTime() - (new Date(a.dateTime)).getTime());
return dateItems[0];
});
// output
console.log(filtered);
Sorting the Array on the Date value of dateTime, write a result array with dates-only as keys, retrieve the values of the result.
The TypeError, by the way, is because you should check for i + 1 being smaller than result.length in the loop (this will be falsy for the last element within the loop. In that case new Date(result[i+1].dateTime) will throw the error).
// initialize log helper
const log = Logger();
// create an empty Object
const result = {};
// sort data ascending
const dataSorted = getData().sort((a, b) =>
new Date(a.dateTime) - new Date(b.dateTime));
// add to result with datestring as key.
// The value with the most recent date will be preserved
// because key values are unique (so equal keys are overwritten)
dataSorted.forEach(v => result[new Date(v.dateTime).toDateString()] = v);
// the values of [result] contain the most recent records per date
log(Object.values(result));
// this can also be a one liner, using a reducer method
const resultX = Object.values(
getData()
.sort( (a, b) => new Date(a.dateTime) - new Date(b.dateTime) )
.reduce( (acc, value) =>
({...acc, [new Date(value.dateTime).toDateString()]: value}), {} )
);
log(`\n**from reducer`, resultX);
function getData() {
return [{
"index": 13,
"id": 1122,
"price": 100,
"dateTime": "11/12/2020 1:59"
},
{
"index": 14,
"id": 1122,
"price": 300,
"dateTime": "11/12/2020 3:15"
},
{
"index": 15,
"id": 1122,
"price": 314,
"dateTime": "11/13/2020 2:20"
},
{
"index": 16,
"id": 1122,
"price": 280,
"dateTime": "11/13/2020 2:23"
}
];
}
function Logger() {
const report = document.querySelector("#report") ||
document.body.insertAdjacentElement(
"beforeend",
Object.assign(document.createElement("pre"), {id: "report"}));
return (...args) => args.forEach(stuff =>
report.textContent += (stuff instanceof Object
? JSON.stringify(stuff, null, 2) : stuff) + "\n");
}
You can group the data based on date like,
const groups = data.reduce((groups, currVal) => {
const date = currVal.dateTime.split(' ')[0];
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(currVal);
return groups;
}, {});
-> Here we split the date and and time part using split(' ') and took the date alone separately and form a group,
const date = currVal.dateTime.split(' ')[0];
And then you can get the recent date and time using the method,
groups[item].reduce((a, b) => (a.dateTime > b.dateTime ? a : b));
Working snippet:
const data = [
{
"index": 13,
"id": 1122,
"price": 100,
"dateTime": "11/12/2020 1:59"
},
{
"index": 14,
"id": 1122,
"price": 300,
"dateTime": "11/12/2020 3:15"
},
{
"index": 15,
"id": 1122,
"price": 314,
"dateTime": "11/13/2020 2:20"
},
{
"index": 16,
"id": 1122,
"price": 280,
"dateTime": "11/13/2020 2:23"
}
];
//Group the data based on date
const groups = data.reduce((groups, currVal) => {
const date = currVal.dateTime.split(' ')[0];
if (!groups[date]) {
groups[date] = [];
}
groups[date].push(currVal);
return groups;
}, {});
//Get the recent date and time based on each group
const result = [];
Object.keys(groups).filter(item => {
const newData = groups[item].reduce((a, b) => (a.dateTime > b.dateTime ? a : b));
result.push(newData);
});
console.log(result)
So, the this is about consuming an API that has a date/time property. The content should change every 3 hours by comparing current user Date/time with that of the API and also assigning past and upcoming hours in a separate an arrays to be displayed in other section of the page. I managed to assign past and upcoming dates to their respective arrays. I need to compare the date and to assign "current data" if the user Date/Time is equal to or within 3 hours in a property to display it for the whole duration of three hours.
this.dataService.getData().subscribe((data:any[])=>{
const now = new Date('2021-02-14 09:00:00');
for (const item of data) {
const apiDate = new Date(item.dateTime);
if(now.getTime() > apiDate.getTime()){
this.future.push('future dates')
} else if(now.getTime() < apiDate.getTime()){
this.past.push('past dates')
}else if(now.getTime() == apiDate.getTime()){
//in real time, they'll only be equal for one second
this.current = 'Show NOW'
}
}
This is the structure of API/Json Data retuned
[ { "number": 10, "dateTime": "2021-02-14 00:00:00" }, { "number": 20, "dateTime": "2021-02-14 03:00:00" }, { "number": 30, "dateTime": "2021-02-14 06:00:00" }, { "number": 40, "dateTime": "2021-02-14 09:00:00" }, { "number": 50, "dateTime": "2021-02-14 12:00:00" }]
a better approach to this would even be better.
Thanks
If your want to show time within range, then you can create an object with your time boundaries:
getTimeInterval = () => {
const from = new Date();
const to = new Date(from);
to.setHours(to.getHours() + 3)
return { from, to };
}
and then just check both boundaries of date from and to:
this.dataService.getData().subscribe((data:any[])=>{
const dateRange = this.getTimeInterval();
for (const item of data) {
const apiDate = new Date(item.dateTime);
if (dateRange.from.getTime() > apiDate.getTime()
&& dateRange.to.getTime() > apiDate.getTime())
{
this.future.push('future dates');
}
else if(dateRange.from.getTime() < apiDate.getTime())
{
this.past.push('past dates')
}
else if (dateRange.from.getTime() >= apiDate.getTime()
&& dateRange.to.getTime() <= apiDate.getTime())
{
this.current = 'Show NOW'
}
}
I can't set up an algo that counts my occurrences while respecting ESlint's 6 standards in javascript.
My input table is :
[
{
"id": 2,
"name": "Health",
"color": "0190fe"
},
{
"id": 3,
"name": "Agriculture",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
},
{
"id": 1,
"name": "Urban planning",
"color": "0190fe"
}
]
And i want to get :
{"Urban planning": 2, "Health": 1, ...}
But that does not work with ESLINT / REACT compilation...
This is my code :
const jsonToIterate = *'MyPreviousInputJson'*
const names = []
jsonToIterate.map(item => (names.push(item.name)))
const count = []
names.forEach(item => {
if (count[item]){
count.push({text: item, value: 1})
} else {
count.forEach(function(top){top.text === item ? top.value =+ 1 : null})
}
})
Thank you so much
Well, you want an object in the end, not an array, so count should be {}. I also wouldn't use map if you're not actually returning anything from the call. You can use reduce for this:
let counts = topicsSort.reduce((p, c, i, a) => {
if (!p.hasOwnProperty(c.name)) p[c.name] = 0;
p[c.name]++;
return p;
}, {});
I'm half exppecting someone to close this as a duplicate because all you've asked for is a frequency counter. But here's an answer anyway:
const jsonToIterate = *'MyPreviousInputJson'*;
const names = {};
jsonToIterate.map(obj => {
if(obj.name in names){
names[obj.name]++
}
else{
names[obj.name] = 1;
}
})
I am new to Lodash and Functional Programming concepts. So, I have an array of objects with day-wise date like these:
[
{
"date": '1-Jan-2015',
"count": 4
},
{
"date": '4-Jan-2015',
"count": 3
},
{
"date": '1-Feb-2015',
"count": 4
},
{
"date": '18-Feb-2015',
"count": 10
}
]
and I want to reduce and aggregate it in such a way that I get an array of objects where each object has monthly data instead of day-wise data like this:
[
{
"date": 'Jan, 2015',
"count": 7 // aggregating the count of January
},
{
"date": 'Feb, 2015',
"count": 14 //aggregating the count of February
}
]
Currently, I have a written a very unreadable and convoluted code full of ifs and fors which works. However, I want to refactor it using lodash. Is it possible using lodash? I looked around and found _.reduce and _.groupBy which I can probably use but I am stumped right now and can't figure out a good clean implementation.
We can use _.reduce & _.values
var arr = [
{
"date": '1-Jan-2015',
"count": 4
},
{
"date": '4-Jan-2015',
"count": 3
},
{
"date": '1-Feb-2015',
"count": 4
},
{
"date": '18-Feb-2015',
"count": 10
}
]
_.values(_.reduce(arr,function(result,obj){
var name = obj.date.split('-');
name = name[1]+', '+name[2];
result[name] = {
date:name,
count:obj.count + (result[name]?result[name].count:0)
};
return result;
},{}));
You don't need lodash to achieve what you want, you could use plain old Javascript:
var array = [{
"date": '1-Jan-2015',
"count": 4
}, {
"date": '4-Jan-2015',
"count": 3
}, {
"date": '1-Feb-2015',
"count": 4
}, {
"date": '18-Feb-2015',
"count": 10
}]
var result = array.reduce(function(ar, item) {
var index = item.date.split('-').slice(1,3).join(', ') //getting date Month-Year
_item = ar.filter(function(a) {
return a.date === index
})[0] // getting item if already present in array
// getting index of _item if _item is already present in ar
indexOf = ar.indexOf(_item)
if(indexOf > -1)
// we sum the count of existing _item
ar[indexOf] = {date: index, count: count: _item.count + item.count }
else
// item is not yet in the array, we push a new _item
ar.push({date: index, count: item.count})
return ar; // return the array as required by reduce
}, []) // initialize the reduce method with an empty array
console.log(result) // your array with aggregated dates
And for the fun, a lodash version:
_.values(array.reduce(function(obj, item) {
var index = item.date.split('-').slice(1, 3).join(', ')
obj[index] = {date: index, count: (obj[index] && obj[index].count || 0) + item.count}
return obj
}, {}))
See jsfiddle here