Related
I have two arrays as below,
var day1 = [{id: 1, type:"20H", cases: 30, fail: 5},
{id: 1, type:"12C", cases: 10, fail: 3},
{id: 1, type:"4B", cases: 20, fail: 8}];
var day5 = [{id: 5, type:"12C" ,cases: 5, fail: 2},
{id: 5, type:"4B", cases: 12, fail: 3},
{id: 5, type:"20H", cases: 20, fail: 6}];
Expecting the below result of array,
var result = [{id: 1, type:"20H", caseCount: 30, failCount: 5, difference: 10, diffPercentage: 50.00},
{id: 1, type:"12C", caseCount: 10, failCount: 3, difference: 5, diffPercentage: 100.00},
{id: 1, type:"4B", caseCount: 20, failCount: 8, difference: 8, diffPercentage: 66.66}];
The logic of difference and diffPercentage as below,
Here, I am not getting how to get the matched value between two arrays and proceed.
if(day1.type === day5.type){
difference = day1.cases - day5.cases;//Here, 30-20 = 10
diffPercentage = ((day1.cases - day5.cases)/(day5.cases)*100).toFixed(2);// 10/20 * 100 = 50.00
}
Tried the following,
result = [];
day1.forEach(function(day1Items, idx){
var day5Items = day5[idx];
var outputElements = {};
if(day1Items && day5Items){
if(day1Items.type == day5Items.type)){ //Here, I am not getting how to get the matched value between two array and proceed.
console.log("Inside if block2"); //it is coming here, because, the if condition going index wise check
outputElements.id = day1Items.id;
outputElements.type = day1Items.type;
outputElements.caseCount = day1Items.cases;
outputElements.failCount = day1Items.fail;
outputElements.difference = day1Items.cases - day5Items.cases;//Here, I need to get the cases value respective type matched
outputElements.diffPercentage = ((day1.cases - day5.cases)/(day5.cases)*100).toFixed(2);
result.push(outputElements);
}
}
});
console.log(result);
You can use a Map to achieve this.
var day1 = [
{ id: 1, type: "20H", cases: 30, fail: 5 },
{ id: 1, type: "12C", cases: 10, fail: 3 },
{ id: 1, type: "4B", cases: 20, fail: 8 }
];
var day5 = [
{ id: 5, type: "12C", cases: 5, fail: 2 },
{ id: 5, type: "4B", cases: 12, fail: 3 },
{ id: 5, type: "20H", cases: 20, fail: 6 }
];
function merge(arr1, arr2) {
const hashMap = new Map();
arr1.forEach((elem) => {
const elemClone = {
...elem,
failCount: elem.fail,
caseCount: elem.cases
};
delete elemClone.fail;
delete elemClone.cases;
hashMap.set(elem.type, elemClone);
});
arr2.forEach((elem) => {
if (hashMap.has(elem.type)) {
const difference = Math.abs(elem.cases -
hashMap.get(elem.type).caseCount);
const diffPercentage = Number(parseFloat((difference / elem.cases)
* 100).toFixed(2));
hashMap.set(elem.type, {
...hashMap.get(elem.type),
difference,
diffPercentage
});
} else {
hashMap.set(elem.type, elem);
}
});
return Array.from(hashMap.values());
}
console.log(merge(day1, day5));
This should work:
var day1 = [{id: 1, type:"20H", cases: 30, fail: 5},
{id: 1, type:"12C", cases: 10, fail: 3},
{id: 1, type:"4B", cases: 20, fail: 8}];
var day5 = [{id: 5, type:"12C" ,cases: 5, fail: 2},
{id: 5, type:"4B", cases: 12, fail: 3},
{id: 5, type:"20H", cases: 20, fail: 6}];
let result = []
day1.forEach(d1 => {
const day5Item = day5.find(d5 => d5.type === d1.type);
if(day5Item){
let difference = d1.cases - day5Item.cases;
let diffPercentage = (difference/day5Item.cases*100).toFixed(2)
result.push({
id:d1.id,type:d1.type,caseCount:d1.cases,failCount:d1.fail,difference,diffPercentage
})
}
});
console.log(result);
Here is the corrected version of your code.
The issue with your code was, you were looping through day1 array with array.forEach and by making use of that index you were selecting matching node from day5 array. Thats worng, This will work only if the arays are in the same order with type. Rather that selecting with var day5Items = day5[idx]; you have to select the node from day5 using Array.find and checking with type. Thil will give you the desired node.
Also there was an error in calculating diffPercentage
Instead of outputElements.diffPercentage = ((day1.cases - day5.cases)/(day5.cases)*100).toFixed(2); It should be outputElements.diffPercentage = ((day1Items.cases - day5Items.cases)/(day5Items.cases)*100).toFixed(2);. day1 and day5 are arrays. You cannot access day1.cases or day5.cases instead it should be day1Items.cases and day5Items.cases
var day1 = [
{ id: 1, type: "20H", cases: 30, fail: 5 },
{ id: 1, type: "12C", cases: 10, fail: 3 },
{ id: 1, type: "4B", cases: 20, fail: 8 }
];
var day5 = [
{ id: 5, type: "12C", cases: 5, fail: 2 },
{ id: 5, type: "4B", cases: 12, fail: 3 },
{ id: 5, type: "20H", cases: 20, fail: 6 },
];
const result = [];
day1.forEach(function(day1Items, idx){
// You cannot simply select with index.
// Instead make use of `Array.find` to select the node from day5 Array with the condition
// var day5Items = day5[idx];
var day5Items = day5.find((node) => node.type === day1Items.type)
var outputElements = {};
if(day1Items && day5Items){
if(day1Items.type == day5Items.type){ //Here, I am not getting how to get the matched value between two array and proceed.
outputElements.id = day1Items.id;
outputElements.type = day1Items.type;
outputElements.caseCount = day1Items.cases;
outputElements.failCount = day1Items.fail;
outputElements.difference = day1Items.cases - day5Items.cases;//Here, I need to get the cases value respective type matched
// Calculation was wrong here
outputElements.diffPercentage = ((day1Items.cases - day5Items.cases)/(day5Items.cases)*100).toFixed(2);
result.push(outputElements);
}
}
});
console.log(result);
Simplified Method
Use Array.reduce
var day1 = [
{ id: 1, type: "20H", cases: 30, fail: 5 },
{ id: 1, type: "12C", cases: 10, fail: 3 },
{ id: 1, type: "4B", cases: 20, fail: 8 },
{ id: 1, type: "49B", cases: 20, fail: 8 }
];;
var day5 = [
{ id: 5, type: "12C", cases: 5, fail: 2 },
{ id: 5, type: "4B", cases: 12, fail: 3 },
{ id: 5, type: "20H", cases: 20, fail: 6 },
];
const result = day1.reduce((acc, curr) => {
const insertNode = { id: curr.id, type: curr.type, caseCount: curr.cases, failCount: curr.fail };
const d5Node = day5.find((node) => node.type === curr.type);
if (d5Node) {
insertNode.difference = curr.cases - d5Node.cases;
insertNode.diffPercentage = (insertNode.difference / d5Node.cases * 100).toFixed(2);
} else {
// Handle the calculation logic here
insertNode.difference = curr.cases;
insertNode.diffPercentage = insertNode.difference * 100;
}
acc.push(insertNode);
return acc;
}, []);
console.log(result);
So simplified code.
var a = [
{ name: "first", num: 1 },
{ name: "first", num: 2 },
{ name: "first", num: 3 },
{ name: "first", num: 4 },
{ name: "first", num: 5 },
{ name: "first", num: 6 },
{ name: "first", num: 7 },
{ name: "first", num: 8 },
{ name: "first", num: 9 }
];
var b = a.filter(function(el) {
return el.num % 2 == 0;
});
console.log("a1", a); // [1, 20, 3, 40, 5, 60, 7, 80, 9]
console.log("b1", b); // [20, 40, 60, 80]
for (let i = 0; i < b.length; i++) {
b[i].num = b[i].num * 10;
}
console.log("a2", a); // [1, 20, 3, 40, 5, 60, 7, 80, 9]
console.log("b2", b); // [20, 40, 60, 80]
My new understanding is the array element contains a reference to an object, not the object. What are some ways to get those objects duplicated?
Filter, then build new objects from the filtered array and put the new things in a new array?
Use some method I'm not currently familiar with?
Redesign the code to stop using objects in an array?
Also, what's up with console.log() showing the variables have changed when placed before the for loop?
If you wish to duplicate the objects inside the array, you should use the map function.
var b = a.filter(val => val.num %2 === 0).map(val => Object.assign({}, val, { num: val.num * 10}));
The map function will return a new array with the value returned from the function. In this example, we are creating a new object Object.assign({}) and duplicating the existing object while changing the num field.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
If you want to clone objects you will need a clone function, I use this function
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
You can then clone the array with
let c = clone(b);
Which will be a new array where each object is a new clone.
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
var b = a.filter(function(el){return el.num%2==0 });
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
let c = clone(b);
console.log(b[0] === c[0]);
Yes, elements of Array a are all pointers. so you need to use Object.assign (as many says)
and other solution with array reduce usage (see Adrian Brand comment)
var a = [ { name: 'first', num: 1 }
, { name: 'first', num: 2 }
, { name: 'first', num: 3 }
, { name: 'first', num: 4 }
, { name: 'first', num: 5 }
, { name: 'first', num: 6 }
, { name: 'first', num: 7 }
, { name: 'first', num: 8 }
, { name: 'first', num: 9 }
]
var b = a.filter(el=>!(el.num%2)).map(el=>Object.assign({},el))
// other solution with reduce
var c = a.reduce((acc,cur)=>{
if (!(cur.num%2) )acc.push(Object.assign({},cur))
return acc
}, [])
ConsoleArrayNamNum('var a -1-',a) // [1,2,3,4,5,6,7,8,9]
ConsoleArrayNamNum('var b -1-',b) // [2, 4, 6, 8]
ConsoleArrayNamNum('var c -1-',c) // [2, 4, 6, 8]
for(let elm of b)
{ elm.num *= 10 }
ConsoleArrayNamNum('var a -2-',a) // [1,2,3,4,5,6,7,8,9]
ConsoleArrayNamNum('var b -2-',b) // [20, 40, 60, 80]
function ConsoleArrayNamNum(title,arr) {
console.log(title)
for(let elm of arr)
{ console.log(`{ name: '${elm.name}', num: ${elm.num} }`) }
}
.as-console-wrapper { min-height: 100% !important; }
If you want a new array with the final values you can use reduce to do it all in one go, reduce starts with an accumulator of an empty array and each iteration if it meets the condition it adds a clone with the spread operator overriding the num time 10.
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
const evensTimes10 = array => array.reduce((results, item) => {
if (item.num % 2 === 0) {
results.push({ ...item, num: item.num * 10 });
}
return results;
}, []);
var b = evensTimes10(a);
console.log('a1',a); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log('b1',b); // [20, 40, 60, 80]
A simple solution using some ES6 syntax:
var a = [{name: 'first', num:1}, {name:'first', num: 2}, {name:'first', num: 3},
{name:'first', num: 4}, {name:'first', num: 5}, {name:'first', num: 6}, {name:'first', num: 7},
{name:'first', num: 8}, {name:'first', num: 9}];
const b = a
.filter(el => {
if (el.num % 2 === 0) {
return {
...el
}
}
})
.map(newEl => newEl.num * 10);
console.log('a', a); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log('b', b);
.filter() iterates the "a" array and returns only elements with
"num" property that reaches the condition. This is a cloned array.
return { ...el } returns a cloned object thanks to spread
operator.
.map() creates a new array and returns each "el.num" value *
10
Here some info about .map() .filter() and spread operator:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
I found this very interesting site that lists all Javascript functions with their descriptions and shows if is mutable or not, this helps a lot:
https://doesitmutate.xyz/
I am facing problem to merge the two arrays. I have two arrays of objects first is prev having old values and another with updated values. I would like to have result array with all the objects of prev array with its updated value in array next, and also have objects in next array.
Example:
var prev = [{id: 1, val: 'abc'}, {id: 2, val: 'pqr'}];
var next = [{id: 1, val: 'nextVal'}, {id: 3, val: 'xyz'}];
expected
mergeOutput = [
{id: 1, val: 'nextVal'}, // value is updated
{id: 2, val: 'pqr'},
{id: 3, val: 'xyz'}
]
Note: Array order do not matter.
You can use Map() to merge array.
var prev = [{id: 1, val: 'abc'}, {id: 2, val: 'pqr'}];
var next = [{id: 1, val: 'nextVal'}, {id: 3, val: 'xyz'}];
var hash = new Map();
prev.concat(next).forEach(function(obj) {
hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj))
});
var mergedArray = Array.from(hash.values());
console.log(mergedArray);
Source : StackOverflow
I have a bunch of arrays in this form:
var myRows = [
[{idx: 0, val: 90}, {idx: 1, val: 75}, {idx: 2, val: 35}],
[{idx: 0, val: 50}, {idx: 1, val: 17}, {idx: 2, val: 95}],
[{idx: 0, val: 10}, {idx: 1, val: 24}, {idx: 2, val: 80}]
// ...
];
Lets say I would like to sort the first row ascending by val, so it becomes:
[{idx: 2, val: 35}, {idx: 1, val: 75}, {idx: 0, val: 90}]
Is there an easy way to sort the remaining arrays, so that their order matches the idx-order of the sorted first row?
myArrays = [
[{idx: 2, val: 35}, {idx: 1, val: 75}, {idx: 0, val: 90}]
, [{idx: 2, val: 95}, {idx: 1, val: 17}, {idx: 0, val: 50}]
, [{idx: 2, val: 80}, {idx: 1, val: 24}, {idx: 0, val: 10}]
// ...
];
Maybe this is even possible without the idx property?
You could use sorting with map and apply the mapping for all items.
This proposal saves the indices, order the array and applies the order to all other arrays as well.
// the array to be sorted
var list = [[{ idx: 0, val: 90 }, { idx: 1, val: 75 }, { idx: 2, val: 35 }], [{ idx: 0, val: 50 }, { idx: 1, val: 17 }, { idx: 2, val: 95 }], [{ idx: 0, val: 10 }, { idx: 1, val: 24 }, { idx: 2, val: 80 }]];
// temporary array holds objects with position and sort-value
var mapped = list[0].map(function (el, i) {
return { index: i, value: el.val };
})
// sorting the mapped array containing the reduced values
mapped.sort(function (a, b) {
return a.value - b.value;
});
// rearrange all items in list
list.forEach(function (a, i, aa) {
aa[i] = mapped.map(function (el) {
return a[el.index];
});
});
console.log(list);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could do something like this.
var order = myRows[0].map(function(e) { return e.idx })
myRows.forEach(function(row) {
row.sort(function(a,b) {
return order.indexOf(a.idx) - order.indexOf(b.idx);
});
});
This is very simple code just to demonstate the idea.
It will probably be slow for very large arrays.
You can do the following which does,
Sort the first row of array1, and store their idxes in a temporary array2
Assign the remaining array with a temp property according to the first idx3
Sort the remaining array based on their temp property4 (Which is based on the first array)
Remove the temp property5
E.g.
var filteredRows = [];
var myRows = [
[{idx: 0, val: 90}, {idx: 1, val: 75}, {idx: 2, val: 35}],
[{idx: 0, val: 50}, {idx: 1, val: 17}, {idx: 2, val: 95}],
[{idx: 0, val: 10}, {idx: 1, val: 24}, {idx: 2, val: 80}]
];
/* 1. Sort the first row */
myRows[0].sort(function(a, b) {
return a.val - b.val;
});
filteredRows.push(myRows[0]);
/* 2. Get indexes */
var idxs = [];
for (var obj of myRows[0]) {
idxs.push(obj.idx);
}
/* Handle the remaining array */
myRows.slice(1).map(function (val) {
/* 3. Assign temp value */
val.map(function (obj, i) {
obj.temp = idxs[i];
});
/* 4. Sort them */
val.sort(function (a, b) {
return a.temp - b.temp;
});
/* 5. Remove temp value */
val.map(function (obj, i) {
delete obj.temp;
});
});
console.log(JSON.stringify(myRows));
When you drop the idx property, you can just use an array:
// Function copied from here: http://stackoverflow.com/a/36164530/5710637
var transpose = m => m[0].map((x,i) => m.map(x => x[i]))
var sortByRow = 0
var myRows = [
[90, 75, 35],
[50, 17, 95],
[10, 24, 80]
]
var myCols = transpose(myRows)
myCols.sort((x, y) => x[sortByRow] - y[sortByRow])
myRows = transpose(myCols)
console.log(myRows)
Use a hash table to create a sorting criteria based on the first row - see demo below:
var myRows=[[{idx:0,val:90},{idx:1,val:75},{idx:2,val:35}],[{idx:0,val:50},{idx:1,val:17},{idx:2,val:95}],[{idx:0,val:10},{idx:1,val:24},{idx:2,val:80}]];
// sort the first row (as desired)
myRows[0].sort((a,b) => a.val - b.val);
myRows.forEach(function(c,i){
if(i === 0){
// create order criteria based on first row
c.forEach(function(e, k){
this[e.idx] = k;
});
} else {
c.sort(function(a,b) {
return this[a.idx] - this[b.idx];
});
}
}, Object.create(null));
console.log(myRows);
.as-console-wrapper{top:0;max-height:100%!important;}
I have a stupid problem that at first seems to be simple to solve, but turns out to be tricky.
I have an array of objects, each with two properties: id and value:
[
{id: 2, value: 10},
{id: 4, value: 3},
{id: 2, value: 2},
{id: 1, value: 15}
]
I want to write an algorithm that sums up the values of ones with similar id.
My end result should be a new array with only the merged objects:
[
{id: 2, value: 12},
{id: 4, value: 3},
{id: 1, value: 15}
]
I've tried the following, but it doesn't work:
var arr = [];
arr.push({id: 2, visit:10});
arr.push({id: 4, visit:3});
arr.push({id: 2, visit:2});
arr.push({id: 1, visit:15});
// Deep copy
var copy = jQuery.extend(true, [], arr);
var masterArr = [];
for (var i = 0; i < arr.length; i++) {
var objArr = [];
objArr.push(arr[i]);
for (var j = copy.length-1; j > -1; j--) {
if (arr[i].id === copy[j].id) {
var q = copy.splice(j,1);
}
}
masterArr.push(objArr);
}
My plan was to first gather all similar objects in separate arrays (objArr), sum them up and put them in an end array (masterArr). I use jquerys extend to make a deep copy (not a reference) and reverse iteration and splice to remove objects thats already been found as "duplicates".
This doesn't work! And it doesn't seem to be a very efficient mehtod to solve my problem.
How could I do this? Performance isn't top priority but rather "nice to have"!
Thanks!
You can do it like this:
// Assuming:
a = [{id: 2, value: 10}, {id: 4, value: 3}, {id: 2, value: 2}, {id: 1, value: 15}]
var b = {}, // Temporary variable;
c = []; // This will contain the result;
// Build a id:value object ( {1: 15, 2: 12, 4: 3} )
a.map(function(current){b[current.id] = (b[current.id] || 0) + current.value});
for(var key in b){ // Form that into the desired output format.
c.push({id: parseInt(key, 10), value: b[key]});
}
console.log(c);
/* [{id: 1, value: 15},
{id: 2, value: 12},
{id: 4, value: 3}] */
I'm using parseInt(key, 10), since the keys are strings, you'll probably want them converted to integers again.
// First group the data based on id and sum the values
var temp = data.reduce(function(result, current) {
result[current.id] = (result[current.id] || 0) + current.value;
return result;
}, {});
// then recreate the objects with proper id and value properties
var result = [];
for (var key in temp) {
result.push({
id: parseInt(key, 10),
value: temp[key]
});
}
console.log(result);
Output
[ { id: 1, value: 15 },
{ id: 2, value: 12 },
{ id: 4, value: 3 } ]
The quickest approach loops over the array only once using Array.prototype.filter():
var tmp = {},
result = arr.filter(function (el) {
if (tmp.hasOwnProperty(el.id)) {
tmp[el.id].visit += el.visit;
return false;
}
else {
tmp[el.id] = el;
return true;
}
});
It also reuses the objects, though this renders the original array to contain inaccurate values. If this is a problem, you can modify the example to copy each object property to a new object.