Using map reduce in javascript - javascript

I'm trying to use mapreduce to calculate monthly sales and graph with chart.js later.
Suppose I have a JSON response with fields, amount and date.
For my map function, I took out the month:
month = _.map([items[i].transactionDate], function(date) {
return {
lables: items[i].transactionDate.slice(5,7)
};
});
and for my reduce function ... well I didn't know what to do here.
sum = _.reduce([items[i].amt], function(memo, num) {
return memo + items[i].amt
});
I realized that this will eventually calculate total sum not per month. Problem is that I don't know how to relate the two functions properly.
Edit: Per request, my JSON :
"items": [
{
"_id": "56d1cf3704aee3c68d89cc09",
"amt": 5,
"transactionDate": "2016-02-27T16:30:47.561Z",
}
]
and what I'm trying to get out of this is sales per month on a graph. so far I've been able to project months and total sale but not sales per a specific month.

I think, simply make one loop with adding new variable
let result= {};
_.forEach(items, (item) => {
// get month
let month = item.transactionDate.slice(5,7);
// get amount
let amount = item.amt;
// check if we have this month in finaly object
if(month in finaly) {
// added amount
result[month] += amount;
} else {
// if this month doesn't exist added with inital value
result[month ] = amount;
}
});
When you can get all amount of certain month or get sum of all months
let allSum = _.reduce(result, (sum, amount) => sum += amount);
let amountInCertainMonth = result["01"];

map and reduce are functions of Array that iterate over the array for you
map returns a new array with the results of the mapping function for each element
var months = items.map ( item => item.transactionDate.slice(5,7) )
and reduce applies the reducing function to each element and an accumulator.
var sum = items.reduce( (accum, item) => accum + item.amt , 0);

I assume items[i] is an object that has .transactionsDate and .amt as two arrays with corresponding indices. If so, you can get sales per month and total sales within one reduce function by starting with { totalSales, months }.
var chartData = items[i].transactionDate.reduce(function(total, month, index) {
var monthlyData = {
label: month.slice(5, 7),
sales: items[i].amt[index]
};
total.months.push(monthlyData);
total.totalSales += item.amt;
return total;
}, {
totalSales: 0, months: []
});
// get total sales
var totalSales = chartData.totalSales;
// get sales for May starting at index 0
var salesForMay = chartdata.months[4];
Let me know if this is what you were looking for.

I know this is old, but here is how to use reduce as a group by
var results = items
.map(function(data){
return {"month": data.transactionDate.slice(0,7), "amt": data.amt};
})
.reduce(function(amounts, data){
if (!amounts.hasOwnProperty(data.month))
amounts[data.month] = data.amt;
else
amounts[data.month] = amounts[data.month] + data.amt;
return amounts;
}, {});

Related

How do I filter the data sent by firestore

