Find sum of objects inside array using reduce - node.js

I am trying to find the total from objects inside an array, which each object has a price and quantity,
i can find the total when the array has exactly two objects, but for more than two it produces NaN.
arr = [ { quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 } ]
const reducer = (accumulator, currentValue) => {
var a = accumulator.quantity * accumulator.price;
var b = currentValue.quantity * currentValue.price;
return a + b;
}
console.log(arr.reduce(reducer)); // sum if array contains 2 objects, NaN otherwise.

let arr = [
{ quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 }
]
let reducer = (acc, cur) => {
return acc + (Number(cur.quantity) * Number(cur.price));
};
console.log(arr.reduce(reducer, 0));
// 100
Your reducer function seems to be wrong. Accumulator no longer has any parameters to it, since well, it accumulates - its an integer.
Also, set a initial value for your accumulator to start accumulating from, as shown in the reduce function, second parameter input

arr = [ { quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 } ]
const reducer = (accumulator, currentValue) {
return accumulator + (currentValue.quantity * accumulator.price);
}
console.log(arr.reduce(reducer, 0 ));

you can simply say
arr = [ { quantity: 1, price: 30 },
{ quantity: 1, price: 40 },
{ quantity: 2, price: 10 },
{ quantity: 1, price: 10 } ]
const total = arr.reduce((total,item)=>{
total += item.quantity * item.price;
return total
},0)

Related

MongoDb multiply aggregate returns null sometimes

I have this aggregate:
const dpi = (imgSize.height * imgSize.width) / (printDpi * printDpi);
let printSizes = await printSizeModel
.aggregate([
{
$project: {
id: 1,
width: 1,
height: 1,
price: 1,
shippingWidth: 1,
shippingHeight: 1,
shippingLength: 1,
shippingWeight: 1,
framePrice: 1,
hasFrame: 1,
total: { $multiply: ['$width', '$height'] },
},
},
{ $match: { total: { $lt: dpi } } },
])
.exec();
Width and height are both numbers and DPi is number as well (DPI is float and width and height are int)
I am using mongoos and Nodejs. This aggregate sometimes returns correct result and sometimes returns null. Based on my understanding this should be correct, but I might be missing something in here
After using Math.ceil to convert the number to Int the issue went away, so I can say that the issue was the float number:
const dpi = Math.ceil((imgSize.height * imgSize.width) / (printDpi * printDpi));
let printSizes = await printSizeModel
.aggregate([
{
$project: {
id: 1,
width: 1,
height: 1,
price: 1,
shippingWidth: 1,
shippingHeight: 1,
shippingLength: 1,
shippingWeight: 1,
framePrice: 1,
hasFrame: 1,
total: { $multiply: ['$width', '$height'] },
},
},
{ $match: { total: { $lt: dpi } } },
])
.exec();

How can a file created on a node express temp folder be exported properly via http res.sendFile(...) or res.download(...)

