Get array value sums based on other array key values - javascript

I have an array similar to this:
var programs_array = [
{"id":3543,"category":"1","target_revenue":1845608},
{"id":2823,"category":"1","target_revenue":1627994},
{"id":1611,"category":"1","target_revenue":1450852},
{"id":1624,"category":"1","target_revenue":25473},
{"id":4626,"category":"2","target_revenue":253048},
{"id":5792,"category":"2","target_revenue":298468},
{"id":5799,"category":"2","target_revenue":256815},
{"id":5171,"category":"2","target_revenue":239090},
{"id":4064,"category":"3","target_revenue":119048},
{"id":2322,"category":"3","target_revenue":59146},
{"id":3466,"category":"3","target_revenue":29362},
{"id":3442,"category":"3","target_revenue":149860},
{"id":1254,"category":"3","target_revenue":15600},
{"id":1685,"category":"3","target_revenue":45463}
];
I want the sum of all "target_revenue" values if "category" equals 2. Currently, I'm doing this, but I'd like to ensure I'm doing this the most efficient way.
Array.prototype.sum_cat = function (prop, cat, val) {
var total = 0
for ( var i = 0, _len = this.length; i < _len; i++ ) {
if(this[i][cat]==val){total += this[i][prop]}
}
return total
}
console.log('total 2: '+programs_array.sum_cat('target_revenue','category',2));
Here's a fiddle: https://jsfiddle.net/26v48djp/

I would use reduce, adding to the accumulator if category is 2:
const programs_array=[{"id":3543,"category":"1","target_revenue":1845608},{"id":2823,"category":"1","target_revenue":1627994},{"id":1611,"category":"1","target_revenue":1450852},{"id":1624,"category":"1","target_revenue":25473},{"id":4626,"category":"2","target_revenue":253048},{"id":5792,"category":"2","target_revenue":298468},{"id":5799,"category":"2","target_revenue":256815},{"id":5171,"category":"2","target_revenue":239090},{"id":4064,"category":"3","target_revenue":119048},{"id":2322,"category":"3","target_revenue":59146},{"id":3466,"category":"3","target_revenue":29362},{"id":3442,"category":"3","target_revenue":149860},{"id":1254,"category":"3","target_revenue":15600},{"id":1685,"category":"3","target_revenue":45463}]
const getSum = (findCat) => programs_array.reduce((a, { category, target_revenue }) => (
category === findCat
? a + target_revenue
: a
), 0);
console.log(getSum("2"));

You could chain filter with a reduce to accomplish this easily and concisely.
const sum = programs_array.filter(e => e.category === '2').reduce((acc, element) => acc + element.target_revenue);
Or if you wanted a slightly more performant, but less concise way you could do the following. But the difference for an array of this size is likely negligible.
const sum = programs_array.reduce((acc, element) => {
return element.category === '2' ? (acc + element.target_revenue) : acc;
});

You can simply achieve this using array.reduce()
let arr = [{"id":3543,"category":"1","target_revenue":1845608}, {"id":2823,"category":"1","target_revenue":1627994}, {"id":1611,"category":"1","target_revenue":1450852}, {"id":1624,"category":"1","target_revenue":25473}, {"id":4626,"category":"2","target_revenue":253048}, {"id":5792,"category":"2","target_revenue":298468}, {"id":5799,"category":"2","target_revenue":256815}, {"id":5171,"category":"2","target_revenue":239090}, {"id":4064,"category":"3","target_revenue":119048}, {"id":2322,"category":"3","target_revenue":59146}, {"id":3466,"category":"3","target_revenue":29362}, {"id":3442,"category":"3","target_revenue":149860}, {"id":1254,"category":"3","target_revenue":15600}, {"id":1685,"category":"3","target_revenue":45463} ];
function getSum(prop, val){
return arr.reduce((a,curr)=> curr.category === val ? a + curr[prop] : a,0);
}
console.log(getSum("target_revenue", "2"));