How do I filter the data in collection in Firestore
If the first letter is "+" then I want to get filtered in Income section if the first letter is "-" then I want to get filtered in Expenditure Section
I tried this, but not working:
const getUsers = async()=>{
total_income_amount = await db.collection("users").get().then((querySnapshot) => {
const sum = querySnapshot.docs.filter((item) => item > 0).reduce((a, b) => a + b.data().amount, 0)
return sum
})
}
I am getting the output as 0
and I want the output with two decimal places
EDIT
Here's my code:
total_amount = db.collection("users").get().then((querySnapshot) => {
var total_sum = 0;
var income_sum = 0;
var exp_sum = 0;
querySnapshot.docs.forEach(doc => {
const amount = doc.data().amount;
amount >= 0.0? income_sum += amount : exp_sum -= amount;
total_sum += amount;
});
return {total: total_sum, income: income_sum, expense: exp_sum }
}).finally(result => console.log("Total: ", result));
You can loop through the list of values and append the values once rather than looping through it 5 times with a reduce & filter
total_amount = db.collection("users").get().then((querySnapshot) => {
var total_sum = 0;
var income_sum = 0;
var exp_sum = 0;
querySnapshot.docs.forEach(doc => {
const amount = doc.data().amount;
amount >= 0.0? income_sum += amount : exp_sum -= amount;
total_sum += amount;
});
return {total: total_sum, income: income_sum, expense: exp_sum }
})
.finally(result => console.log("TOTALS", result);
EDIT
It's not good to make major changes to your Question
but in general, you can truncate all number's to a decimal place using toFixed() on the final output
https://www.w3schools.com/jsref/jsref_tofixed.asp
UPDATE
Ensure your numbers are converted to real numbers from user inputs with Number(value) if the typeof returned is not a number, you can know if it is valid or not.
Once this has been done, ensure you return the correct calls as needed as scripts also only fire once.
when rendering values inside HTML, you want to use an embedded <span> tag with the appropriate id tag rather than editing it via javascript

How to set value in array depending on previous value of array

I've got a simple problem, but I'm struggling to find the easiest solution without transforming the array a hundred times.
I want to do a simple stacked graph in google sheets, with weeks on X and values on Y. I got the values for each week, but only for weeks, that have a value.
The values are all calculations I've done with google apps script/ js.
person1 = [[2019/37,2], [2019/42,3]] and so on, for multiple persons and for 80 weeks in total.
The num value is the total value after each week. So I want the array to be filled up with the missing weeks. Therefore I mapped this to another array, where I have all the weeks but no values, giving these weeks the value 0:
person1= [[2019/37,2],[2019/38,0],[2019/39,0],...,[2019/42,3],[2019/43,0],[2019/44,0],...]
This of course does not fit to see a progress in the graph.
So I need something to set the weeks, which were filled up, to the previous value, resulting in
person1= [[2019/37,2],[2019/38,2],[2019/39,2],...,[2019/42,3],[2019/43,3],[2019/44,3],...]
Looping through this and setting the values with something like person[i][1] == person[i-1][1] seems not to be a good practice of course.
So, what would be the best way to achieve this? I'm kind of stuck with this now, I feel like I don't see the forest for the trees.
Thanks in advance!
code:
let valueArray = [[2019/37,2], [2019/42,3]]
let weeksArray = [2019/38,2019/39,2019/40,2019/41...]
//find missing weeks
let notFound = weeksArray.filter(el => valueArray.includes(el) == false).map(x => [x,0]);
//concat and sort
let outArray = arr.concat(notFound).sort((a,b)=> a[0].localeCompare(b[0]));
//output:
//[[2019/37,2],[2019/38,0],[2019/39,0],...,[2019/42,3],[2019/43,0],[2019/44,0],...]
Solution:
Since you already have the expanded array, you can use map on the whole array and use a function to replace the values:
var weeks = [[2019/37,2],[2019/38,0],[2019/39,0],[2019/40,3],[2019/41,0],[2019/42,4],[2019/43,0],[2019/44,0]];
weeks.map((a,b)=>{weeks[b][1] = (a[1] == 0 && b > 0) ? weeks[b-1][1] : weeks[b][1]});
To make it more readable, this is the same as:
weeks.forEach(function missing(item,index,arr) {
if (item[1] == 0 && index > 0) {
arr[index][1] = arr[index-1][1];
}
}
);
Console log:
References:
Arrow Functions
Conditional Operator
Array.prototype.map()
function fixArray() {
var array = [["2019/1", "1"], ["2019/10", "2"], ["2019/20", "3"], ["2019/30", "4"], ["2019/40", "5"]];
var oA = [];
array.forEach(function (r, i) {
oA.push(r);
let t1 = r[0].split('/');
let diff;
if (i + 1 < array.length) {
let inc = 1;
let t2 = array[i + 1][0].split('/');
if (t1[0] == t2[0] && t2[1] - t1[1] > 1) {
do {
let t3 = ['', ''];
t3[0] = t1[0] + '/' + Number(parseInt(t1[1]) + inc);
t3[1] = r[1];
diff = t2[1] - t1[1] - inc;
oA.push(t3);
inc++;
} while (diff > 1);
}
}
});
let end = "is near";
console.log(JSON.stringify(oA));
}
console.log:
[["2019/1","1"],["2019/2","1"],["2019/3","1"],["2019/4","1"],["2019/5","1"],["2019/6","1"],["2019/7","1"],["2019/8","1"],["2019/9","1"],["2019/10","2"],["2019/11","2"],["2019/12","2"],["2019/13","2"],["2019/14","2"],["2019/15","2"],["2019/16","2"],["2019/17","2"],["2019/18","2"],["2019/19","2"],["2019/20","3"],["2019/21","3"],["2019/22","3"],["2019/23","3"],["2019/24","3"],["2019/25","3"],["2019/26","3"],["2019/27","3"],["2019/28","3"],["2019/29","3"],["2019/30","4"],["2019/31","4"],["2019/32","4"],["2019/33","4"],["2019/34","4"],["2019/35","4"],["2019/36","4"],["2019/37","4"],["2019/38","4"],["2019/39","4"],["2019/40","5"]]

Check if dates consecutives in array with starting date today

I am trying to check if in this following array, the dates are consecutives with starting date today (07/01/2020).
var arrayDate = [
'06/30/2020', '06/29/2020', '06/28/2020', '06/26/2020'
]
It should return
var nbDatesConsecutives = 3
On the other hand, this following example should return 0 :
var arrayDate = [
'06/29/2020', '06/28/2020', '06/26/2020', '06/25/2020'
]
I have tried many times to resolve it but I still blocked. Here is one of my attempts :
let arrayDiff = []
arrayDate.map((element, i) => {
arrayDiff.push(today.diff(moment(element), 'days'));
});
let previousValue = null;
arrayDiff.map((element, i) => {
let currentValue = arrayDiff[i];
if (i > 0) {
if (currentValue > previousValue) {
strike++;
}
}
previousValue = currentValue;
})
Thanks !
Your idea of mapping to the day diff is good. Let me build on that:
You could...
Get "today" as the start of the current day
Map the dates to their difference to today, in days
Find the first array index where this difference is no longer equal to the index plus one (since you expect an array like [1, 2, 3, 4] in the perfect case, so e.g. array[2]=2 + 1=3)
This first mismatching index is already your result, except in the case where the whole array has the expected dates, so no index will mismatch - in that case you return the length of the array
Here you can see it working:
function getConsecutive (dates) {
// Note: I hardcoded the date so that the snippet always works.
// For real use, you need to remove the hardcoded date.
// const today = moment().startOf('day')
const today = moment('2020-07-01').startOf('day')
const diffs = dates.map(date => today.diff(moment(date, 'MM/DD/YYYY'), 'days'))
const firstIncorrectIndex = diffs.findIndex((diff, i) => diff !== i + 1)
return firstIncorrectIndex === -1 ? dates.length : firstIncorrectIndex
}
// Outputs 4:
console.log(getConsecutive(['06/30/2020', '06/29/2020', '06/28/2020', '06/27/2020']))
// Outputs 3:
console.log(getConsecutive(['06/30/2020', '06/29/2020', '06/28/2020', '06/26/2020']))
// Outputs 0:
console.log(getConsecutive(['06/29/2020', '06/28/2020', '06/26/2020', '06/25/2020']))
<script src="https://momentjs.com/downloads/moment.js"></script>
The mistake you are doing is 1) currentValue > previousValue instead you should have checked the difference, which must be 1 and when not 1 break the loop. So, here comes the mistake 2) you are using map function rather use simple for loop so that you can break.
`
function getConsecutiveDateCount(arrayDate) {
let arrayDiff = [];
let today = moment();
arrayDate.map((element, i) => {
arrayDiff.push(today.diff(moment(element), 'days'));
});
let strike = 0;
arrayDiff.unshift(0); /// insert 0 for today
let previousValue = arrayDiff[0];
for (let i = 1; i < arrayDiff.length; i++) {
currentValue = arrayDiff[i];
if (currentValue - previousValue === 1) {
strike++;
} else {
break;
}
previousValue = currentValue;
}
return strike;
}
`