The problems is the excel file I create in a node/express app, downloads the file with zero bytes via http. What should I be doing to download the file with data?
I have a node/express code that generates an excel file and saves it to a temporary folder. Ultimately I intend to move this code to google cloud functions once it works. The problems is, even though, the excel file does get created with data (as I can see it in the temp folder), the res.download(filename) via http downloads a file with zero bytes. What should I be doing to download the file with data?
Sample code follows below. Please ignore significance of sample data, commented code and console.logs
// import * as functions from 'firebase-functions';
import * as Excel from 'exceljs';
import * as express from 'express';
import * as os from 'os';
import * as path from 'path';
const app = express();
const tempFilePath = path.join(os.tmpdir(), "excel.xlsx");
interface Country {
id: number;
country: string;
capital: string;
population: number;
}
interface Heading {
header: string;
key: string;
width: number;
}
interface Align { col: string; align: string; }
function addBottomBorder(worksheet: any, cols: number, rowNo: number) {
for (let i = 0; i < cols; i++) {
const col = String.fromCharCode(65 + i) + rowNo;
worksheet.getCell(col).border = {
bottom: {style:'thin'},
};
}
}
function hiliteRow(worksheet: any, row: number, color: string, cols: number) {
for (let i = 0; i < cols; i++) {
const col = String.fromCharCode(65 + i) + row;
worksheet.getCell(col).fill = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: color }
};
}
}
function createHeadings(worksheet: any, headings: Heading[]) {
worksheet.columns = headings;
hiliteRow(worksheet, 1, '808B96', headings.length);
addBottomBorder(worksheet, headings.length, 1);
// add filters
const lastColumn = String.fromCharCode(64 + headings.length);
worksheet.autoFilter = { from: 'A1', to: `${lastColumn}1`, };
}
async function generate(data: Country[], headings: Heading[], sheetname: string, alignments: Align[]) {
const workbook = new Excel.Workbook();
const worksheet: any = workbook.addWorksheet(sheetname);
createHeadings(worksheet, headings);
for (const alignObj of alignments) {
worksheet.getColumn(alignObj.col).alignment = { vertical: 'middle', horizontal: alignObj.align };
}
data.forEach((r: any , i: number) => {
worksheet.addRow(r);
if (i % 2 !== 0) {
hiliteRow(worksheet, i + 2, 'D6DBDF', headings.length)
}
});
addBottomBorder(worksheet, headings.length, data.length + 1);
console.log(tempFilePath);
workbook.xlsx.writeFile(tempFilePath).then( result => {
console.log('...ready', result)
return tempFilePath;
})
.catch( err => {
return err;
})
}
app.get('/', async (req, res) => {
const alignments = [
{ col: 'A', align: 'left'},
{ col: 'D', align: 'right'},
];
const columns = [
{ header: 'ID', key: 'id', width: 4 },
{ header: 'Country', key: 'country', width: 10 },
{ header: 'Capital', key: 'capital', width: 22 },
{ header: 'Population', key: 'population', width: 9 }
];
const data = [{
id: 1,
country: 'USA',
capital: 'Washington DC',
population: 325
}, {
id: 2,
country: 'UK',
capital: 'London',
population: 66
}, {
id: 3,
country: 'Italy',
capital: 'Rome',
population: 60.59
}, {
id: 4,
country: 'China',
capital: 'Beijing',
population: 1386
}, {
id: 5,
country: 'Canada',
capital: 'Ottawa',
population: 36.7
}, {
id: 6,
country: 'UK',
capital: 'London',
population: 66
}, {
id: 7,
country: 'Italy',
capital: 'Rome',
population: 60.59
}, {
id: 8,
country: 'China',
capital: 'Beijing',
population: 1386
}, {
id: 9,
country: 'Canada',
capital: 'Ottawa',
population: 36.7
}
];
const sheetname = 'countries';
try {
generate(data, columns, sheetname, alignments).then( notImportant => {
console.log(notImportant);
// !!!!!!!!! - a file with zero bytes is downloaded
// !!!!!!!!! - same result with res.sendFile(...)
res.status(200).download(tempFilePath);
})
} catch (error) {
res.status(500).send(error);
}
})
app.listen(3001, () => {
console.log('...listening')
})
// exports.getExcel = functions.https.onRequest(app);
The solution for me to this issue was to make 2 endpoints - 1s to generate the file, and a second to download the file

How to sum specific dictionary value in nodejs

I am working on nodejs. How to sum of amount which has the dictionary key value of Senior from the list dictionary?
traveler = [
{ description: 'Senior', Amount: 50},
{ description: 'Senior', Amount: 50},
{ description: 'Adult', Amount: 75},
{ description: 'Child', Amount: 35},
{ description: 'Infant', Amount: 25 },
];
expected result = {"senior": 100, "Adult":75}
You can use a reduce function like the following:
traveler.reduce((prevValue, t) => {
return Object.assign(prevValue, { [t.description]: (prevValue[t.description] || 0) + t.Amount })
}, {});
Read about reduce function here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

Traverse Tree in Nodejs with Promises