In general, a for loop is going to be the most efficient solution.
See: https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7
I would use your code with slight modifications as seen below:
Array.prototype.sum_cat = function (prop, cat, val) {
let total = 0,
_len = this.length;
for (let i = 0; i < _len; i++) {
if (this[i][cat] == val) { total += this[i][prop]; }
}
return total;
}
console.log('total 2: '+programs_array.sum_cat('target_revenue','category',2));

Related

Javascript nested array: halve items and sum them up

I'm trying to create a function which returns me halve the data sumed up. I was able to do it on a non-nested Array but failing on the nested Array. I get the error Cannot read properties of undefined (reading 'push').
How the returned data should look like:
var data = [{"Key":1,"values":[
{"LastOnline":"21-11-29","Value":2},
{"LastOnline":"21-12-01","Value":2},
{"LastOnline":"21-12-03","Value":2}
]}];
What I have right now:
var data = [{"Key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data){
var newData = [];
var temp = [{"key":data.key,"values":[{}]}];
// sum 2 togheter
for(var i=1;i<data.values.length;i++){
if(data.values[i]){
temp.values[i].push({"LastOnline":data.values[i].LastOnline, "Value":(data.values[i].Value + data.values[[i-1]].Value)});
}
}
for(var i=0;i<temp.values.length;i++){
if(i % 2 == 0){
newData.push(temp.values[i]);
}
}
return newData;
}
console.log(halveMonth(data));
JS variables are case sensitive. Keep the key consistent everywhere. If you don't plan to use reduce here is the solution.
var data = [{"key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data){
let newData = []
for (let i = 0; i < data.length; i++) {
let temp = {"key":data[i].key,"values":[]}
for (let j = 0; j < data[i].values.length; j += 2) {
const res = (j+1===data[i].values.length) ? data[i].values[j].Value : data[i].values[j].Value + data[i].values[j+1].Value
temp.values.push({"LastOnline":(j+1===data[i].values.length)?data[i].values[j].LastOnline:data[i].values[j+1].LastOnline,"Value":res});
}
newData.push(temp);
}
return newData
}
console.log(halveMonth(data));
The variable data you declare at the first line of your snippet is an array. So you can't do data.values. You first need to indicate which index of your array you want to read. In this case : data[0].values
First things first, you data is itself an array - so assuming your real data has more than 1 element you'll need to do the same thing for each one.
It's helpful to start off with a method which does the work on just 1 element
const justASingle = {"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1}]};
function halveMonthSingle(data) {
return {
...data,
values: data.values.reduce((acc, item, idx) => {
if ((idx % 2) != 0)
acc.push({
...item,
Value: data.values[idx - 1].Value + item.Value
})
return acc;
}, [])
}
}
console.log(halveMonthSingle(justASingle))
Once you have that you can just use map do do it for every element
const data = [{"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1}]}];
function halveMonthSingle(data) {
return {
...data,
values: data.values.reduce((acc, item, idx) => {
if ((idx % 2) != 0)
acc.push({
...item,
Value: data.values[idx - 1].Value + item.Value
})
return acc;
}, [])
}
}
const result = data.map(halveMonthSingle)
console.log(result)
I would use reduce - saves me from trying to figure out why your two loops do not work other than data.values should be data[0].values
var data = [{"Key":1,"values":[{"LastOnline":"21-11-28","Value":1},{"LastOnline":"21-11-29","Value":1},{"LastOnline":"21-11-30","Value":1},{"LastOnline":"21-12-01","Value":1},{"LastOnline":"21-12-02","Value":1},{"LastOnline":"21-12-03","Value":1},]}];
const newArr = data.slice(0); // to not mutate original
newArr[0].values = data[0].values.reduce((acc,item,i) => {
if (i%2 !== 0) { // every second
acc.push(data[0].values[i]); // push the item
acc[acc.length-1].Value += data[0].values[i-1].Value; // add the first
}
return acc
},[])
console.log(newArr)
This works too. I basically did your idea, skipping the temp array and merging the two steps into one.
const data = [{"Key":1,"values":[
{"LastOnline":"21-11-28","Value":1},
{"LastOnline":"21-11-29","Value":1},
{"LastOnline":"21-11-30","Value":1},
{"LastOnline":"21-12-01","Value":1},
{"LastOnline":"21-12-02","Value":1},
{"LastOnline":"21-12-03","Value":1},
]}];
function halveMonth(data) {
const newData = [];
newData.push({
Key: data[0].Key,
values: []
});
for(let i = 0; i < data[0].values.length; i++){
if (i % 2 !== 0) {
newData[0].values.push({
LastOnline: data[0].values[i].LastOnline,
Value: data[0].values[i].Value + data[0].values[i-1].Value
});
}
}
return newData;
}
console.log(halveMonth(data));

Completely removing duplicate items from an array

Let's assume that I have ;
var array = [1,2,3,4,4,5,5];
I want it to be;
var newArray = [1,2,3];
I want to remove the duplicates completely rather than keeping them as unique values. Is there a way achieve that through reduce method ?
You could use Array#filter with Array#indexOf and Array#lastIndexOf and return only the values which share the same index.
var array = [1, 2, 3, 4, 4, 5, 5],
result = array.filter(function (v, _, a) {
return a.indexOf(v) === a.lastIndexOf(v);
});
console.log(result);
Another approach by taking a Map and set the value to false, if a key has been seen before. Then filter the array by taking the value of the map.
var array = [1, 2, 3, 4, 4, 5, 5],
result = array.filter(
Map.prototype.get,
array.reduce((m, v) => m.set(v, !m.has(v)), new Map)
);
console.log(result);
I guess it won't have some remarkable performance, but I like the idea.
var array = [1,2,3,4,4,5,5],
res = array.reduce(function(s,a) {
if (array.filter(v => v !== a).length == array.length-1) {
s.push(a);
}
return s;
}, []);
console.log(res);
Another option is to use an object to track how many times an element is used. This will destroy the array order, but it should be much faster on very large arrays.
function nukeDuplications(arr) {
const hash = {};
arr.forEach(el => {
const qty = hash[el] || 0;
hash[el] = qty+1;
});
const ret = [];
Object.keys(hash).forEach(key => {
if (hash[key] === 1) {
ret.push(Number(key));
}
})
return ret;
}
var array = [1,2,3,4,4,5,5];
console.log(nukeDuplications(array));
A slightly more efficient solution would be to loop over the array 1 time and count the number of occurrences in each value and store them in an object using .reduce() and then loop over the array again with .filter() to only return items that occurred 1 time.
This method will also preserve the order of the array, as it merely uses the object keys as references - it does not iterate over them.
var array = [1,2,3,4,4,5,5];
var valueCounts = array.reduce((result, item) => {
if (!result[item]) {
result[item] = 0;
}
result[item]++;
return result;
}, {});
var unique = array.filter(function (elem) {
return !valueCounts[elem] || valueCounts[elem] <= 1;
});
console.log(unique)
Another option is to use an object to track how many times an element is used. This will destroy the array order, but it should be much faster on very large arrays.
// Both versions destroy array order.
// ES6 version
function nukeDuplications(arr) {
"use strict";
const hash = {};
arr.forEach(el => {
const qty = hash[el] || 0;
hash[el] = qty + 1;
});
const ret = [];
Object.keys(hash).forEach(key => {
if (hash[key] === 1) {
ret.push(Number(key));
}
})
return ret;
}
// ES5 version
function nukeDuplicationsEs5(arr) {
"use strict";
var hash = {};
for (var i = 0; i < arr.length; i++) {
var el = arr[i];
var qty = hash[el] || 0;
hash[el] = qty + 1;
};
var ret = [];
for (let key in hash) {
if (hash.hasOwnProperty(key)) {
if (hash[key] === 1) {
ret.push(Number(key));
}
}
}
return ret;
}
var array = [1, 2, 3, 4, 4, 5, 5];
console.log(nukeDuplications(array));
console.log(nukeDuplicationsEs5(array));
There are a lot of over-complicated, and slow running code here. Here's my solution:
let numbers = [1,2,3,4,4,4,4,5,5]
let filtered = []
numbers.map((n) => {
if(numbers.indexOf(n) === numbers.lastIndexOf(n)) // If only 1 instance of n
filtered.push(n)
})
console.log(filtered)
you can use this function:
function isUniqueInArray(array, value) {
let counter = 0;
for (let index = 0; index < array.length; index++) {
if (array[index] === value) {
counter++;
}
}
if (counter === 0) {
return null;
}
return counter === 1 ? true : false;
}
const array = [1,2,3,4,4,5,5];
let uniqueValues = [];
array.forEach(element => {
if(isUniqueInArray(array ,element)){
uniqueValues.push(element);
}
});
console.log(`the unique values is ${uniqueValues}`);
If its help you, you can install the isUniqueInArray function from my package https://www.npmjs.com/package/jotils or directly from bit https://bit.dev/joshk/jotils/is-unique-in-array.
My answer is used map and filter as below:
x = [1,2,3,4,2,3]
x.map(d => x.filter(i => i == d).length < 2 ? d : null).filter(d => d != null)
// [1, 4]
Object.values is supported since ES2017 (Needless to say - not on IE).
The accumulator is an object for which each key is a value, so duplicates are removed as they override the same key.
However, this solution can be risky with misbehaving values (null, undefined etc.), but maybe useful for real life scenarios.
let NukeDeps = (arr) => {
return Object.values(arr.reduce((curr, i) => {
curr[i] = i;
return curr;
}, {}))
}
I would like to answer my questions with an answer I came up with upon reading it again
const array = [1, 2, 3, 4, 4, 5, 5];
const filtered = array.filter(item => {
const { length } = array.filter(currentItem => currentItem === item)
if (length === 1) {
return true;
}
});
console.log(filtered)
//Try with this code
var arr = [1,2, 3,3,4,5,5,5,6,6];
arr = arr.filter( function( item, index, inputArray ) {
return inputArray.indexOf(item) == index;
});
Also look into this link https://fiddle.jshell.net/5hshjxvr/

Sum of string values on array object

I need to sum some object values in an array. Some can be int while others can be string ie:
JavaScript:
let array = [
{quantity: 1, amount: "24.99"}
{quantity: 5, amount: "4.99"},
]
Digging around Stack Overflow I have found this method (Im using React):
Array.prototype.sum = function (prop) {
var total = 0
for ( var i = 0, _len = this.length; i < _len; i++ ) {
total += this[i][prop]
}
return total
};
let totalQuantity = array.sum("quantity");
console.log(totalQuantity);
While that works great, I need to do the same for the string amount. Since I need to convert amount into float, the above will not work. React complains about Component's children should not be mutated.
Not being JS ninja, I thought this would do some magic:
Array.prototype.sum = function (prop) {
var newProp = parseFloat(prop);
var total = 0
for ( var i = 0, _len = this.length; i < _len; i++ ) {
total += this[i][newProp] // Surely this is wrong :(
}
return total
};
Any clean way to achieve this?
I need this:
let totalAmount = array.sum("amount");
Define a generic sum function, which is as trivial as
let sum = a => a.reduce((x, y) => x + y);
and apply it to the list of values picked from the source array:
let array = [
{quantity: 1, amount: "24.99"},
{quantity: 5, amount: "4.99"}
];
let sum = a => a.reduce((x, y) => x + y);
let totalAmount = sum(array.map(x => Number(x.amount)));
console.log(totalAmount.toFixed(2))
Please try:
Array.prototype.sum = function (prop) {
var total = 0
for ( var i = 0, _len = this.length; i < _len; i++ ) {
total += parseFloat(this[i][prop]) // Surely this will work :)
}
return total
};
const array = [
{quantity: 1, amount: "24.99"},
{quantity: 5, amount: "4.99"}
]
Array.prototype.sum = function(key) {
return this.reduce(function(total, item) {
return total + parseFloat(item[key]);
}, 0);
}
// weird javascript math ...
console.log(array.sum("amount"));
// workaround
console.log(array.sum("amount").toFixed(2));
This work fine ;)
I usually use the reduce() method for situations like these. Here is a little demo: http://codepen.io/PiotrBerebecki/pen/zKEQgL
let array = [
{quantity: 1, amount: "24.99"},
{quantity: 5, amount: "4.99"}
]
function sumProperty(arr, type) {
return arr.reduce((total, obj) => {
if (typeof obj[type] === 'string') {
return total + Number(obj[type]);
}
return total + obj[type];
}, 0);
}
let totalAmount = ( sumProperty(array, 'amount') ).toFixed(2);
console.log( totalAmount ); // 29.98
let totalQuantity = sumProperty(array, 'quantity');
console.log( totalQuantity ); // 6

How to rollup data points using underscore.js?

Say I have an array of 6 numeric data points and want to change it to an array of 3 data points where each point is the sum of a 2 of the points
[1,1,1,1,1,1] ~> [2,2,2]
What is the best way to do this with a library like underscore.js
If you wanted to do it in a generic, functional way, then
function allowIndexes(idx) {
return function(item, index) {
return index % idx;
}
}
function sum() {
return _.reduce(_.toArray(arguments)[0], function(result, current) {
return result + current;
}, 0);
}
var isIndexOdd = allowIndexes(2);
var zipped = _.zip(_.reject(data, isIndexOdd), _.filter(data, isIndexOdd));
console.log(_.map(zipped, sum));
# [ 3, 7, 11 ]
But, this will be no where near the performance of
var result = [];
for (var i = 0; i < data.length; i += 2) {
result.push(data[i] + data[i + 1]);
}
console.log(result);
I found one way. I'll gladly accept a better solution.
values = [1,1,1,1,1,1];
reportingPeriod = 2;
var combined = [];
_.inject( values, function ( total, value, index) {
if ((index + 1) % reportingPeriod === 0) {
combined.push(total + value);
return 0;
} else {
return total + value
}
}, 0);
Using lodash, you could do the following:
let arr = _.fill(Array(6), 1);
let reducer = (sum, num) => sum += num;
arr = _.chain(arr).chunk(2).map(arr => arr.reduce(reducer)).value();

fastest way to detect if duplicate entry exists in javascript array?

var arr = ['test0','test2','test0'];
Like the above,there are two identical entries with value "test0",how to check it most efficiently?
If you sort the array, the duplicates are next to each other so that they are easy to find:
arr.sort();
var last = arr[0];
for (var i=1; i<arr.length; i++) {
if (arr[i] == last) alert('Duplicate : '+last);
last = arr[i];
}
This will do the job on any array and is probably about as optimized as possible for handling the general case (finding a duplicate in any possible array). For more specific cases (e.g. arrays containing only strings) you could do better than this.
function hasDuplicate(arr) {
var i = arr.length, j, val;
while (i--) {
val = arr[i];
j = i;
while (j--) {
if (arr[j] === val) {
return true;
}
}
}
return false;
}
There are lots of answers here but not all of them "feel" nice... So I'll throw my hat in.
If you are using lodash:
function containsDuplicates(array) {
return _.uniq(array).length !== array.length;
}
If you can use ES6 Sets, it simply becomes:
function containsDuplicates(array) {
return array.length !== new Set(array).size
}
With vanilla javascript:
function containsDuplicates(array) {
return array
.sort()
.some(function (item, i, items) {
return item === items[i + 1]
})
}
However, sometimes you may want to check if the items are duplicated on a certain field.
This is how I'd handle that:
containsDuplicates([{country: 'AU'}, {country: 'UK'}, {country: 'AU'}], 'country')
function containsDuplicates(array, attribute) {
return array
.map(function (item) { return item[attribute] })
.sort()
.some(function (item, i, items) {
return item === items[i + 1]
})
}
Loop stops when found first duplicate:
function has_duplicates(arr) {
var x = {}, len = arr.length;
for (var i = 0; i < len; i++) {
if (x[arr[i]]) {
return true;
}
x[arr[i]] = true;
}
return false;
}
Edit (fix 'toString' issue):
function has_duplicates(arr) {
var x = {}, len = arr.length;
for (var i = 0; i < len; i++) {
if (x[arr[i]] === true) {
return true;
}
x[arr[i]] = true;
}
return false;
}
this will correct for case has_duplicates(['toString']); etc..
var index = myArray.indexOf(strElement);
if (index < 0) {
myArray.push(strElement);
console.log("Added Into Array" + strElement);
} else {
console.log("Already Exists at " + index);
}
You can convert the array to to a Set instance, then convert to an array and check if the length is same before and after the conversion.
const hasDuplicates = (array) => {
const arr = ['test0','test2','test0'];
const uniqueItems = new Set(array);
return array.length !== uniqueItems.size();
};
console.log(`Has duplicates : ${hasDuplicates(['test0','test2','test0'])}`);
console.log(`Has duplicates : ${hasDuplicates(['test0','test2','test3'])}`);
Sorting is O(n log n) and not O(n). Building a hash map is O(n). It costs more memory than an in-place sort but you asked for the "fastest." (I'm positive this can be optimized but it is optimal up to a constant factor.)
function hasDuplicate(arr) {
var hash = {};
var hasDuplicate = false;
arr.forEach(function(val) {
if (hash[val]) {
hasDuplicate = true;
return;
}
hash[val] = true;
});
return hasDuplicate;
}
It depends on the input array size. I've done some performance tests with Node.js performance hooks and found out that for really small arrays (1,000 to 10,000 entries) Set solution might be faster. But if your array is bigger (like 100,000 elements) plain Object (i. e. hash) solution becomes faster. Here's the code so you can try it out for yourself:
const { performance } = require('perf_hooks');
function objectSolution(nums) {
let testObj = {};
for (var i = 0; i < nums.length; i++) {
let aNum = nums[i];
if (testObj[aNum]) {
return true;
} else {
testObj[aNum] = true;
}
}
return false;
}
function setSolution(nums) {
let testSet = new Set(nums);
return testSet.size !== nums.length;
}
function sortSomeSolution(nums) {
return nums
.sort()
.some(function (item, i, items) {
return item === items[i + 1]
})
}
function runTest(testFunction, testArray) {
console.log(' Running test:', testFunction.name);
let start = performance.now();
let result = testFunction(testArray);
let end = performance.now();
console.log(' Duration:', end - start, 'ms');
}
let arr = [];
let setSize = 100000;
for (var i = 0; i < setSize; i++) {
arr.push(i);
}
console.log('Set size:', setSize);
runTest(objectSolution, arr);
runTest(setSolution, arr);
runTest(sortSomeSolution, arr);
On my Lenovo IdeaPad with i3-8130U Node.js v. 16.6.2 gives me following results for the array of 1,000:
results for the array of 100,000:
Assuming all you want is to detect how many duplicates of 'test0' are in the array. I guess an easy way to do that is to use the join method to transform the array in a string, and then use the match method.
var arr= ['test0','test2','test0'];
var str = arr.join();
console.log(str) //"test0,test2,test0"
var duplicates = str.match(/test0/g);
var duplicateNumber = duplicates.length;
console.log(duplicateNumber); //2

Categories

Resources