minimum and maximum values of keys - group - javascript

Group by same key, find min and max value. Keys are ordered.
[{key:1,val:2}
{key:1,val:3}
{key:1,val:4}
{key:2,val:4}
{key:2,val:21}
{key:2,val:22}]
to
[2,4,1,22]
Thing is, the keys are ordered. For a real time app, i have an array of 20000. I can loop each item and check its group and act but i feel like i should not loop every object. There might be other possible solutions like picking random index and relying on the order for optimized code.
Any efficent way is appriciated. I have come up with few solutions but i have a large dataset to pass to chart, i need to optimize.

You could take a hash table for the grouping and get the min and max value by using a default array and map the new minima and maxima.
var data = [{ key: 1, val: 2 }, { key: 1, val: 3 }, { key: 1, val: 4 }, { key: 2, val: 4 }, { key: 2, val: 21 },{ key: 2, val: 1 }],
result = Object
.values(data.reduce(
(r, { key, val }) =>
(t => {
r[key] = ['min', 'max'].map((m, i) => Math[m](t[i], val));
return r;
})
(r[key] || [+Infinity, -Infinity]),
{}
))
.reduce((a, b) => a.concat(b));
console.log(result);

Related

How to find smallest and biggest value from list of elements, in most efficient way?

I have a two sets of elements, one holds list of numbers and second of names.
something like this.
A: 4,
B: 3,
C: 2,
A: 5,
C: 3,
And my task is to find elements with smallest value and highest value.
I know that i could create array of objects and sort it with map [{A: 4},{C:2}....]
But i was wondering is there any efficient ways of doing it.
Instead of creating a have object, and use three loops.
Would it be possible to replace it with something more efficient.
Like set or something where i could just call set.getItemWithMinValue, set.getItemWithMaxValue
and return would be : C:2, A:5
Sorry for silly quesition, i am still learning.
You are going to have to loop, parse the object into its values, and check if the value is greater or less.
var data = [
{ A: 4 },
{ B: 3 },
{ C: 2 },
{ A: 5 },
{ C: 3 },
];
const results = data.reduce((minMax, item) => {
const value = Object.values(item)[0];
if (!minMax) {
minMax = {
min: { value, item },
max: { value, item },
}
} else if (minMax.min.value > value) {
minMax.min = { value, item };
} else if (minMax.max.value < value) {
minMax.max = { value, item };
}
return minMax;
}, null);
console.log(results.min.item);
console.log(results.max.item);
This would be one way of doing it. Caution: the array will be changed (sorted) in the course of the script.
const arr=[{A: 4},{B: 3},{C: 2},{A: 5},{C: 3}],
val=o=>Object.values(o)[0];
arr.sort((a,b)=>val(a)-val(b));
console.log(arr[0],arr[arr.length-1])
if you have this data
const arr = [{A:2},{A: 4},{B: 3},{C: 2},{A: 5},{C: 3}];
you can iterate over it even if you don't know the property of the objects like this.ç
const arr = [{A:2},{A: 4},{B: 3},{C: 2},{A: 5},{C: 3}];
const result = arr.sort((prev, next) => {
const prevProp = Object.getOwnPropertyNames(prev);
const nextProp = Object.getOwnPropertyNames(next);
return prev[prevProp] - next[nextProp]
});
console.log('max',result[0]);
console.log('min',result[result.length - 1]);
You could take a single loop with getting the entries from the object.
This approach expects only a singl min and max value.
const
array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }];
let min, max;
for (const object of array) {
const [[k, v]] = Object.entries(object);
if (!min || min[1] > v) min = [k, v];
if (!max || max[1] < v) max = [k, v];
}
console.log('min', Object.fromEntries([min]));
console.log('max', Object.fromEntries([max]));
This approach respects more than one name with same min or max value.
const
array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }, { F: 2 }];
let min, max;
for (const object of array) {
const v = Object.values(object)[0];
if (!min || min[1] > v) min = [[object], v];
else if (min[1] === v) min[0].push(object);
if (!max || max[1] < v) max = [[object], v];
else if (max[1] === v) max[0].push(object);
}
console.log('min', min[0]);
console.log('max', max[0]);
This is probably premature optimisation but I'm going to leave it here in case it's useful to anyone. When you need both the minimum and the maximum, you can save one comparison for every two objects (that is, cut it down from two comparisons per object to three comparison per two objects) by taking the objects in pairs, comparing the pair of objects with each other (one comparison) and then comparing only the larger one with the accumulated maximum and only the smaller one with the accumulated minimum.
To start the procedure, you have the options of initialising both the maximum and the minimum with the first element, or of initialising the maximum as the larger of the first two elements and the minimum as the smaller of the two. If you know in advance how many elements there are, you can choose one or the other of these depending on whether the number of objects to scan is odd or even, so that the scan will always be over complete pairs.
The code is slightly more complicated, so it's only worth doing if the performance benefit is actually significant in your application. Or, I suppose, as a learning exercise.
You can use Array.prototype.reduce():
const array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }]
const result = array.reduce((a, c) => {
const [v, max, min] = [c, a.max, a.min].map((o) => Object.values(o)[0])
a.max = max > v ? a.max : c
a.min = min < v ? a.min : c
return a
},
{ max: {}, min: {} })
console.log(result)
Or you can use Array.prototype.sort():
const array = [{ A: 4 }, { B: 3 }, { C: 2 }, { A: 5 }, { C: 3 }]
const arraySorted = array.sort((a, b) => {
const [aValue, bValue] = [a, b].map((o) => Object.values(o)[0])
return bValue - aValue
})
const result = {
max: arraySorted[0],
min: arraySorted[arraySorted.length - 1],
}
console.log(result)

