immutablejs & merge with new key - javascript

I have the 2 map objects (One is initial state & one from Network ) and I need to merge the intial state with data received from network. However, i keep "hideDetails": true to handle the state in the client side and others from server end.
//initial state
var j = Immutable.fromJS({
"staffs": {
"hasRecievedData": false,
"addingNewStaff": false,
"data": {
"0": {
"name" : null,
"age" : null,
"designation" : null,
"email" : null,
"hideDetails": true
}
}
}
});
// data from network
var m = Immutable.fromJS({
"staffs" : {
"0" : {
"name" : "name1",
"age" : "23",
"designation" : "work1",
"email" : "aliasson#abcd.com"
},
"1" : {
"name" : "name2",
"age" : "22",
"designation" : "work2",
"email" : "aliassson#abcd.com"
}
}
});
I want to merge these two and get the new states as below,
var m = Immutable.fromJS({
"staffs" : {
"hasRecievedData": false,
"addingNewStaff": false,
"0" : {
"name" : "name1",
"age" : "23",
"designation" : "work1",
"email" : "aliasson#abcd.com",
"hideDetails": true
},
"1" : {
"name" : "name2",
"age" : "22",
"designation" : "work2",
"email" : "aliassson#abcd.com",
"hideDetails": true
}
}
});
How can I achieve it using Merge functions?
Update1:
Not sure this is correct or not, however I get the result with this,
f = j.merge({
hasRecievedData: true,
data: m.get("staffs").map(function(x){
return x.set("hideDetails",true);
})
});

From ImmutableJS docs:
var map1 = Immutable.Map({a:1, b:2, c:3, d:4});
var map2 = Immutable.Map({c:10, a:20, t:30});
var obj = {d:100, o:200, g:300};
var map3 = map1.merge(map2, obj);
// Map { a: 20, b: 2, c: 10, d: 100, t: 30, o: 200, g: 300 }

Related

ES6 reduce array of objects with conditions

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: "-" };

How to return the given input values not available in array using mongodb query

I am having one input arrays,EX: let UPID = ["0","1","10"]. i have to check members.regularStudent whether given input values available or not ?, suppose not available means i have to push one array and return the results
My documents:
{
"_id" : "5bb20d7556db6915846da67f",
"members" : {
"regularStudent" : [
"3",
"4"
]
}
},
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"1",
"2"
]
}
}
My Expected Output
[
"0",
"10"
]
My Code:
let UPID = ["0","1","10"]
db.Groups.find(
/*{
"members.regularStudent": { $nin: UPIDs }
}*/
)
.forEach(function(objects){
print(objects)
})
I had updated mycode, kindly see top on my question section, print(objects) means i am having the my objects, based on this variable can you update your answer,
** print(objects) **
{
"_id" : "5bb20d7556db6915846da67f",
"members" : {
"regularStudent" : [
"3",
"4"
]
}
},
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"1",
"2"
]
}
}
You could use map method in combination with filter.
let UPID = ["0","1","10"];
let docs = [{ "_id" : "5bb20d7556db6915846da67f", "members" : { "regularStudent" : [ "3", "4" ] } },
{ "_id" : "5bb20d7556db6915846da55f", "members" : { "regularStudent" : [ "1", "2" ] } }]
let ids = [].concat(...docs.map(elem => elem.members.regularStudent));
console.log(UPID.filter(id => !ids.includes(id)));
Here I use forEach to iterate through the data to get all of the regularStudent data into one array then use filter to filter out the data from UPID array.
const UPID = ["0", "1" , "10"]
let data = [
{
"_id" : "5bb20d7556db6915846da67f",
"members" : {
"regularStudent" : [
"3",
"4"
]
}
},
{
"_id" : "5bb20d7556db6915846da55f",
"members" : {
"regularStudent" : [
"1",
"2"
]
}
}
]
let resularStudents = []
data.forEach(d => {
d.members.regularStudent.forEach(rs => {
resularStudents.push(rs)
})
})
var result = UPID.filter(
function(d) {
return this.indexOf(d) < 0;
},
resularStudents
);
console.log(result);

Underscore sortBy algorithm

I need your help for a little algorithme for my app :
i have an object like this :
var obj = { "response" : [
"candidate" : {
"id":"1",
"price" : 10,
"distance" : 20
},
"candidate" : {
"id":"2"
"price" : 14,
"distance" : 2
},
"candidate" : {
"id":"3",
"price" : 200,
"distance" : 1
}
] }
Which i sort by price like this :
var sortPrice = _(obj.response).sortBy(function(p){
return p.candidate.price
})
It works fine and sort the object (ids) : 1,2,3
Now if candidate has the same price but different distance, i should show first candidate with the same price and the lowest distance :
var obj = { "response" : [
"candidate" : {
"id":"1",
"price" : 10,
"distance" : 20
},
"candidate" : {
"id":"2"
"price" : 10,
"distance" : 2
},
"candidate" : {
"id":"3",
"price" : 200,
"distance" : 1
}
] }
var sorted = _(obj.response).chain().sortBy(function (p) {
return parseInt(p.candidate.price) ;
}).sortBy(function(d){
return parseInt(d.candidate.distance)
}).value();
But it sort me the lowest distance first (ids) : 3(with distance 1), 2(with distance 2), 1(with distance 20) than 2,1,3
Do you have any suggestion?
Thank you.
In pure js you can use sort() like this.
var obj = {
"response": [{
"candidate": {
"id": "1",
"price": 8,
"distance": 20
}
}, {
"candidate": {
"id": "2",
"price": 8,
"distance": 2
}
}, {
"candidate": {
"id": "3",
"price": 200,
"distance": 1
}
}]
}
obj.response.sort(function(a, b) {
return a.candidate.price - b.candidate.price || a.candidate.distance - b.candidate.distance;
})
console.log(obj.response)
Lodash is a fork of underscore that allows you to sort by several properties of the object.
Using it, a solution could be:
_(obj.response).map(_.partial(_.get, _, 'candidate')).sortBy(['price', 'distance']).value();
Here's the fiddle in case you want to play with it.

Javascript - Reduce array with multiple-key objects [duplicate]

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);

trying to make next button in javascript to display json array

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

Categories