Process any array without using For Loop - javascript

I want to achieve Output dynamically without using FOR Loop.
Input is array of multiple objects.
var input = [
{
"name": "Siner1",
"RTime": 40,
"FTime": 30
},
{
"name": "Siner2",
"RTime": 50,
"FTime": 60
}
]
var output = [
{
"RTime": {
"Siner1": 40,
"Siner2": 50
},
"FTime": {
"Siner1": 30,
"Siner2": 60
}
}
]
//console.log(input)
console.log(output);
Can someone assist me on this.

Approach 1
var input = [
{
"name": "Siner1",
"RTime": 40,
"FTime": 30
},
{
"name": "Siner2",
"RTime": 50,
"FTime": 60
}
]
var output = input.reduce(function(initial,next){
initial["RTime"][next["name"]]=(initial["RTime"][next["name"]] || 0 ) + next["RTime"];
initial["FTime"][next["name"]]=(initial["FTime"][next["name"]] || 0 ) + next["FTime"];
return initial;
},{"RTime":{},"FTime":{}});
console.log(output)
Approach 2
In the event that there are other keys or values you would like to aggregate
var input = [
{
"name": "Siner1",
"RTime": 40,
"FTime": 30
},
{
"name": "Siner2",
"RTime": 50,
"FTime": 60
}
]
var keys = ['RTime','FTime'];
output=input.reduce(function(initial,next){
keys.map(function(key){
initial[key][next["name"]]=(initial[key][next["name"]] || 0 ) + next[key];
});
return initial;
},keys.reduce(function(data,key){
data[key]={};
return data;
},{}));
console.log(output)