Checking if date matches recurring date

I have a date variable containing a date, and an interval variable containing an array of numbers. Each number on the interval array represents a date, which is acquired by adding said number to the day count of the previous date. For example:
If date is equal to 2016-09-01 and interval is equal to [15, 15, 20], the resulting dates would be 2016-09-16, 2016-10-01, 2016-10-21, 2016-11-06 and so on.
I want to check if a given date matches that pattern. To do this, I tried using moment-recur, which does exactly what I want with the .every() function, but intervals with repeating numbers don't seem to work ([15, 15, 20] would be parsed as [15, 20] for example). How can I accomplish this, either with moment-recur or a different library?
Here's the desired output using moment-recur:
const now = moment();
const date = moment("2016-09-10", "YYYY-MM-DD");
console.log(date.recur().every([18, 18, 57]).days().matches(now));
The accumulative nature of what you are trying to do is a little tricky.
There may be a nicer way, but I think this works pretty well so long as your interval list isn't too big.
The main insight is that if you are looking for an accumulative interval of say [2, 10, 11] then you will be looking for every 2, 12, 23, 25, 35, 46, etc. This amount to looking for three different dates at regular intervals of the sum of your accumulator -- in this case 23. So you could just use moment's every() with each of the three cases and a single interval.
For example if you have:
const now = moment();
const date = moment("2016-10-22", "YYYY-MM-DD");
const interval = [18, 18, 57]
// sum of the intervals -- i.e. the main interval (93)
const main_int = interval.reduce( (prev, curr) => prev + curr );
// an array of days for each small interval
const dates = interval.map(i => moment(date.add(i ,'days')))
// moment mutates the original so this results in
// [ moment("2016-11-09T00:00:00.000"),
// moment("2016-11-27T00:00:00.000"),
// moment("2017-01-23T00:00:00.000") ]
// Now see if any of these days matches the main interval
const match = dates.some(d => d.recur().every(main_int).days().matches(now))
In a very kludgy way, what you're trying to do is generate new dates that accumulate according to your sequence, then see if at some point it matches a test date.
The following uses moment.js, but really doesn't need it. The moment functionality could be replaced with about 10 lines of code in a couple of separate functions.
/* #param {Date} sequenceStart - start of sequence
** #param {Array} sequence - sequence of intervals
** #param {Date} date - date for comparison
*/
function inSequence(sequenceStart, sequence, date) {
// Copy start date so don't affect original
var s = moment(sequenceStart);
// Get test date in suitable format
var d = moment(date).format('YYYYMMDD');
var i = 0;
do {
// debug
console.log(s.format('YYYYMMDD') + ' ' + d)
// If dates match, return true
if (s.format('YYYYMMDD') == d) {
return true;
}
// If didn't match, add the next value in the sequence
s.add(sequence[i++ % sequence.length], 'day');
// Stop if go past test date
} while (s.format('YYYYMMDD') <= d)
// If didn't return true, return false
return false;
}
var sequence = [15,15,20];
var start = new Date(2017,8,1); // 1 Sep 2017
var test = new Date(2017,10,5) // 5 Nov 2017
console.log(inSequence(start, sequence, test));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>
You can do this without having to use moment-recur if you want to, using a similar approach to what #Mark_M described
The first step is to determine the number of days between the given date and the start date: moment(endDate).diff(startDate, 'days');.
Then, create an array of cumulative totals for the days since start date for each of the entries in the interval array:
function sumSoFar(nums) {
return nums.reduce((sums, num) => {
const prevSum = last(sums) || 0;
return sums.concat(num + prevSum);
}, []);
}
// ...
const sums = sumSoFar(interval);
Finally, the sum of the whole interval array is just the last entry in that list, and so we can find out which entry in the interval list it matches by taking the days difference modulo interval sum. If that is 0, or an entry in the sums array, then the date matches the interval. If not, then it doesn't.
Here is the complete code I came up with:
const startDate = moment('2016-09-01');
const interval = [15, 15, 20];
const last = (arr) => arr[arr.length - 1];
const sum = (nums) => nums.reduce((acc, num) => acc + num, 0);
function sumSoFar(nums) {
return nums.reduce((sums, num) => {
const prevSum = last(sums) || 0;
return sums.concat(num + prevSum);
}, []);
}
const validDates = [moment('2016-09-16'), moment('2016-10-01'), moment('2016-10-21'), moment('2016-11-05')];
function isValid(startDate, interval, date) {
const days = moment(date).diff(startDate, 'days');
const sums = sumSoFar(interval);
const remainingDays = days % last(sums);
return remainingDays === 0 || sums.indexOf(remainingDays) >= 0;
}
validDates.forEach(d => console.log(isValid(startDate, interval, d)));
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.19.1/moment.min.js"></script>