Given a simple user tree:
users:
id | name | parent_id
1 | Bob | NULL
2 | Jan | 1
3 | Mat | 2
4 | Irene | 2
5 | Ellie | 2
6 | Laura | 5
7 | Uma | 6
I am trying to walk it with this code:
addChildren: function(db, treeUsers, parentId) {
const main = this;
var sql = Sql.select().from('users')
.where('parent_id = ?', parentId)
.toParam();
return db.execute(sql.text, sql.values)
.then(function([rs,meta]) {
rs.map(function(obj) {
treeUsers[obj.id] = obj;
});
return treeUsers;
});
},
getTreeUsers: function(db, depth) {
const main = this;
const rootNodeId = 1;
var treeUsers = {};
const getChildren = function(parentId) {
return main.addChildren(db, treeUsers, parentId).then(function(children) {
treeUsers = Object.assign(treeUsers, children);
--depth;
if(depth < 1) {
return Promise.resolve(treeUsers);
}
return Promise.all(Object.keys(children).map(function(id) {
return getChildren(id);
}));
};
return getChildren(rootNodeId);
});
The desired output would like this:
{
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
}
Right now I'm getting an more like this:
[[ {
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
} ],
[ {
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
} ],
[ {
'2': { id: 2, name: 'Jan' , parent_id: 2 },
'3': { id: 3, name: 'Mat' , parent_id: 2 },
'4': { id: 4, name: 'Irene', parent_id: 2 },
'5': { id: 5, name: 'Ellie', parent_id: 2 },
'6': { id: 6, name: 'Laura', parent_id: 5 },
'7': { id: 7, name: 'Uma' , parent_id: 6 }
} ] ... ]
I'm positive that the problem is the Promise.all(getChildren(..)) call, but I'm not sure how to refactor so that I get back exactly the one final result set.
Some issues:
You mutate the treeUsers object in both methods, which means that the object you resolve with, will still be mutated later. Note how you only have one instance of that object for one call of getTreeUsers. Note that the assignment in:
treeUsers = Object.assign(treeUsers, children);
does not do anything, as Object.assign already mutates the first argument anyway.
Promise.all resolves to an array value, where each element contains the treeUsers object. As it was mutated by all separate calls, all these array entries point to the same object reference, explaining the repetitions you see
depth is a variable common to each call to getChildren. It would be safer to pass it as argument to be sure that siblings are expanded with the same value for depth
I would just avoid passing the treeUsers object around, as it should really be just the resolved value. Let each promise resolve to the children it can "see" and consolidate the results after each Promise.all promise resolves.
Here is how that could look:
addChildren: function(db, treeUsers, parentId) {
const main = this,
sql = Sql.select().from('users')
.where('parent_id = ?', parentId)
.toParam();
return db.execute(sql.text, sql.values).then(function([rs,meta]) {
// Create a new(!) object and return it
return Object.assign(...rs.map(function(obj) {
return { [obj.id]: obj };
}));
});
},
getTreeUsers: function(db, depth) {
const main = this,
rootNodeId = 1;
const getChildren = function(parentId, depth) { // pass depth
return main.addChildren(db, parentId).then(function(children) {
if (depth <= 1) {
return children; // only the children. No need for Promise.resolve
}
return Promise.all(Object.keys(children).map(function(id) {
return getChildren(id, depth - 1); // pass depth
})).then(arr => Object.assign(children, ...arr)); // consolidate
});
}
return getChildren(rootNodeId, depth);
}
Yes, Promise.all always returns an array of all the result values. When your calls all return promises that fulfill with the same value, treeUsers, you get an array of those objects. As you always want to make getChildren return always just that object, you can simplify to
function getChildren(parentId) {
return main.addChildren(db, treeUsers, parentId).then(function(children) {
Object.assign(treeUsers, children);
// when `children` is empty, the recursion terminates here
return Promise.all(Object.keys(children).map(getChildren)).then(() =>
treeUsers
);
});
}
(Of course this does not produce a tree structure)

Building multi level menu using nodejs

I currently have the following data in my database
The Mongo database stores like this
id parent
1 0
2 0
3 1
4 1
5 2
6 2
7 2
30 3
31 3
70 7
71 7
Now I want the output in a single javascript array like so using nodejs
[
{id:1,sub:[
{id:3, sub:[{id:30},{id:31}]},
{id:4,sub:[]}
]
},
{id:2,sub:[
{id:5,sub: []},
{id:6,sub: []},
{id:7,sub: [{id:70}, {id:71}]}
]
}
]
The purpose of this is basically to output the category in a megamenu.
The following example shows a way to do what you want.
// Example data from the question
var nodes = [
{ id: 1, parent: 0 },
{ id: 2, parent: 0 },
{ id: 3, parent: 1 },
{ id: 4, parent: 1 },
{ id: 5, parent: 2 },
{ id: 6, parent: 2 },
{ id: 7, parent: 2 },
{ id: 30, parent: 3 },
{ id: 31, parent: 3 },
{ id: 70, parent: 7 },
{ id: 71, parent: 7 }
];
// We construct `t`, the array of parents, so that `t[i] === x` means that `x`
// is the parent of `i`
var t = [];
for (var i = 0; i < nodes.length; i++) {
t[nodes[i].id] = nodes[i].parent;
}
// `t` represents the array of parents
// `c` represents the parent whose children should be put in the outputted array
function f(t, c) {
// The output structure
var a = [];
// We loop through all the nodes to fill `a`
for (var i = 0; i < t.length; i++) {
// If the node has the parent `c`
if (t[i] === c) {
// Create an object with the `id` and `sub` properties and push it
// to the `a` array
a.push({
id: i,
// The `sub` property's value is generated recursively
sub: f(t, i)
});
}
}
// Finish by returning the `a` array
return a;
}
// Print the outputted array in a pretty way
// We call the `f` function with the 0 parameter because 0 is the parent of the
// nodes that should be directly put in the returned array
alert(JSON.stringify(f(t, 0)));
On Node.js 0.12.13 running this code instead of the alert at the end of the above snippet:
var util = require('util');
console.log(util.inspect(f(t, 0), {
colors: true,
depth: null
}));
prints the following:
[ { id: 1,
sub:
[ { id: 3, sub: [ { id: 30, sub: [] }, { id: 31, sub: [] } ] },
{ id: 4, sub: [] } ] },
{ id: 2,
sub:
[ { id: 5, sub: [] },
{ id: 6, sub: [] },
{ id: 7, sub: [ { id: 70, sub: [] }, { id: 71, sub: [] } ] } ] } ]
which I think is what you want.
I also read this page where another solution, possibly more efficient, is described.

Resources