Compare Nested Objects and save difference in new object using Javascript - javascript

I have two Javascript objects
var order1 = {
sandwich: 'tuna',
chips: true,
drink: 'soda',
order: 1,
toppings: [{VendorNumber: 18, PreferredFlag: false, SupportedFlag: true}, {VendorNumber: 19, PreferredFlag: false, SupportedFlag: true}, {VendorNumber: 20, PreferredFlag: false, SupportedFlag: true}],
details: {
name: 'Chris',
phone: '555-555-5555',
email: 'no#thankyou.com'
},
otherVal1: '1'
};
var order2 = {
sandwich: 'turkey',
chips: true,
drink: 'soda',
order: 2,
toppings: [{VendorNumber: 18, PreferredFlag: false, SupportedFlag: true}, {VendorNumber: 19, PreferredFlag: false, SupportedFlag: false}, {VendorNumber: 20, PreferredFlag: true, SupportedFlag: true}],
details: {
name: 'Jon',
phone: '(555) 555-5555',
email: 'yes#please.com'
},
otherVal1: '2'
};
What I need is to compare these two objects (order1 is existing and order2 is the edited data) and store the difference in a new variable named var order3. However if there is an array inside an object like the toppings array to be copied as whole with the changes.
In short the result should be
{
details: {
email: "yes#please.com",
name: "Jon",
phone: "(555) 555-5555"
},
order: 2,
otherVal1: "2",
sandwich: "turkey",
toppings: [{
PreferredFlag: false,
SupportedFlag: true,
VendorNumber: 18
}, {
PreferredFlag: false,
SupportedFlag: false,
VendorNumber: 19
}, {
PreferredFlag: true,
SupportedFlag: true,
VendorNumber: 20
}]
}
How can i achieve this ?

This gives you exactly what you wanted:
function diff(tgt, src) {
if (Array.isArray(tgt)) { // if you got array
return tgt; // just copy it
}
// if you got object
var rst = {};
for (var k in tgt) { // visit all fields
if (typeof src[k] === "object") { // if field contains object (or array because arrays are objects too)
rst[k] = diff(tgt[k], src[k]); // diff the contents
} else if (src[k] !== tgt[k]) { // if field is not an object and has changed
rst[k] = tgt[k]; // use new value
}
// otherwise just skip it
}
return rst;
}
console.log(diff(order2, order1));

I think you are looking for a diff'ing algorithm. Wrote this quick recursive function that iterates over each enumerable property of your JavaScript object (not json object) testing equality. Note that the position of arguments does affect the output
function diff(obj1, obj2) {
if (typeof obj1 === "object") {
const obj = {};
for (const prop in obj1) {
if (diff(obj1[prop], obj2[prop])) {
obj[prop] = obj1[prop]
}
}
return obj
} else {
return obj1 !== obj2;
}
}
console.log(diff(order2, order1))

Related

How to sort array of objects with boolean values: true, false and null