Get the sum of property of nested array of objects with infinite depth

I have an array of objects representing something similar to the system's hard-drive folder structure. Data is represented in a nested array of objects. Each object is a folder containing some files and folders. I know exactly the sum of the size of files directly placed in each node. But I don't know how much space a node has taken containing its child nodes.
Here is an example of the data:
[{
id: 1,
name: 'root',
filesSize: 123456,
children: [{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
children: [{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [{
id: 99999,
name: 'child m - n',
filesSize: ...,
},
.
.
.
}]
}]
}]
I tried to use Array.reduce, but it doesn't help me because it only iterates on direct children of object - not n level of the nested array. Something like this:
const parentSize = typeof parent['total'] !== 'undefined' ? parent['total'] : parent.filesSize;
parent['total'] = children.reduce((sum, child) => {
return sum + (typeof child.total !== 'undefined' ? child.filesSize : child.total);
}, parentSize);
What am I missing?
Using reduce is fine, but you need:
recursion, so that this reduce is also called on the children when needed.
to always assign to the total. Checking that it already exists is not useful, as it will not. And if it does, it is risky to rely on it, as you don't know whether the tree had been modified after that property was added.
let tree = [{id: 1,name: 'root',filesSize: 123456,children: [{id: 2,name: 'child 1',filesSize: 789654,}, {id: 3,name: 'child 2',filesSize: 321123,children: [{id: 4,name: 'child 3 - 1',filesSize: 88888,}, {id: 5,name: 'child 3 - 2',filesSize: 99999,children: [{id: 99999,name: 'child m - n',filesSize: 1234}]}]}]}];
let totalSize = tree.reduce(function recur(sum, child) {
return sum + (child.total = (child.children ?? []).reduce(recur, child.filesSize));
}, 0);
// The returned size can be interesting when the top level has
// multiple entries (which is not the case in your example):
console.log(totalSize);
console.log(tree);
Just a note:
Assuming your size unit is bytes:
1 petabyte is 1_000_000_000_000_000 bytes (or 1e3 ** 5): so your sum (which is a JS number) can safely hold Number.MAX_SAFE_INTEGER / (1e3 ** 5), which is about 9. If you expect that your size will approach 9 petabytes, you should use a BigInt type to store the sum instead of a number.
The other answers demonstrate recursive approaches, which are terse and elegant, but will fail if your hierarchy is too numerous (stack overflow!).
Here's an iterative alternative:
TS Playground
function getTotal (containerNode) {
let total = 0;
const stack = [containerNode];
while (stack.length > 0) {
const node = stack.pop();
// Already available, skip children
if (typeof node.total === 'number') {
total += node.total;
continue;
}
total += node.filesSize;
if (!node.children?.length) continue;
for (const child of node.children) stack.push(child);
}
return total;
}
// Example usage:
const nodes = [
{
id: 1,
name: 'root',
filesSize: 123456,
children: [
{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
total: 321123 + 88888 + 99999 + 34523,
children: [
{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [
{
id: 99999,
name: 'child m - n',
filesSize: 34523,
},
// ...
],
},
],
},
],
},
];
function test (containerNode, expectedTotal) {
const actual = getTotal(containerNode);
return `${containerNode.id}: ${actual === expectedTotal ? 'pass' : 'fail'}`;
}
const results = [
test(
nodes[0].children[1].children[1].children[0],
34523,
),
test(
nodes[0].children[1].children[1],
99999 + 34523,
),
test(
nodes[0].children[1].children[0],
88888,
),
test(
nodes[0].children[1],
321123 + 88888 + 99999 + 34523,
),
test(
nodes[0].children[0],
789654,
),
test(
nodes[0],
123456 + 789654 + 321123 + 88888 + 99999 + 34523,
),
];
for (const result of results) console.log(result);
I took the liberty of doubling the objects from top to bottom so it splits at the root four levels deep.
/**#function
* #Name totalFileSize
* #Description - This function accepts array of objects and any nested array of
* objects as well. It will extract a number value of a given key
* and recursively search all sub-arrays as well. It will return the sum of all
* extracted values.
* #param {array<object>} objArr - An array of objects
* #param {string} prop - A key/property with a number value
* #returns {number} - A sum of all extracted number values
*/
Pass the array of objects and the property you want to get the total sum of.
// data↘️ ↙️"filesSize"
const totalFileSize = (objArr, prop) =>
Next, run each object through .map() and then convert each object into an array of pairs by using Object.entries(). An array of pairs is a 2D array in which the sub-arrays consist of two elements:
[[key, value], [key, value],...]
objArr.map(obj => Object.entries(obj)
// [{A: 1, B: 'z'}, {...},...] => [["A", 1], ["B", "z"], [...],...]
Now we have a 2D array witch is easier to work with using Array methods. The logical choice of methods is .reduce() since we need a single result. The second parameter represents the element of the current iteration, note it is destructured into an array (specifically a key/value pair). In this form we can easily construct more granular expressions.
// ↙️On each iteration, this value accumulates when a match is made
.reduce((sum, [key, val]) =>
// property↗️ ↖️value
The destructured values allow us to write a more terse and direct way. The heart of this function is a if/else if/else statement as a ternary conditional.
/* if the current key matches prop ("filesSize"), then add sum and the current value
(val).*/
key == prop ? sum + parseInt(val) :
/* Note: the value needed to be converted to a number because the + operator coerced
it into a String.*/
Now onto the 2nd (or else if) of the ternary operator. I noticed there's a running total property in the other answers, so here's where to get it: obj.total = sum + parseInt(totalFileSize(val, prop)). It's the sum and the return value of each recursive call. I don't think the total should be calculated for each object (all you get is a duplicate value of filesSize).
/* else if val is an array recursively invoke totalFileSize() and pass val and
prop in...*/
Array.isArray(val) ? obj.total = sum + parseInt(totalFileSize(val, prop)) :
// ...then convert it's return into a number and add it to sum
else add 0 to sum. Any non-matching values are no longer a worry.
0, 0))
// The last zero is the initial value of `.reduce()`
Last thing to do is cleanup the return value. The data was doubled so I can demonstrate the functions ability to handle multiple branches. At this point all numbers from "filesSize" are now two totals in an array. This last step adds all branches together.
.reduce((total, current) => total + current);
const data =[{"id":1,"name":"root","filesSize":123456,"children":[{"id":2,"name":"child 1","filesSize":789654},{"id":3,"name":"child 2","filesSize":321123,"children":[{"id":4,"name":"child 3 - 1","filesSize":88888},{"id":5,"name":"child 3 - 2","filesSize":99999,"children":[{"id":6,"name":"child 4 - 1","filesSize":325941}]}]}]},
{"id":7,"name":"root","filesSize":654321,"children":[{"id":8,"name":"child 1","filesSize":978855},{"id":9,"name":"child 2","filesSize":123321,"children":[{"id":10,"name":"child 3 - 1","filesSize":11111},{"id":11,"name":"child 3 - 2","filesSize":66666,"children":[{"id":12,"name":"child 4 - 1","filesSize":18756}]}]}]}];
const totalFileSize = (objArr, prop) =>
objArr.map(obj => Object.entries(obj)
.reduce((sum, [key, val]) =>
key == prop ? sum + parseInt(val) :
Array.isArray(val) ? obj.total = sum + parseInt(totalFileSize(val, prop)) :
0, 0))
.reduce((total, current) => total + current);
console.log(`Input array is doubled at the root so this the total sum of 12 objects not 6 like OP example.`);
console.log(totalFileSize(data, "filesSize"));
console.log(data);
const data = [{
id: 1,
name: 'root',
filesSize: 123456,
children: [{
id: 2,
name: 'child 1',
filesSize: 789654,
},
{
id: 3,
name: 'child 2',
filesSize: 321123,
children: [{
id: 4,
name: 'child 3 - 1',
filesSize: 88888,
},
{
id: 5,
name: 'child 3 - 2',
filesSize: 99999,
children: [{
id: 99999,
name: 'child m - n',
filesSize: 12345,
}]
}]
}]
}];
function calculateTotals(d) {
let total = d.filesSize;
if (d.children)
d.children.forEach(c => total += calculateTotals(c));
return d.total = total;
}
data.forEach(d => calculateTotals(d));
console.log(data);

What's the simplest way to return the key with the largest value in an array of objects?

I'm making a simple RPG and trying to calculate which attribute should be increased when a character levels up. They have a potential limit for each attribute and I want to increment the attribute that is furthest from its potential.
I can loop through each attribute and subtract its current value from its potential value to get the difference. I can then push the difference to an array. The result looks like:
[
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
]
Charisma is the key with the highest difference, so how can I evaluate this and return the name of the key (not the value itself)?
EDIT: Here is the logic which is used to get the array:
let difference = [];
let key;
for (key in currentAttributes) {
difference.push({[key]: potentialAttributes[key] - currentAttributes[key]});
};
Simple reduce with Object.entries
const items = [
{ Strength: 5 },
{ Dexterity: 6 },
{ Constitution: 3 },
{ Wisdom: 4 },
{ Charisma: 8 }
]
const biggest = items.reduce((biggest, current, ind) => {
const parts = Object.entries(current)[0] //RETURNS [KEY, VALUE]
return (!ind || parts[1] > biggest[1]) ? parts : biggest // IF FIRST OR BIGGER
}, null)
console.log(biggest[0]) // 0 = KEY, 1 = BIGGEST VALUE
Your data model is a bit weird with the array with objects, a better model would just be an object.
const items = {
Strength: 5,
Dexterity: 6,
Constitution: 3,
Wisdom: 4,
Charisma: 8
}
const biggest = Object.entries(items)
.reduce((biggest, current, ind) => {
const parts = current
return (!ind || parts[1] > biggest[1]) ? parts : biggest
}, null)
console.log(biggest[0])
You could create an object, take the entries and reduce the entries by taking the entry with the greatest value. At the end take the key from the entry.
var data = [{ Strength: 5 }, { Dexterity: 6 }, { Constitution: 3 }, { Wisdom: 4 }, { Charisma: 8 }],
greatest = Object
.entries(Object.assign({}, ...data))
.reduce((a, b) => a[1] > b[1] ? a : b)
[0];
console.log(greatest);
Sort in descending order and grab the first item:
let attributes = [
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
];
//for convenience
const getValue = obj => Object.values(obj)[0];
//sort descending
attributes.sort((a, b) => getValue(b) - getValue(a));
let highest = attributes[0];
console.log(Object.keys(highest)[0]);
Alternatively, go though the array and find the highest score:
let attributes = [
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
];
//for convenience
const getValue = obj => Object.values(obj)[0];
//find the highest score
let highest = attributes.reduce((currentHighest, nextItem) => getValue(currentHighest) > getValue(nextItem) ? currentHighest : nextItem);
console.log(Object.keys(highest)[0]);

How to remove any objects that appear more than once in an array of objects?

If I have an array like:
[
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
]
And I want to return an array that contains any objects that appear only once. So for this example, the desired output would be:
[
{
id: 1,
title: 'foo'
},
{
id: 4,
title: 'bantz'
}
]
I have tried a few different approaches that I have found to solve this using reduce() and indexOf(), like this solution, but they do not work with objects for some reason.
Any assistance would be greatly appreciated.
You could use a Map to avoid having to look through the array again and again, which would lead to inefficient O(n²) time-complexity. This is O(n):
function getUniquesOnly(data) {
return Array.from(
data.reduce( (acc, o) => acc.set(o.id, acc.has(o.id) ? 0 : o), new Map),
(([k,v]) => v)
).filter( x => x );
}
var data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
console.log(getUniquesOnly(data));
Do something like this:
const data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a, index) =>
result.concat(arr.some(b => a !== b && isEqual(a, b)) ? [] : a)
, []);
console.log(unique(data));
In this case, we loop through each element to reduce(), and before we add it, we see if another version of it exists in the array before adding it. We have to make sure that we are also not being equal without ourselves (otherwise we'd get an empty array).
isEqual() is a separate function to make it easy to customize what "equal" means.
As written, each element in data is unique, they're all separate objects. data[0] === data[4] is false, even though they have the same data. You must compare the data inside to determine if they're duplicates or not. As Paulpro mentioned earlier, {} === {} is also false, because they're two different objects, even though their values are the same.
console.log({} === {});
console.log({ a: 1 } === { a: 1 });
In the example version of isEqual(), I considered them equal if they had the same id.
Answer to previous version of the question
Do something like this:
const data = [
{
id: 1,
title: 'foo'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
},
{
id: 4,
title: 'bantz'
},
{
id: 2,
title: 'bar'
},
{
id: 3,
title: 'bat'
}
];
const isEqual = (a, b) => a.id === b.id;
const unique = (arr) => arr.reduce((result, a) =>
result.concat(result.some(b => isEqual(a, b)) ? [] : a)
, []);
console.log(unique(data));
I split isEqual() to it's own function so you could easily define what "equal" means. As someone pointed out, technically all of those are unique, even if the data is different. In my example, I defined equal ids to mean equal.
I then use reduce to go through each and build an object. Before I add it to the array (via concat()), I loop through all of them with some() and go until either I find one that is equal (which I wouldn't include) or none are equal and I add it.
A straightforward implementation would look something like this:
Create an empty set (in this case an array) to contain unique values by whatever metric you define (I.E. deep comparison or comparing by a unique value like the "id")
Loop over the list of values
Whenever you find a value that is not contained within the set of unique values, add it
That is essentially how the solution you posted works, except that all of the values in your array are -- in JavaScript's eyes -- unique. Because of this you need to define your own way to compare values.
The .reduce method can be used like so:
function areEqual(a, b) { /* define how you want the objects compared here */ }
function contains(a, lst) {
return lst.reduce(function (acc, x) {
return acc || areEqual(a, x);
}, false);
}
function getUnique(lst) {
return lst.reduce(function (acc, x) {
if(!contains(x, acc))
{
acc.push(x);
}
return acc;
}, []);
}
You may want to look at how JavaScript object comparison works. For deep comparison specifically (which it sounds like you want) I would look at existing answers.

rank while looping through an array javascript

var testArray = [{
value: john,
count: 5
},
{
value: henry,
count: 2
},
{
value: bill,
count: 10
}]
testArray.map(function(value) {
//otherfunctions
})
So i have a function where i am already mapping through an array of objects similar to the above. I want to add a third value to the objects of rank based upon the count.
My current thought is to finish my map that i am already doing and then re sort the data based upon the count and then assign a rank based upon the the sorted position in the array. But this seems to be long winded given i am already mapping the array?
Vanilla JS:
var testArray = [{
value: john,
count: 5
},
{
value: henry,
count: 2
},
{
value: bill,
count: 10
}]
let newArray = testArray.map(function(item) {
return {
value: item.value,
count: item.count,
newProperty: 'x'
}
}).sort(function(x, z) {
return x.count - z.count;
});
ES6:
let newArray = testArray
.map(item => {
return {
...item,
newProperty: 'x'
}
}).sort((x, z) => x.count - z.count);
P.S. this is the functional way of doing this computation, should have a o(n*nlog n) time, you could do it in a o(n) with an imperative approach, but this is easier to read/understand in my opinion.
EDIT 1
After comment from author: wants to add the current count to the items (cannot think of a case where this would be necessary) but just to indulge:
let newArray = testArray
.map((item, index) => {
return {
...item,
currentCount: index
}
}).sort((x, z) => x.count - z.count);
Read more about map

Categories

Resources