Getting the average price of OPSkins pricelist via NodeJS

I want to somehow get the average price of every skin in this list
My question arises is how would one use the use dates and the prices to get the average price of 60 days?
In my idea I would parse the JSON for every item/object and get the dates and prices, then somehow loop through every date and add up the prices then divide by the days.
But I suppose my idea doesnt even need the dates but just the prices, though what difference does average and median give? Because I hear people use the word median for pricing rather than average.
const minimumDate = getMinimumDate();
for(let item in list){
let count = 0;
item['average'] = 0;
for(let date in item){
if(parse(date) >= minimumDate){
item.average += date.price;
count++;
}
}
item.average /= count;
}
getMinimumDate is a function that gives you a date based on today less N days, in your case 60 days, implement it.
parse is a function that parse string to a date.
The median is the value in the middle of the highest and the minimum values in a sorted list.
The average it's the most common value.
1,1,1,2,3,4,6
The average is 18 / 7 = 2'....
There are 7 numbers, the median is the number in the position 7/2 + 1, so our median is 2 due to it is in 4th position.
If the list length is pair take two numbers add them and divide:
1,1,2,3,4,6
Take the two middles, 2+3 and divide by 2, so the median is 2.5
Thanks to #jesusgn90 answer I figured it out and made it look like so:
const request = require("request");
var url = 'https://files.opskins.media/file/opskins-static/pricelist/578080.json';
request({
url: url,
json: true
}, (err, res, body) => {
if (!err && res.statusCode === 200) {
var cur = new Date(),
7daysbefore = cur.setDate(cur.getDate() - 7);
var parsed = JSON.parse(JSON.stringify(body));
for (let item in parsed) {
parsed[item]['average'] = 0;
count = 0;
for (date in parsed[item]) {
if(new Date(date) >= cur){
parsed[item].average += parsed[item][date].price;
count++;
}
}
parsed[item].average = Math.floor(parsed[item].average / count);
}
}
fs.writeFile("test.json", JSON.stringify(parsed), function(err) {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});
});

Categories

Resources