Hi i have an array of objects that i want to sort based on a boolean that one of the objects has. However normally there would be either true or false but in this case we also check on null values because sometimes the data has not been set and in that case we wanna show that it has yet to be set with an icon.
Here's an example of the array:
const arrayOfObjects = [
{
id: 69,
boolean: true,
name: 'foo',
},
{
id: 42,
boolean: false,
name: 'bar',
},
{
id: 666,
boolean: null,
name: 'foo',
},
{
id: 420,
boolean: false,
name: 'bar',
},
{
id: 2,
boolean: null,
name: 'foo',
},
{
id: 123,
boolean: true,
name: 'foo',
},
]
So what i tried first was:
arrayOfObjects.sort((a, b) => b.boolean - a.boolean);
This sets the objects that are true at the front but the objects with false or null are scattered.
Then i tried:
arrayOfObjects.sort((a, b, c) => (c.boolean - b.boolean) - a.boolean);
This just didn't work at all.
I couldn't really find a case that was similar enough to base a solution off of it so hopefully i can find it here.
If you like to use a custom sorting, you could take an object with the wanted sorting, like
const
order = { true: 1, null: 2, false: 3 };
data = [{ id: 69, boolean: true, name: 'foo' }, { id: 42, boolean: false, name: 'bar' }, { id: 666, boolean: null, name: 'foo' }, { id: 420, boolean: false, name: 'bar' }, { id: 2, boolean: null, name: 'foo' }, { id: 123, boolean: true, name: 'foo' }];
data.sort((a, b) => order[a.boolean] - order[b.boolean]);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you have unknown values and want to move them to bottom, you could add another key with a large value, like
order = { true: 1, null: 2, false: 3, bottom: Number.MAX_VALUE };
Usage:
data.sort((a, b) =>
(order[a.boolean] || order.bottom) - (order[b.boolean] || order.bottom)
);
You can check for the null explicitly ...
let list = [{i: 0, boolean: true}, { i: 1, boolean: null}, { i:2, boolean: false}, { i: 4, boolean: true}]
function cpBoolWithNull(a,b) {
//if both are null return 0 to maintain a stable sort
//if only one is null return 0 or 1 depending on the value of the other
if (a.boolean === null) return b.boolean === null ? 0 : b.boolean ? 1 : -1;
if (b.boolean === null) return a.boolean ? -1 : 1;
//if both are different from null, sort true before false
return b.boolean - a.boolean
}
console.log(list.sort(cpBoolWithNull));
This will sort true ... null ... false If you need a differnt order, adjust the return values.
I think that you can have a type checker with JS with this simple script.
let array =[true, false, null];
function check(i){
if (array[i] != null||array[i]!=false){
if (array[i]!=null || array[i]!=true)document.write(" Array item"+" "+i+" "+"has the value of boolean false. ");
if (array[i]!=true||array[i]!=false)document.write(" Array item"+" "+i+" "+"has the value of boolean true. ");
if (array[i] != true || array[i] != false )document.write(" Array item"+" "+i+" "+"has the value of object null. ");
document.write("<br>")
}
}
check(0);
You can comment out the other text when it is not needed.

merge objects in same array based on common value in Javascript [duplicate]