The provided generic approach is based on Array.prototype.reduce. Since it uses the accumulator argument of its callback function as a configurable collector it is totally agnostic about the keys of any processed item of any given list. One just needs to provide the (source) key of a processed item that's value then serves as target key of newly grouped key values ...
function restructureKeysAndValues(collector, item) {
const {
keyOfTargetValue,
registry,
index,
list
} = collector;
const targetKey = item[keyOfTargetValue];
Object.entries(item).forEach(([key, value]) => {
if (key !== keyOfTargetValue) {
let keyGroup = registry[key];
if (!keyGroup) {
keyGroup = registry[key] = { [key]: {} };
Object.assign(index, keyGroup);
list.push(keyGroup);
}
keyGroup[key][targetKey] = value;
}
});
return collector;
}
console.log('a list of group objects ...', [{
"name": "Siner1",
"RTime": 40,
"FTime": 30
}, {
"name": "Siner2",
"RTime": 50,
"FTime": 60
}].reduce(restructureKeysAndValues, {
keyOfTargetValue: 'name',
registry: {}, // - for internal tracking
index: {}, // - for external use, output as map/index.
list: [] // - for external use, output as array/list.
}).list
);
console.log('a single grouped object ...', [{
"name": "Siner1",
"RTime": 40,
"FTime": 30
}, {
"name": "Siner2",
"RTime": 50,
"FTime": 60
}].reduce(restructureKeysAndValues, {
keyOfTargetValue: 'name',
registry: {}, // - for internal tracking
index: {}, // - for external use, output as map/index.
list: [] // - for external use, output as array/list.
}).index
);
console.log('a list of group objects ...', [{
"name": "Siner1",
"RTime": 40,
"FTime": 30,
"CTime": 70,
"ATime": 90
}, {
"name": "Siner2",
"RTime": 50,
"FTime": 60,
"CTime": 30,
"ATime": 40
}, {
"name": "Siner3",
"RTime": 90,
"FTime": 20,
"CTime": 10,
"ATime": 20
}].reduce(restructureKeysAndValues, {
keyOfTargetValue: 'name',
registry: {}, // - for internal tracking
index: {}, // - for external use, output as map/index.
list: [] // - for external use, output as array/list.
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Related

how to get max value from a nested json array

I have a nested json array and I am trying to get the maximum value of the points attribute in this array.
data = {
"name": "KSE100",
"children": [
{
"name": "TECHNOLOGY & COMMUNICATION",
"children": [
{
"name": "TRG",
'points': -21
},
{
"name": "SYS",
},
]
},
{
"name": "OIL",
"children": [
{
"name": "PPL",
'points': 9
},
{
"name": "PSO",
'points': -19
},
]
},
]
}
I want the max value of points from under the children sections. I mean from under technology and oil sectors.
What I've done so far:
var max;
for (var i in data.children.length) {
for (var j in data.data[i]) {
var point = data.data[i].children[j]
}
}
Try the following:
data = {
"name": "KSE100",
"children": [
{
"name": "TECHNOLOGY & COMMUNICATION",
"children": [
{
"name": "TRG",
'points': -21
},
{
"name": "SYS",
},
]
},
{
"name": "OIL",
"children": [
{
"name": "PPL",
'points': 9
},
{
"name": "PSO",
'points': -19
},
]
},
]
}
var array = [];
for (var first of data.children) {
for (var second of first.children) {
if(second.points != undefined)
{
array.push(second);
}
}
}
var maximumValue = Math.max.apply(Math, array.map(function(obj) { return obj.points; }));
console.log(maximumValue);
you can use the reduce method on the array object to do this
const maxValues = []
data.children.forEach(el => {
if (el.name === 'OIL' || el.name === 'TECHNOLOGY & COMMUNICATIO'){
const max = el.children.reduce((current, previous) => {
if (current.points > previous.points) {
return current
}
}, 0)
maxValues.append({name: el.name, value: max.points})
}
})
This will give you an array of the objects with the name and max value.
First you can convert your object to a string through JSON.stringify so that you're able to use a regular expression
(?<=\"points\":)-?\\d*
To matchAll the values preceded by the pattern \"points\": that are or not negative values. After it, convert the result to a array through the spread operator ... and then reduce it to get the max value.
const data = {name:"KSE100",children:[{name:"TECHNOLOGY & COMMUNICATION",children:[{name:"TRG",points:-21},{name:"SYS"}]},{name:"OIL",children:[{name:"PPL",points:9},{name:"PSO",points:-19}]}]};
console.log(
[ ...JSON.stringify(data).matchAll('(?<=\"points\":)-?\\d*')]
.reduce((acc, curr) => Math.max(curr, acc))
)
I wasn't 100% sure, what your exact goal is, so I included a grouped max value and and overall max value with a slight functional approach.
Please be aware that some functionalities are not working in older browsers i.e. flatMap. This should anyways help you get started and move on.
const data = {
name: "KSE100",
children: [
{
name: "TECHNOLOGY & COMMUNICATION",
children: [
{
name: "TRG",
points: -21,
},
{
name: "SYS",
},
],
},
{
name: "OIL",
children: [
{
name: "PPL",
points: 9,
},
{
name: "PSO",
points: -19,
},
],
},
],
};
const maxPointsByGroup = data.children.reduce(
(acc, entry) => [
...acc,
{
name: entry.name,
max: Math.max(
...entry.children
.map((entry) => entry.points)
.filter((entry) => typeof entry === "number")
),
},
],
[]
);
console.log("grouped max:", maxPointsByGroup);
const overallMax = Math.max(
...data.children
.flatMap((entry) => entry.children.flatMap((entry) => entry.points))
.filter((entry) => typeof entry === "number")
);
console.log("overall max:", overallMax);

How to DRY mapping function?

I have a following data:
var jsonData = {
"data": [
{
"id": 1,
"name": "London",
"date": "2018-04-20",
"temp": 15,
"rain": 2,
"wind": 50,
"humidity" : 80,
}
]
};
and this is the mapping part I have currently coded:
var mainContainer = {
temp : jsonData.data.map (a => a.temp),
rain : jsonData.data.map (a => a.rain), // jsonData.data.map (a => ) being repeated
wind : jsonData.data.map (a => a.wind), // jsonData.data.map (a => ) being repeated
humidity: jsonData.data.map (a => a.humidity) // jsonData.data.map (a => ) being repeated
};
console.log(mainContainer);
Is there any way to DRY the code, so I do not repeat mapping function? The only difference is in property name and mapping part.
Create an array of property names to be mapped
var props = ["temp", "rain", "wind", "humidity" ];
Now iterate this props using reduce
var mainContainer = props.reduce( (a,c) => {
a[c] = jsonData.data.map (s => s[c]);
return a;
}, {});
You can use a loop over to the data array and create a new object accordingly by checking the property already exist or not.
var jsonData = {
"data": [
{
"id": 1,
"name": "London",
"date": "2018-04-20",
"temp": 15,
"rain": 2,
"wind": 50,
"humidity" : 80,
},
{
"id": 1,
"name": "London",
"date": "2018-04-20",
"temp": 25,
"rain": 22,
"wind": 40,
"humidity" : 60,
}
]
};
var res = {};
jsonData.data.forEach((obj)=>{
if(res.temp){
res.temp.push(obj.temp);
} else {
res.temp = [obj.temp];
}
if(res.rain){
res.rain.push(obj.rain);
} else {
res.rain = [obj.rain];
}
if(res.wind){
res.wind.push(obj.wind);
} else {
res.wind = [obj.wind];
}
if(res.humidity){
res.humidity.push(obj.humidity);
} else {
res.humidity = [obj.humidity];
}
});
console.log(res);

Group sum and transform json object with values in nested array

I am trying to aggregate and transform the following json :
[
{
"orderId" : "01",
"date" : "2017-01-02T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 12,
"itemQuantity": 10
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 4
}
]
},
{
"orderId": "02",
"date" : "2017-01-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
},
{
"orderId": "03",
"date" : "2017-02-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
}]
into an object that is grouped by itemId, and then aggregated by quantity, and aggregated by total cost (item cost * item quantity for each order) by month. Example:
[
{
"itemId": 100,
"period": [
{
"month": "01/17",
"quantity": 12,
"cost": 130
}
]
},
{
"itemId": 101,
"period": [
{
"month": "01/17",
"quantity": 5,
"cost": 100
},
{
"month": "02/17",
"quantity": 5,
"cost": 100
}
]
},
{
"itemId": 102,
"period": [
{
"month": "01/17",
"quantity": 5,
"cost": 125
},
{
"month": "02/17",
"quantity": 1,
"cost": 25
}
]
}
]
I have a small indention on my desk in which I have been beating my head trying to figure how to do this using native map/reduce or lodash.
You can do like this:
var orders = [{orderId:"01",date:"2017-01-02T06:00:00.000Z",items:[{itemId:100,itemCost:12,itemQuantity:10},{itemId:102,itemCost:25,itemQuantity:4}]},{orderId:"02",date:"2017-01-08T06:00:00.000Z",items:[{itemId:100,itemCost:15,itemQuantity:2},{itemId:101,itemCost:20,itemQuantity:5},{itemId:102,itemCost:25,itemQuantity:1}]},{orderId:"03",date:"2017-02-08T06:00:00.000Z",items:[{itemId:100,itemCost:15,itemQuantity:2},{itemId:101,itemCost:20,itemQuantity:5},{itemId:102,itemCost:25,itemQuantity:1}]}];
// First, map your orders by items
var items = {};
orders.forEach(function(order) {
// set the month of each order
var month = new Date(order.date);
month = ('0' + (month.getMonth() + 1)).slice(-2) + '/' + String(month.getFullYear()).slice(-2);
// for each item in this order
order.items.forEach(function(item) {
// here we already have both keys: "id" and "month"
// then, we make sure they have an object to match
var id = item.itemId;
if (!items[id]) {
items[id] = {};
}
if (!items[id][month]) {
items[id][month] = { cost:0, quantity:0 };
}
// keep calculating the total cost
items[id][month].cost += item.itemCost * item.itemQuantity;
items[id][month].quantity += item.itemQuantity;
});
});
// Now, we format the calculated values to your required output:
var result = Object.keys(items).map(function(id) {
var obj = {
itemId: id,
period: Object.keys(items[id]).map(function(month) {
items[id][month].month = month;
return items[id][month];
}),
};
return obj;
});
console.log(result);
Hope it helps.
You could use this transformation:
const result = Object.values(myList.reduce( (acc, o) => {
const month = o.date.substr(5,2) + '/' + o.date.substr(2,2);
return o.items.reduce ( (acc, item) => {
const it = acc[item.itemId] || {
itemId: item.itemId,
period: {}
},
m = it.period[month] || {
month: month,
quantity: 0,
cost: 0
};
m.cost += item.itemCost * item.itemQuantity;
m.quantity += item.itemQuantity;
it.period[month] = m;
acc[item.itemId] = it;
return acc;
}, acc);
}, {})).map( o =>
Object.assign({}, o, { period: Object.values(o.period) })
);
const myList = [
{
"orderId" : "01",
"date" : "2017-01-02T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 12,
"itemQuantity": 10
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 4
}
]
},
{
"orderId": "02",
"date" : "2017-01-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
},
{
"orderId": "03",
"date" : "2017-02-08T06:00:00.000Z",
"items" : [
{
"itemId": 100,
"itemCost": 15,
"itemQuantity": 2
},
{
"itemId": 101,
"itemCost": 20,
"itemQuantity": 5
},
{
"itemId": 102,
"itemCost": 25,
"itemQuantity": 1
}
]
}];
const result = Object.values(myList.reduce( (acc, o) => {
const month = o.date.substr(5,2) + '/' + o.date.substr(2,2);
return o.items.reduce ( (acc, item) => {
const it = acc[item.itemId] || {
itemId: item.itemId,
period: {}
},
m = it.period[month] || {
month: month,
quantity: 0,
cost: 0
};
m.cost += item.itemCost * item.itemQuantity;
m.quantity += item.itemQuantity;
it.period[month] = m;
acc[item.itemId] = it;
return acc;
}, acc);
}, {})).map( o =>
Object.assign({}, o, { period: Object.values(o.period) })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think the other answers out there do a pretty good job from the vanilla angle, so I wanted to take a stab at a more lodash-intensive approach since you mentioned it as a tag. This is mainly just a fun challenge, but I hope the solution is elegant enough for you to lift components from.
Before we begin, I'll be using both the vanilla lodash module and the functional programming flavor of lodash. Let fp be the functional programming module and _ be vanilla (and let orders be your original data structure). Also, as a challenge, I'll do my best to minimize vanilla JS methods and arrow funcs to maximize lodash methods and function creation methods.
First, let's get all the items in a row, paired with their order information:
const items = _.flatMap(orders, o=> _.map(o.items, i=> [i, o]));
I know I said I wanted to minimize arrow functions, but I couldn't think of any other way to get the order object to the end of the chain. Challenge yourself to rewrite the above in terms of a composition (e.g. fp.compose or _.flow) and see what happens.
I'd say now's as good a time as any to group up our pairs by the item id:
const id_to_orders = _.groupBy(items, fp.get('[0].itemId'));
Here, fp.get('[0].itemId') gives us a function which, given an array, returns the itemId of the first element (in our case, we have a list of pairs, the first element of which is the item, the second of which is the relevant order object). Therefore, id_to_orders is a map from an item's ID to a list of all the times it was ordered.
This id_to_orders map looks pretty close to the data structure we're after. At a high level, all that's left is transforming the order data for each item into the quantity and cost, grouped by month.
const result = _.mapValues(id_map, fp.flow(
// Arrange the item's orders into groups by month
fp.groupBy(month)
// We're done with the order objects, so fp.get('[0]') filters them
// out, and the second function pairs the item's cost and quantity
, fp.mapValues(fp.flow(
fp.map(fp.flow(fp.get('[0]'), i=> [i.itemCost, i.itemQuantity]))
// Sum up the cost (left) and quantity (right) for the item for the month
, fp.reduce(add_pair, [0, 0])))
// These last couple lines just transform the resulting data to look
// closer to the desired structure.
, _.toPairs
, fp.map(([month, [cost, count]])=> ({month, cost, count}))
));
And the helpers month and add_pair referenced above:
function month([item, order]){
const date = new Date(order.date)
, month = date.getMonth() + 1
, year = date.getFullYear().toString().slice(-2);
return `${month}/${year}`;
}
function add_pair(p1, p2){
return [p1[0] + p2[0], p1[1] + p2[1]];
}
Just out of curiosity (or sadism), let's see what this whole thing would look like chained together as a single pipeline:
const get_order_data = fp.flow(
fp.flatMap(o=> _.map(o.items, i=> [i, o]))
, fp.groupBy(fp.get('[0].itemId'))
, fp.mapValues(fp.flow(
fp.groupBy(month)
, fp.mapValues(fp.flow(
fp.map(fp.flow(fp.get('[0]'), i=> [i.itemCost, i.itemQuantity]))
, fp.reduce(add_pair, [0, 0])))
, _.toPairs
, fp.map(([month, [cost, count]])=> ({month, cost, count})))
));
const result = get_order_data(orders);
You'll notice this composed version has a lot more fp (as opposed to _). If you're curious why it's easier this way, I encourage you to read the lodash FP guide.
jsfiddle with everything.
Finally, if you'd like to transform the result from the code above exactly into the output format you mentioned in your post, here's what I recommend:
const formatted = _.keys(result).map(k=> ({itemId: k, periods: result[k]}));

Javascript code to split JSON data into two datapoints array to bind with stackedbar chart canvasjs?

I have variable data having json data as below:
[
{
"BillingMonth":"11",
"BillingYear":"2016",
"Volume":"72",
"BillingMonthName":"November",
"BillingProduct":"Product1"
},
{
"BillingMonth":"11",
"BillingYear":"2016",
"Volume":"617",
"BillingMonthName":"November",
"BillingProduct":"Product2"
},
{
"BillingMonth":"12",
"BillingYear":"2016",
"Volume":"72",
"BillingMonthName":"December",
"BillingProduct":"Product1"
},
{
"BillingMonth":"12",
"BillingYear":"2016",
"Volume":"72",
"BillingMonthName":"December",
"BillingProduct":"Product2"
}
]
What I want to split above json data using javascript/jquery and get them stored in two variables data1, data2 having json data as below as result:
{
type: "stackedBar",
legendText: "Product1",
showInLegend: "true",
data1: [
{ x: November, y: 72 },
{ x: December, y: 72 },
]
}
and
{
type: "stackedBar",
legendText: "Product2",
showInLegend: "true",
data2: [
{ x: November, y: 617 },
{ x: December, y: 72 },
]
}
The above will bind in canvas js stackedbar chart.
Thanks!
Hey here's a solution I had a lot of fun working on I hope it works well for you. I wasn't sure if you would always have 2 products product1, product2 so I went with a more general approach for n amount of products. The result is in an array format, but you can use es6 destructuring to get the two variables data1 and data2 like I did below:
/*
* helper function to restructure json in the desired format
*/
function format(obj) {
var formatted = {
"type": "stackedBar",
"legendText": obj.BillingProduct,
"showInLegend": "true",
"data": [{
"x": obj.BillingMonthName,
"y": obj.Volume
}]
}
return formatted;
}
/*
* returns an array of unique products with corresponding BillingMonth/Volume data
*/
function getStackedBarData(data) {
// transform each obj in orignal array to desired structure
var formattedData = data.map(format);
// remove duplicate products and aggregate the data fields
var stackedBarData =
formattedData.reduce(function(acc, val){
var getProduct = acc.filter(function(item){
return item.legendText == val.legendText
});
if (getProduct.length != 0) {
getProduct[0].data.push(val.data[0]);
return acc;
}
acc.push(val);
return acc;
}, []);
return stackedBarData;
}
var data = [{
"BillingMonth": "11",
"BillingYear": "2016",
"Volume": "72",
"BillingMonthName": "November",
"BillingProduct": "Product1"
}, {
"BillingMonth": "11",
"BillingYear": "2016",
"Volume": "617",
"BillingMonthName": "November",
"BillingProduct": "Product2"
}, {
"BillingMonth": "12",
"BillingYear": "2016",
"Volume": "72",
"BillingMonthName": "December",
"BillingProduct": "Product1"
}, {
"BillingMonth": "12",
"BillingYear": "2016",
"Volume": "72",
"BillingMonthName": "December",
"BillingProduct": "Product2"
}]
var dataVars = getStackedBarData(data);
var data1 = dataVars[0];
var data2 = dataVars[1];
console.log(data1);
console.log(data2);
Hope this helps you!

Combining 2 jsons objects

I've 2 json object that I would like to combine. I tried using concat and merge function, but the result is not what I want. Any help would be appreciated.
var jason1 =
{
"book1": {
"price": 10,
"weight": 30
},
"book2": {
"price": 40,
"weight": 60
}
};
and this is the other object
var jason2 =
{
"book3": {
"price": 70,
"weight": 100
},
"book4": {
"price": 110,
"weight": 130
}
};
This is what I want:
var jasons =
{
"book1": {
"price": 10,
"weight": 30
},
"book2": {
"price": 40,
"weight": 60
}
"book3": {
"price": 70,
"weight": 100
},
"book4": {
"price": 110,
"weight": 130
}
};
See the source of the Object.extend method from the Prototype.js framework:
https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/object.js#L88
function extend(destination, source) {
for (var property in source) {
destination[property] = source[property];
}
}
The usage is then…
extend(jason1, jason2);
The object jason1 now contains exactly what you want.
You jsut need to manually iterate over them:
var both = [json1, json2],
jasons = {};
for (var i=0; i < both.length; i++) {
for (var k in both[i]) {
if(both[i].hasOwnProperty(k)) {
jasons[k] = both[i][k];
}
}
}
Heres a working fiddle. You might want to think about what happens if there are duplicate keys though - for example what if book3 exists in both json objects. With the code i provided the value in the second one always wins.
Here's one way, although I'm sure there are more elegant solutions.
var jason1 = {
"book1": {
"price": 10,
"weight": 30
},
"book2": {
"price": 40,
"weight": 60
}
};
var jason2 = {
"book3": {
"price": 70,
"weight": 100
},
"book4": {
"price": 110,
"weight": 130
}
};
var jasons = {};
var key;
for (key in jason1) {
if (jason1.hasOwnProperty(key) && !(jasons.hasOwnProperty(key))) {
jasons[key] = jason1[key];
}
}
for (key in jason2) {
if (jason2.hasOwnProperty(key) && !(jasons.hasOwnProperty(key))) {
jasons[key] = jason2[key];
}
}
console.log(jasons);

Categories

Resources