Related
I'm stucked in a (in my opinion) complex reduce method.
Given is an array of objects.
const data =
[
{
"key" : "test1",
"value" : 32,
"type" : "OUT"
},
{
"key" : "test1",
"value" : 16,
"type" : "OUT"
},
{
"key" : "test1",
"value" : 8,
"type" : "IN"
},
{
"key" : "test2",
"value" : 32,
"type" : "OUT"
},
{
"key" : "test2",
"value" : 16,
"type" : "IN"
},
{
"key" : "test2",
"value" : 8,
"type" : "OUT"
},
];
I want to get the sum of values of each object grouped by key attribute. There are two type attributes (IN, OUT) where OUT should be interpreted as negative value.
So in the example above, I'm expecting following result object:
//-32 - 16 + 8 = -40
{
"key" : "test1",
"value" : -40,
"type" : "-"
},
//-32 + 16 - 8 = -24
{
"key" : "test2",
"value" : -24,
"type" : "-"
},
I'm grouping the data using the groupBy function of this SO answer.
Now I'm trying to get the sum using reduce with a filter, like in this SO answer.
However, it delivers me the wrong sums (16 and 8) + since I use filter - only one type is considered.
Here is my code:
const data =
[
{
"key" : "test1",
"value" : 32,
"type" : "OUT"
},
{
"key" : "test1",
"value" : 16,
"type" : "OUT"
},
{
"key" : "test1",
"value" : 8,
"type" : "IN"
},
{
"key" : "test2",
"value" : 32,
"type" : "OUT"
},
{
"key" : "test2",
"value" : 16,
"type" : "IN"
},
{
"key" : "test2",
"value" : 8,
"type" : "OUT"
},
];
//group by key
const groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
const grouped = groupBy(data,"key");
for (const [key, value] of Object.entries(grouped))
{
let x = value.filter(({type}) => type === 'OUT')
.reduce((sum, record) => sum + record.value)
console.log(x);
}
//const filtered = grouped.filter(({type}) => type === 'OUT');
console.log(Object.values(grouped));
Question 1:
Why does the reduce give me the wrong sum for type OUT?
Question 2:
Is there a way to consider both types (IN, OUT) without doing the same procedure again?
You can combine the grouping + counting in 1 reduce() if you set the default value to 0, you can always add (or remove) the value from the current key (type)
const data = [{"key" : "test1", "value" : 32, "type" : "OUT"}, {"key" : "test1", "value" : 16, "type" : "OUT"}, {"key" : "test1", "value" : 8, "type" : "IN"}, {"key" : "test2", "value" : 32, "type" : "OUT"}, {"key" : "test2", "value" : 16, "type" : "IN"}, {"key" : "test2", "value" : 8, "type" : "OUT"}, ];
const res = data.reduce((p, c) => {
(p[c['key']] = p[c['key']] || { ...c, value: 0 });
p[c['key']].value =
(c.type === 'IN')
? (p[c['key']].value + c.value)
: (p[c['key']].value - c.value);
return p;
},{});
console.log(res)
Output:
{
"test1": {
"key": "test1",
"value": -40,
"type": "OUT"
},
"test2": {
"key": "test2",
"value": -24,
"type": "OUT"
}
}
I would break this into two problems:
How to reduce each data value (reduce)
How to evaluate existing/new values (switch)
This way your code is less-coupled and it affords you with greater extensibility. Adding a new operator is as simple as adding a new case in the switch.
const reduceValue = (type, existingValue, newValue) => {
switch (type) {
case 'IN' : return existingValue + newValue;
case 'OUT' : return existingValue - newValue;
default : return existingValue; // or throw new Error(`Unsupported type: ${type}`)
}
};
const processValues = (data) =>
data.reduce((acc, { key, type, value }) => {
acc[key] ??= { key, type: '-', value: 0 };
acc[key].value = reduceValue(type, acc[key].value, value);
return acc;
},{});
const testData = [
{ "key" : "test1", "value" : 32, "type" : "OUT" },
{ "key" : "test1", "value" : 16, "type" : "OUT" },
{ "key" : "test1", "value" : 8, "type" : "IN" },
{ "key" : "test2", "value" : 32, "type" : "OUT" },
{ "key" : "test2", "value" : 16, "type" : "IN" },
{ "key" : "test2", "value" : 8, "type" : "OUT" }
];
console.log(processValues(testData))
.as-console-wrapper { top: 0; max-height: 100% !important; }
I would create 2 functions for applying the sign and store them in a variable.
const applySign = { "IN": nr => +nr, "OUT": nr => -nr };
Then do a simple for...of loop (with object destructuring). If there is no running total at the moment for the current key, set the initial value to 0 (using nullish coalescing assignment ??=). Finally add the current value with applied sign to the running total.
const sums = {};
for (const { key, value, type } of data) {
sums[key] ??= 0;
sums[key] += applySign[type](value);
}
const data = [
{ key: "test1", value: 32, type: "OUT" },
{ key: "test1", value: 16, type: "OUT" },
{ key: "test1", value: 8, type: "IN" },
{ key: "test2", value: 32, type: "OUT" },
{ key: "test2", value: 16, type: "IN" },
{ key: "test2", value: 8, type: "OUT" },
];
const applySign = { "IN": nr => +nr, "OUT": nr => -nr };
const sums = {};
for (const { key, value, type } of data) {
sums[key] ??= 0;
sums[key] += applySign[type](value);
}
console.log(sums);
With a few simple tweaks you can change the above in the output you're looking for:
const sums = {};
for (const { key, value, type } of data) {
sums[key] ??= { key, value: 0 };
sums[key].value += applySign[type](value);
}
const expected = Object.values(sums);
This gives you the base answer, though the type properties that you expect are currently missing. To add them you'll have to do another loop and check the final sum result.
for (const sum of expected) {
sum.type = sum.value < 0 ? "-" : "+";
}
const data = [
{ key: "test1", value: 32, type: "OUT" },
{ key: "test1", value: 16, type: "OUT" },
{ key: "test1", value: 8, type: "IN" },
{ key: "test2", value: 32, type: "OUT" },
{ key: "test2", value: 16, type: "IN" },
{ key: "test2", value: 8, type: "OUT" },
];
const applySign = { "IN": nr => +nr, "OUT": nr => -nr };
const sums = {};
for (const { key, value, type } of data) {
sums[key] ??= { key, value: 0 };
sums[key].value += applySign[type](value);
}
const expected = Object.values(sums);
console.log(expected);
// add type based on the value sign (don't know why)
for (const sum of expected) {
sum.type = sum.value < 0 ? "-" : "+";
}
console.log(expected);
If type is a static "-" and was not supposed to depend on the sign of value, then you can add it when you initially create the sum object.
sums[key] ??= { key, value: 0, type: "-" };
I really need Your help! I have a .JSON file that looks something like this:
{
"_id" : { "$yid" : "279e128r" },
"userId" : "245981e3",
"data" : {
"workRate" : 51,
"workStages" : [
{ "quantity" : 33, "value" : "APPLES" },
{ "quantity" : 32, "value" : "PEACH" },
{ "quantity" : 12, "value" : "AVOCADO" },
{ "quantity" : 53, "value" : "APPLES" },
{ "quantity" : 22, "value" : "PEACH" },
{ "quantity" : 27, "value" : "AVOCADO" }
],
"timeInterval" : 25,
"timeSource" : 1
},
"sessionStartTime" : 1494311320,
"sessionEndTime" : 1494336640
}
There are around 500 id ("_id") with 30 participants ("userId").
So I want to take the existing JSON data, and create a new Array that has only 30 Objects ( each object for each participant ) and then find the "data" field and count up the times that vegetables were mentioned, and combine them, and have the number of times that it was mentioned.
{
"userId" : "245981e3",
"data" : {
"workRate" : 51,
"workStages" : [
{ "value" : "APPLES", "times": 2 },
{ "value" : "PEACH", "times": 2 },
{ "value" : "AVOCADO", "times": 2 }
]
}
}
{
"userId" : "7295ew41",
"data" : {
"workRate" : 45,
"workStages" : [
{ "value" : "LEMON", "times": 13 },
{ "value" : "AVOCADO", "times": 42 },
{ "value" : "KIWI", "times": 14 }
]
}
}
I know this sounds difficult, but I believe that there is somebody who will be able to help me out!
This question already has answers here:
Group array items using object
(19 answers)
Closed 6 years ago.
I want to modify a Javascript array, so that the elements having the same values for specifies properties merge into one object in a way that the other properties are kept as a comma-separated string, JSON string, or an array. Basically, I want to turn this:
[
{
"language" : "english",
"type" : "a",
"value" : "value1"
},
{
"language" : "english",
"type" : "a",
"value" : "value2"
},
{
"language" : "english",
"type" : "b",
"value" : "value3"
},
{
"language" : "spanish",
"type" : "a",
"value" : "valor1"
}
]
into this:
[
{
"language" : "english",
"type" : "a",
"value" : ["value1" , "value2"] // A Json string is welcome too
},
{
"language" : "english",
"type" : "b",
"value" : "value3"
},
{
"language" : "spanish",
"type" : "a",
"value" : "valor1"
}
]
I have tried iterating and filtering, then upserted the object as given in the snippet. But I wonder if there is a more elegant way to do that.
P.S EcmaScript6 and additional JS library suggestions are also welcome.
var originalArray = [
{
"language" : "english",
"type" : "a",
"value" : "value1"
},
{
"language" : "english",
"type" : "a",
"value" : "value2"
},
{
"language" : "english",
"type" : "b",
"value" : "value3"
},
{
"language" : "spanish",
"type" : "a",
"value" : "valor1"
}
];
var resultingArray = [];
// iterate through the original array
$.each(originalArray, function(i, val) {
// apply filter on key properties (i.e. language and type)
var result = resultingArray.filter(function( obj ) {
return (obj.language === val.language && obj.type === val.type);
});
// if a record exists, update its value
if (result.length === 1) {
result[0].value += (", " + val.value);
}
// else, add value
else if (result.length === 0) {
resultingArray.push(val);
}
// if multiple rows exist with same key property values...
else {
alert("Too many records with language '" + val.language + "' and type '" + val.type + "'");
}
});
console.log(JSON.stringify(resultingArray));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
This is what you need?
var baseData= [
{
"language" : "english",
"type" : "a",
"value" : "value1"
},
{
"language" : "english",
"type" : "a",
"value" : "value2"
},
{
"language" : "english",
"type" : "b",
"value" : "value3"
},
{
"language" : "spanish",
"type" : "a",
"value" : "valor1"
}
];
var newData = [];
baseData.forEach(function(item, index) {
if (newData.length === 0) {
newData.push(item);
} else {
var dIndex = -1;
newData.forEach(function(itm, idx) {
if (item.language === itm.language && item.type === itm.type) dIndex = idx;
});
if (dIndex !== -1) {
var oldValue = newData[dIndex].value;
if (typeof(oldValue).toString() === 'string') {
newData[dIndex].value = [oldValue, item.value];
}
} else {
newData.push(item);
}
}
});
console.log(newData);
I am trying to display the json array according to certain start and end indices. I did displayed it all but when I click on first button it doesn't work. Here is my code .. after clicking on first button it says "this.data is undefined"
any help ??
function GridLibrary(data) {
this.data = data;
}
GridLibrary.prototype.display = function() {
var html = [];
html.push("<table >\n<tbody>");
html.push("<tr>");
for ( var propertyNames in this.data[0]) {
html.push("<th>" + propertyNames + "</th>");
}
html.push("</tr>");
// loop through the array of objects
for (var i = startIndex; i < endIndex; i++) {
item = this.data[i];
html.push("<tr>");
for ( var key in item) {
html.push("<td>" + item[key] + "</td>");
}
html.push("</tr>");
}
html.push("<table>\n</tbody>");
$('body').append(html.join(""));
};
var grid = new GridLibrary();
$("#first").click(function() {
//size = parseInt(document.getElementById("listsize").value, 10);
startIndex = 3;
endIndex = 6;
grid.display();
});
the Grid.jsp
<script type="text/javascript">
var json = [ {
"id" : 1,
"name" : "name1",
"age" : 10,
"feedback" : "feedback1"
}, {
"id" : 2,
"name" : "name2",
"age" : 90,
"feedback" : "feedback2"
}, {
"id" : 3,
"name" : "name3",
"age" : 30,
"feedback" : "feedback3"
}, {
"id" : 4,
"name" : "name4",
"age" : 50,
"feedback" : "feedback4"
}, {
"id" : 5,
"name" : "name5",
"age" : 55,
"feedback" : "feedback5"
}, {
"id" : 6,
"name" : "name6",
"age" : 68,
"feedback" : "feedback6"
}, {
"id" : 7,
"name" : "name7",
"age" : 57,
"feedback" : "feedback7"
}, {
"id" : 8,
"name" : "name8",
"age" : 89,
"feedback" : "feedback8"
}, {
"id" : 9,
"name" : "name9",
"age" : 65,
"feedback" : "feedback9"
}, {
"id" : 10,
"name" : "name10",
"age" : 54,
"feedback" : "feedback10"
}, {
"id" : 11,
"name" : "name11",
"age" : 51,
"feedback" : "feedback11"
}, {
"id" : 12,
"name" : "name12",
"age" : 97,
"feedback" : "feedback12"
} ];
new GridLibrary(json).display();
</script>
I've made a few assumptions here.
1) Your constructor should accept a parameter which will be your data:
function GridLibrary(data){
this.data = data;
return this;
}
2) This will be used like so:
var grid = new GridLibrary(json);
3) You should then pass in startIndex and endIndex as parameters to display():
grid.display(0, 11);
or
grid.display(3, 6);
and change the method to accept those arguments:
GridLibrary.prototype.display = function(startIndex, endIndex) {...
4) Finally you should change the HTML (not append) to a specific div otherwise you will overwrite your first button:
$('#body').html(html.join(""));
DEMO
Just by looking at your code, it seems like you don't close your table tag properly. The last html.push adds a new <table>, instead of </table>. They are also in the wrong order, it should be </tbody></table>
edit: fixed formatting
I've been trying to use the MapReduce functionaltity in Mongo 2.6.11 to help in returning an array of objects as a value to a corresponding key.
For example, given the following input to a MapReduce job:
{ IncidentNumber : 123, StartLoc : 5, EndLoc : 10, },
{ IncidentNumber : 123, StartLoc : 10, EndLoc : 15, },
{ IncidentNumber : 123, SStartLoc : 10, EndLoc : 15, },
{ IncidentNumber : 321, StartLoc : 0, EndLoc : 5, },
{ IncidentNumber : 321, StartLoc : 10, EndLoc : 20, }
I would like to get output that looks like this:
{ IncidentNumber : 123, Locations : [{StartLoc : 5, EndLoc : 10},{StartLoc : 10, EndLoc : 15}, {StartLoc : 10, EndLoc : 15}],
{ IncidentNumber : 321, Locations : [{StartLoc : 0, EndLoc : 5},{StartLoc : 10, EndLoc : 20}]
My current map and reduce functions for this is as follows:
var mapFunction = function() {
emit(this.IncidentNumber, {StartLoc : this.StartLoc, EndLoc: this.EndLoc} )
}
var reduceFunction = function(key, values) {
var out = []
return out.push(values);
}
This is giving me results that look like:
{ "_id" : 50144, "value" : 1 }
{ "_id" : 68971, "value" : { "startLoc" : 10, "endLoc" : 5} }
{ "_id" : 108294, "value" : 1 }
{ "_id" : 165130, "value" : 1 }
{ "_id" : 194016, "value" : 1 }
{ "_id" : 210018, "value" : 1 }
{ "_id" : 210195, "value" : 1 }
{ "_id" : 212069, "value" : 1 }
I know my reduce function is not correct and I'm not sure if this is an odd use case as I'm not really doing any actual 'reduction'. But I'm not sure exactly what the problem is. Any help would be much appreciated.
Thanks.
After some more searching I found a way of a getting this to work based on this post MongoDB Map/Reduce Array aggregation question. For anyone who is trying to solve the same problem The map/reduce functions I've used are as follows:
var mapFunction = function() {
emit( {
incident: this.IncidentNumber,
} , {
data: [ { start : this.startLoc, end : this.endLoc} ]
} );
}
var reduceFunction = function(key,values) {
var out = [];
values.forEach(function(d){
Array.prototype.push.apply(out, d.data);
});
return { data: out };
}