This question already has answers here:
JavaScript merging objects by id [duplicate]
(18 answers)
Closed 3 years ago.
I have an array:
[
{
assignmentId:17,
email:"john.smith#email.com"
expectation: "Make sure to proofread!",
firstName:"John"
id:23
ignoreForFeedback: true
lastName:"Smith"
level:2
levelFraction:null
score:35
},
{
assignmentId:17
countsPerCategory: Array(4)
email:"john.smith#email.com"
firstName:"John"
frequentErrors: Array(5)
id:23
ignoreForGrading: true
lastName:"Smith"
},
{
assignmentId:17,
email:"cl#email.com"
expectation: "cite sources",
firstName:"Cindy"
id:45
ignoreForFeedback: true
lastName:"Lee"
level:2
levelFraction:null
score:32
},
{
assignmentId:17
countsPerCategory: Array(4)
email:"cl#email.com"
firstName:"Cindy"
frequentErrors: Array(5)
id:45
ignoreForGrading: true
lastName:"Lee"
}
]
I want to combine the Objects with the same 'id' into the same object within the array. Their common keys should also be combined (eg: 'firstName', 'email'). Can someone suggest the best way to do this? Either with ES6 or Lodash
You can use lodash#groupBy to group all items in the array by id and then use lodash#map with an iteratee of lodash#assign that is wrapped with a lodash#spread to make the array callback as a list of arguments for lodash#assgin.
var result = _(array)
.groupBy('id')
.map(_.spread(_.assign))
.value();
var array = [
{
assignmentId:17,
email:"john.smith#email.com",
expectation: "Make sure to proofread!",
firstName:"John",
id:23,
ignoreForFeedback: true,
lastName:"Smith",
level:2,
levelFraction:null,
score:35
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"john.smith#email.com",
firstName:"John",
frequentErrors: Array(5),
id:23,
ignoreForGrading: true,
lastName:"Smith"
},
{
assignmentId:17,
email:"cl#email.com",
expectation: "cite sources",
firstName:"Cindy",
id:45,
ignoreForFeedback: true,
lastName:"Lee",
level:2,
levelFraction:null,
score:32
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"cl#email.com",
firstName:"Cindy",
frequentErrors: Array(5),
id:45,
ignoreForGrading: true,
lastName:"Lee"
}
];
var result = _(array)
.groupBy('id')
.map(_.spread(_.assign))
.value();
console.log(result);
body > div { min-height: 100%; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
Here's an alternative solution that uses Array#filter which takes advantage of the 2nd argument of the Array#filter which gives context to the filter's callback function. We use the this context as a mechanism to store cached objects by their id and then use this to decide whether to retain these objects from the array or not.
var result = array.filter(function(v) {
return this[v.id]?
!Object.assign(this[v.id], v):
(this[v.id] = v);
}, {});
var array = [
{
assignmentId:17,
email:"john.smith#email.com",
expectation: "Make sure to proofread!",
firstName:"John",
id:23,
ignoreForFeedback: true,
lastName:"Smith",
level:2,
levelFraction:null,
score:35
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"john.smith#email.com",
firstName:"John",
frequentErrors: Array(5),
id:23,
ignoreForGrading: true,
lastName:"Smith"
},
{
assignmentId:17,
email:"cl#email.com",
expectation: "cite sources",
firstName:"Cindy",
id:45,
ignoreForFeedback: true,
lastName:"Lee",
level:2,
levelFraction:null,
score:32
},
{
assignmentId:17,
countsPerCategory: Array(4),
email:"cl#email.com",
firstName:"Cindy",
frequentErrors: Array(5),
id:45,
ignoreForGrading: true,
lastName:"Lee"
}
];
var result = array.filter(function(v) {
// does this `id` exist?
return this[v.id]?
// assign existing object with the same id
// from the `this` cache object. Make sure
// to negate the resulting object with a `!`
// to remove this value from the array
!Object.assign(this[v.id], v):
// Assign the value from the `this` cache.
// This also retains this value from the existing
// array
(this[v.id] = v);
}, {});
console.log(result);
body > div { min-height: 100%; top: 0; }
You can use JavaScript's built in Array.reduce() method. The idea is you can create a map with the IDs and use lodash.merge() method (or whatever method you choose for merging objects) to merge all of the objects with the same ID into a single object. Then you can use .map() on the idMap you created to get the objects back into a single array.
var data = [{
assignmentId: 17,
email: "john.smith#email.com",
expectation: "Make sure to proofread!",
firstName: "John",
id: 23,
ignoreForFeedback: true,
lastName: "Smith",
level: 2,
levelFraction: null,
score: 35
},
{
assignmentId: 17,
countsPerCategory: Array(4),
email: "john.smith#email.com",
firstName: "John",
frequentErrors: Array(5),
id: 23,
ignoreForGrading: true,
lastName: "Smith"
},
{
assignmentId: 17,
email: "cl#email.com",
expectation: "cite sources",
firstName: "Cindy",
id: 45,
ignoreForFeedback: true,
lastName: "Lee",
level: 2,
levelFraction: null,
score: 32
},
{
assignmentId: 17,
countsPerCategory: Array(4),
email: "cl#email.com",
firstName: "Cindy",
frequentErrors: Array(5),
id: 45,
ignoreForGrading: true,
lastName: "Lee"
}
];
var idMap = data.reduce(function(result, current) {
if (result[current.id] == null) {
result[current.id] = current;
} else {
_.merge(result[current.id], current);
}
return result;
}, {});
var results = Object.keys(idMap).map(function(key) {
return idMap[key];
});
console.log(results);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
What I can suggest is to use a combination of forEach() and some() methods to iterate the array elements and test if the iterated object id is already processed or not.
This is the solution:
var merged = [];
arr.forEach(function(item) {
var idx;
var found = merged.some(function(el, i) {
idx = el.id === item.id ? i : null;
return el.id === item.id;
});
if (!found) {
merged.push(item);
} else if (idx !== null) {
for (k in Object.keys(item)) {
if (item.hasOwnProperty(k)) {
merged[idx][k] = item[k];
}
}
}
});
Working Demo:
var arr = [{
assignmentId: 17,
email: "john.smith#email.com",
expectation: "Make sure to proofread!",
firstName: "John",
id: 23,
ignoreForFeedback: true,
lastName: "Smith",
level: 2,
levelFraction: null,
score: 35
},
{
assignmentId: 17,
countsPerCategory: [],
email: "john.smith#email.com",
firstName: "John",
frequentErrors: [],
id: 23,
ignoreForGrading: true,
lastName: "Smith"
},
{
assignmentId: 17,
email: "cl#email.com",
expectation: "cite sources",
firstName: "Cindy",
id: 45,
ignoreForFeedback: true,
lastName: "Lee",
level: 2,
levelFraction: null,
score: 32
},
{
assignmentId: 17,
countsPerCategory: [],
email: "cl#email.com",
firstName: "Cindy",
frequentErrors: [],
id: 45,
ignoreForGrading: true,
lastName: "Lee"
}
];
var merged = [];
arr.forEach(function(item) {
var idx;
var found = merged.some(function(el, i) {
idx = el.id === item.id ? i : null;
return el.id === item.id;
});
if (!found) {
merged.push(item);
} else if (idx !== null) {
for (k in Object.keys(item)) {
if (item.hasOwnProperty(k)) {
merged[idx][k] = item[k];
}
}
}
});
console.log(merged);
Thank you for your help everyone, but I ended up going with my own implementation.
let ids = [];
let combinedUsers = [];
users.forEach(function (user) {
ids.push(user.id);
});
ids = _.uniq(ids);
ids.forEach(function(id){
let user = users.filter(function(userObj){
return id === userObj.id
});
if(user.length > 1){
user = Object.assign(user[0], user[1]);
combinedUsers.push(user);
} else {
combinedUsers.push(user[0]);
}
});
return combinedStudents;

Recreate an array of nested objects recursively

I have an array of nested objects created by a user. I need to recursively check the array for if checked is set to true and recreate the nested array of objects as shown below.
With my original attempt I was able to recursively get through the nested array. However, every time it recursed I would overwrite my previous information.
$scope.newPropertyTemplate = [];
function checkProps (item, props){
debugger
var obj = {};
var arr = [];
for(var i in props){
if(props[i].checked === true){
obj.property = item;
arr.push(props[i].name);
obj.subProperties = arr;
}
if(props[i].properties){
checkProps(props[i].name, props[i].properties);
}
}
return obj;
}
for(var i = 0; i < $scope.propertyTemplate.length; i++){
if($scope.propertyTemplate[i].properties !== null){
$scope.newPropertyTemplate.push(checkProps($scope.propertyTemplate[i].name, $scope.propertyTemplate[i].properties));
// $scope.newPropertyTemplate.push( newItem.property = $scope.propertyTemplate[i].name );
}
else {
$scope.newPropertyTemplate.push($scope.propertyTemplate[i].name);
}
}
Here's an example of the nested array:
[
{name: "allocation",
properties:[
{
name: "oid",
checked: true,
properties: null,
type: "integer"
},
{
name: "person",
checked: false,
properties: [
{
name: "Expires",
checked: true,
properties: null,
type: "timestamp"
},
{
name: "State/Province",
checked: true,
properties: null,
type: "string"
}
]
],
type: "allocation"
]
Here's an example of the converted array of objects:
[
{property: "allocation",
subProperties: [
"oid",
{property: "person",
subProperties: [
"Expires",
"State/Province"
]
}
]
}
]
This should work. The returned obj from your recursive checkProps function was not being stored.
var data = [{
name: "allocation",
properties: [{
name: "oid",
checked: true,
properties: null,
type: "integer"
}, {
name: "person",
checked: false,
properties: [{
name: "Expires",
checked: true,
properties: null,
type: "timestamp"
}, {
name: "State/Province",
checked: true,
properties: null,
type: "string"
}]
},
],
type: "allocation"
}];
function process(paths) {
var arr = [];
data.forEach(function(template) {
var resultObj = {};
recurse(template, resultObj);
arr.push(resultObj);
});
console.log(arr);
}
function recurse(template, obj) {
obj.property = template.name;
if (template.properties == null)
return;
obj.subProperties = [];
template.properties.forEach(function(prop) {
var res = {};
if (prop.properties != null)
recurse(prop, res);
else if (prop.checked)
res = prop.name;
if (res)
obj.subProperties.push(res);
});;
}
process(data);

Convert recursive array object to flat array object

I'm looking for a way to convert this array of recursive objects into a flat array of objects to make it easier to work with.
[
{
"name": "bill",
"car": "jaguar",
"age": 30,
"profiles": [
{
"name": "stacey",
"car": "lambo",
"age": 23,
"profiles": [
{
"name": "martin",
"car": "lexus",
"age": 34,
"profiles": []
}
]
}
]
}
]
This is the expected output.
[
{
"name": "bill",
"car": "jaguar",
"age": 30,
},{
"name": "stacey",
"car": "lambo",
"age": 23,
},{
"name": "martin",
"car": "lexus",
"age": 34,
}
]
Each profiles array can have n amount of items, which may or may not have an empty array of sub profiles. Note the converted array objects don't contain profiles after the conversion.
I'm open to using underscore or lodash to achieve this.
Let's call your original data o, combining Array.prototype.reduce with recursion I came up with this:
o.reduce(function recur(accumulator, curr) {
var keys = Object.keys(curr);
keys.splice(keys.indexOf('profiles'), 1);
accumulator.push(keys.reduce(function (entry, key) {
entry[key] = curr[key];
return entry;
}, {}));
if (curr.profiles.length) {
return accumulator.concat(curr.profiles.reduce(recur, []));
}
return accumulator;
}, []);
I would use a recursive function and pass the resulting array in it to avoid working with globals, something in the lines of:
var target = [];
var extractElements(source, target) {
//TODO: check if source is array
for (var i=0; i<source.length; i++) {
// create a new element with our data
var newElement = {
name: source[i].name,
car: source[i].car,
age: source[i].age
};
// put it in our flattened array
target.push(newElement);
// check if we need to go deeper and pass our flattened array around
if (source[i].profiles instanceof Array &&
source[i].profiles.length>0)
extractElements(source[i].profiles, target);
}
}
console.log(target) // should list your elements nicely
I haven't tested it, so use it for inspiration but beware :)
(edit1: "var i" in for)
const _ = require('lodash')
const arrayFromObject = (currentObject, currentArray) => {
const {profiles, ...rest} = currentObject
if (!_.isEmpty(currentObject.profiles)) {
return arrayFromObject(currentObject.profiles!, [...currentArray, rest])
}
return [...currentArray, rest]
}
const flatArray = arrayFromObject(myRecursiveObject, [])
Hi this can also be tried...
var out = [];
var i=0;
var extract = function(s, out) {
if(s[0] == null){
i = out.length -1;
return false;
}else {
out.push(s[0]);
}
extract(s[0].profiles, out);
delete out[i--].profiles;
};
extract(a, out); /// here 'a' is the input array and 'out' output
console.log(out);
All the best...
var _ = require('lodash')
/**
* Flatten a array-object via recursive property
* #see {#link http://stackoverflow.com/questions/31829897/convert-recursive-array-object-to-flat-array-object}
* #param {Array} arr Array of objects with recursive props
* #param {String} recursiveProperty The string of the recursive property
* #return {Array} Flat array of all recursive properties without recursive property
*/
function arrExtract (arr, recursiveProperty) {
var extracted = []
function _arrExtract (children) {
_.each(children, function (item) {
if (item[recursiveProperty] && item[recursiveProperty].length) _arrExtract(item[recursiveProperty])
extracted.push(_.omit(item, recursiveProperty))
})
}
_arrExtract(arr)
return extracted
}
module.exports = arrExtract
Almost three years later and still looking for a one-size fits solution for this. Here it is, heavily influenced by #axelduch's answer.
const {isPlainObject, isArray, get, omit, reduce} = require('lodash')
const recursiveFlatten = (tree, headProp, parentIdProp, parentRefProp, parent = {}) => {
tree = isArray(tree) ? tree : [tree]
return reduce(tree, (acq, current) => {
const currentWithoutHead = omit(current, [headProp])
if (parentIdProp && parentRefProp) currentWithoutHead[parentRefProp] = parent[parentIdProp] || null
acq = [...acq, currentWithoutHead]
const next = get(current, headProp)
if (isPlainObject(next) || isArray(next)) {
parent = currentWithoutHead
acq = [...acq, ...recursiveFlatten(next, headProp, parentIdProp, parentRefProp, parent)]
}
return acq
}, [])
}
Here's a simple example:
const example = recursiveFlatten({
name: 'bill',
love: true,
lovers: [{
name: 'jil',
love: false,
lovers: [{
name: 'diana',
love: false,
lovers: false
}, {
name: 'einstein',
love: false,
lovers: {
name: 'carl sagan',
love: false,
lovers: false
}
}]
}]
}, 'lovers')
[ { name: 'bill', love: true },
{ name: 'jil', love: false },
{ name: 'diana', love: false },
{ name: 'einstein', love: false },
{ name: 'carl sagan', love: false } ]
Here's an example adding parentId prop via parentRef.
const example = recursiveFlatten({
name: 'bill',
love: true,
lovers: [{
name: 'jil',
love: false,
lovers: [{
name: 'diana',
love: false,
lovers: false
}, {
name: 'einstein',
love: false,
lovers: {
name: 'carl sagan',
love: false,
lovers: false
}
}]
}]
}, 'lovers', 'name', 'parentName')
[ { name: 'bill', love: true, parentName: null },
{ name: 'jil', love: false, parentName: 'bill' },
{ name: 'diana', love: false, parentName: 'jil' },
{ name: 'einstein', love: false, parentName: 'jil' },
{ name: 'carl sagan', love: false, parentName: 'einstein' } ]
Here's a fairly simple technique that will solve the problem as originally defined.
const recursiveFlatten = (tree) =>
tree .length == 0
? []
: tree .flatMap (({profiles = [], ... rest}) => [{... rest}, ... recursiveFlatten (profiles)])
const tree = [{name: "bill", car: "jaguar", age: 30, profiles: [{name: "stacey", car: "lambo", age: 23, profiles: [{name: "martin", car: "lexus", age: 34, profiles: []}]}]}, {name: "denise", car: "pinto", age: 28}]
console .log (
recursiveFlatten (tree)
)
This hard-codes the name "profiles" and removes it, keeping the rest of the properties intact in the copy generated.
Your own answer suggest substantially more complex requirements. This version handles these through several optional parameters, the way your answer does, although the way it's called changes here and could easily be altered if necessary:
const recursiveFlatten = (headProp, parentIdProp, parentRefProp, parent = {}) => (tree) =>
tree .length == 0
? []
: tree .flatMap (({[headProp]: children = [], ... rest}) => [
{
... rest,
... (parentIdProp && parentRefProp ? {[parentRefProp]: parent[parentIdProp] || null} : {})
},
... recursiveFlatten (headProp, parentIdProp, parentRefProp, rest) (children)
])
const tree = [{name: "bill", car: "jaguar", age: 30, profiles: [{name: "stacey", car: "lambo", age: 23, profiles: [{name: "martin", car: "lexus", age: 34, profiles: []}]}]}, {name: "denise", car: "pinto", age: 28}]
console .log (recursiveFlatten ('profiles') (tree))
console .log (recursiveFlatten ('profiles', 'name', 'parentName') (tree))
I wouldn't be thrilled about this API in my own code-base, though. The differing behaviors depending on how many parameters are passed adds unnecessary complexity. I would probably bury them under an API such as
const recursiveFlatten = (parentIdProp, parentRefProp) => (headProp) => (tree) => ...
Then we could create functions we need, such as using
const flattenProfiles = recursiveFlatten (null, null) ('profiles')
and
const flattenAndExpand = recuriveFlatten ('name', 'parentName') ('profiles')
to replace the two call inside the console .log () statements above.

Using Underscore.js To Merge/Combine and Sum Array Entries

I have 2 arrays of JSON objects which I'm looking to merge/combine together and then sum up the quantities of any matching entries.
Both of the arrays contain the same structure, one represents a list of equipment that is required to be used...
var required = [
{ SerialisedEquipment: { SerialNo: "ser855212" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: { SerialNo: "ser288945" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm71770" }, Serialised: false, Quantity: 5 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm11025" }, Serialised: false, Quantity: 2 }];
...and the other represents a list of equipment that actually has been used.
var used = [
{ SerialisedEquipment: { SerialNo: "ser663033" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: { SerialNo: "ser288945" }, Type: undefined, Serialised: true, Quantity: 1 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm71770" }, Serialised: false, Quantity: 2 }];
I have access to underscore.js and have been trying to use the _.groupBy and _.reduce methods to try and get the result I'm after but with no success. The result I'm looking to achieve is:
var result = [
{ SerialisedEquipment: { SerialNo: "ser663033" }, Type: undefined, Used: 1, Expected: 0, Remaining: 0 },
{ SerialisedEquipment: { SerialNo: "ser288945" }, Type: undefined, Used: 1, Expected: 1, Remaining: 0 },
{ SerialisedEquipment: { SerialNo: "ser855212" }, Type: undefined, Used: 0, Expected: 1, Remaining: 1 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm71770" }, Used: 2, Expected: 5, Remaining: 3 },
{ SerialisedEquipment: undefined, Type: { ItemId: "itm11025" }, Used: 0, Expected: 2, Remaining: 2 }];
I've also been looking at some of the Array methods that underscore provides but I'm not sure how I would use these to specify the criteria to merge by. Would anyone have any suggestions on the best way to achieve this?
UPDATE
I've managed to get the merged list of both of the arrays, removing duplicates...
// Split based on the serialised flag - so I know to look at either the serialNo or Type property
var isSerialised = _.groupBy(required, function (equip) {
return equip.Serialised;
});
// Get all the required serialised equipment that is not already in the used list
var serialised = _.filter(isSerialised[true], function (value) {
return (!_.some(used, function (equip) {
return equip.SerialisedEquipment && equip.SerialisedEquipment.SerialNo == value.SerialisedEquipment.SerialNo;
}));
});
// Get all the required types that are not already in the used list
var types = _.filter(isSerialised[false], function (value) {
return (!_.some(used, function (equip) {
return equip.Type && equip.Type.ItemId == value.Type.ItemId;
}));
});
// Combine the equipment that is not in the list with the equipment that is in the list
var result = _.union(used, serialised, types);
I think it's now just a case now of looping through this results list with the required equipment list and summing up equipment that match based on serial number or type.
Sometime wanting to use a library to much makes you miss simpler algorithms:
var resultsById = {};
function getTemporaryId(value) {
return value.SerialisedEquipment ? value.SerialisedEquipment.SerialNo : value.Type.ItemId;
}
function getResultForValue(value) {
var id = getTemporaryId(value);
if(!resultsById[id]) {
resultsById[id] = {
SerialisedEquipment: value.SerialisedEquipment,
Type: value.Type,
Used: 0,
Expected: 0,
Remaining: 0
};
}
return resultsById[id];
}
_.each(required, function(value) {
var result = getResultForValue(value);
result.Expected += value.Quantity;
result.Remaining += value.Quantity;
});
_.each(used, function(value) {
var result = getResultForValue(value);
result.Used += value.Quantity;
result.Remaining = Math.max(result.Remaining - value.Quantity, 0);
});
var merged = _.values(resultsById);
If you really want to use lots of underscore, you can play with this solution:
var requiredValues = _.map(required, (function(value){
//maybe want to clone value? value = _.clone(value);
value.Used = 0;
value.Expected = value.Quantity;
return value;
}));
var usedValues = _.map(used, (function(value){
//maybe want to clone value? value = _.clone(value);
value.Used = value.Quantity;
value.Expected = 0;
return value;
}));
var mergedValues = _.chain(requiredValues.concat(usedValues))
.groupBy(function(value){
return value.SerialisedEquipment ? value.SerialisedEquipment.SerialNo : value.Type.ItemId;
})
.map(function(values) {
var memo = {
SerialisedEquipment: values[0].SerialisedEquipment,
Type: values[0].Type,
Used: 0,
Expected: 0,
Remaining: 0
};
return _.reduce(values, function(memo, value) {
memo.Used += value.Used;
memo.Expected += value.Expected;
memo.Remaining = Math.max(memo.Expected - memo.Used, 0);
return memo;
}, memo)
})
.values()
.value();
You can just use Array.concat() method:
var result = required.concat(used);
and it will make merge for you.
EDIT
Though if you want to use undescore method _.union(arr1,arr2) is for you!

Categories

Resources