I have a deeply nested javascript object with an unlimited amout of children. Every child has a value.
var object = {
value: 1,
children: {
value: 10,
children:{
value: 2,
children: {...}
}
}
}
All attempts to make a recursive function did not succeed, it turned out to go down only to a lower level.
After flattening your linked list into an array, you can use Array.prototype.reduce() with an accumulator that is a tuple of min and max, starting with initial values of Infinity and -Infinity respectively to match the implementations of Math.min() and Math.max():
const object = {
value: 1,
children: {
value: 10,
children: {
value: 2,
children: {
value: 5,
children: null
}
}
}
}
const flat = o => o == null || o.value == null ? [] : [o.value, ...flat(o.children)]
const [min, max] = flat(object).reduce(
([min, max], value) => [Math.min(min, value), Math.max(max, value)],
[Infinity, -Infinity]
)
console.log(min, max)
Since children is an object with only one value (vs an array with potentially many), this is a pretty simple recursive function. The base case is when there are no children in which case both min and max are just the value. Otherwise recurse on the children to find the min and max:
var object = {
value: -10,
children: {
value: 4,
children:{
value: 200,
children: {
value: -100,
children: null
}
}
}
}
function getMinMax(obj) {
if (!obj.children || obj.children.value == undefined)
return {min: obj.value, max: obj.value}
else {
let m = getMinMax(obj.children)
return {min: Math.min(obj.value, m.min), max: Math.max(obj.value, m.max)}
}
}
console.log(getMinMax(object))
Short and simple, for min change Math.max to Math.min
var test = {
value: 1,
children: {
value: 10,
children:{
value: 2,
children: {}
}
}
}
function findMaxValue(obj) {
if (Object.keys(obj.children).length === 0) {
return obj.value;
}
return Math.max(obj.value, findMaxValue(obj.children))
}
console.log(findMaxValue(test))
Related
If you have an array of objects like so:
What's the best way to add all numerical values in each object so each one looks something like this:
{category: "A", total: 44}
So in the 0th item in the original array, 0+23+21 is 24, and is now represented by the new 'total' key.
Bearing in mind that the 'keys' with numerical values in the original array e.g. 'col2' are randomly generated (so another array like the original can have keys like 'somethingelse'.
I've attempted it with the following, but I believe it's not written correctly:
newArrayOfObjects.forEach(element => {
Object.values(element).reduce((a, b) => a + b);
});
It may be good to know but the 'key' category always exists in each object and is fixed. All other key values are numerical and there'll always be more than one.
Please check this.
const array = [
{
category: 'A',
col1: 1,
col2: 2,
col3: 3,
},
{
category: 'B',
col1: 2,
col2: 3,
col3: 4,
}
]
const result = array.map(obj => {
const total = Object.values(obj).reduce((acc, value) => {
if (typeof value === 'number') {
return acc + value;
}
return acc;
}, 0)
return {
category: obj.category,
total
}
})
console.log(result)
You could use Array.map() along with Array.reduce() to sum the numeric values in the array.
We'd create a toNumber() function to get the numeric value of any property. If this is not a number, it will return 0 (keeping the total unchanged).
let arr = [
{ a: 0, category: "a", col2: 23, col3: 21 },
{ b: 0, category: "b", x: 100, y: 10, z: 1 },
{ j: 0, category: "x", foo: 25, bar: 50, meta: 'content' },
]
function toNumber(n) {
return isNaN(n) ? 0: n;
}
function sumTotals(a) {
return a.map(({ category, ...obj}) => {
const total = Object.values(obj).reduce((total, value) => {
return total + toNumber(value);
}, 0);
return { category, total };
})
}
console.log('Totals:', sumTotals(arr))
.as-console-wrapper { max-height: 100% !important; }
arr = [{x:1}, {x:3}]
arr.reduce((accumulator, current) => accumulator + current.x, 0);
var data = [
{ "category": "A", "col0": 5, "col1": 8, "some": "thing"},
{ "category": "B", "col1": 3, "col2": 5}
];
var res = data.map((it) => {
const { category, ...rest } = it;
return {
...it,
total: Object.values(rest).reduce(
(prev, curr) =>
typeof curr === "number" ? prev + curr : prev, // add if the current value is numeric
0
)
}
});
console.log(res);
/**
[
{"category":"A","col0":5,"col1":8,"some":"tst","total":13},
{"category":"B","col1":3,"col2":5,"total":8}
]
**/
I think you are on the right way, you just need to do a bit more destructuring and type checking:
const aggregated = newArrayOfObjects.map((obj) =>
Object.entries(obj).reduce(
(newObj, [key, value]) => ({
...newObj,
...(typeof value === "number"
? { total: newObj.total + value }
: { [key]: value }),
}),
{ total: 0 }
)
);
First, you map all objects to their representations as key-value-pairs. Then you iterate over these key-value pairs and keep all non-numerical values and their respective keys, while dropping key-value-pairs with a numerical value and replacing them by a property in which you aggregate the total value.
Why does the first modification work (when I reassign properties of the object) but not the second (when I reassign the whole object)?
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
]
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
});
console.log(arr1);
arr1.forEach((item, index) => {
if (item.id === 1) {
item = {id:6,value:6} // modify fail
}
});
console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }
Consider the example below:
We create an object and assign it to variable foo and then we assign foo to bar. So, now both foo and bar refer to the same object, as illustrated in the diagram below.
let foo = { id: 1, val: "foo" };
let bar = foo;
Next let's change the val field of the object i.e. assigned to bar. We notice that the change is reflected by both the variables foo and bar and this is because both the variables refer to the same object.
let foo = {id: 1,val: "foo"};
let bar = foo;
bar.val = "bar";
console.log(foo, bar);
Next we assign a new object to bar. Notice this doesn't effect the object that foo refers to, bar is simply now referring to a different object.
let foo = { id: 1, val: "foo" };
let bar = foo;
bar = { id: 1, val: "bar" };
console.log(foo, bar);
Let's relate this to the forEach example in your question. So, in every iteration of the forEach loop, the item argument in the callback function points to an object from the array and when you change a field from this item argument it changes the object in the array but when you assign item to a new object it does nothing to the object stored in the array.
If you want to replace the entire object with a new one, there are several approaches you could take, two of which are mentioned below:
Finding the index where the object is stored and replacing it with a new object.
const arr = [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }];
const index = arr.findIndex((obj) => obj.id === 3);
if (index !== -1) {
arr[index] = { id: 6, value: 6 };
}
console.log(arr);
Another really common approach is to map over the array and create a new array with that one object replaced.
const arr = [{ id: 1, value: 1 }, { id: 2, value: 2 }, { id: 3, value: 3 }, { id: 4, value: 4 }, { id: 5, value: 5 }];
const newArr = arr.map((obj) => (obj.id === 3 ? { id: 6, value: 6 } : obj));
console.log(newArr);
Your confusion is natural, and it's to do with the difference between the actual object, and the variable.
In your foreach function, you have a variable "item" which points to the object in the array you are looping. But when you run
item = {id:6,value:6}
You are not modifying the object, but rather making the variable "item" point to a new object that you've just created.
If you want to change the actual object itself, you can do it manually by modifying the values one by one, or by copying from another object using Object.assign.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
In this snippet, I've made a few examples that hopefully illustrate the differences, and at the end your specific case being solved using Object.assign()
const a = { id: 1, value: 1 };
let b = a; // b now points to the same object as variable a
b = {id: 2, value: 2};
console.log('a', a, 'b', b) // output shows two different objects with different values
const c = {id: 3, value: 3};
const d = c;
Object.assign(c, {id: 4, value: 4});
console.log('c', c, 'd', d); // c and d both have id: 4 and value: 4 because they both point to the same object
const e = { id: 5, value: 5 };
const f = e;
e.id = 6;
console.log('e', e, 'f', f); // directly assigning the value also works
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
]
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
})
arr1.forEach((item, index) => {
if (item.id === 1) {
Object.assign(item, {id:6,value:6}) // success with assign
}
})
console.log(arr1)
Because item is a reference to the object that's currently being iterated over, modifying the reference will in turn modify the original object (hence the behaviour observed in your first code example). In the second forEach you just reassign the reference to a brand new object so you can no longer modify the original object you were iterating over.
Basically, when you loop over the array with forEach, item is a variable whose current value is a reference/pointer to the object currently being iterated over. Modifying this object modifies the original (as it's just a reference, not an actual cloned object). However, when you reassign it, the reference is no longer present because item has a new value that it points to. You could modify it like so, with success:
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
];
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
});
console.log(arr1);
arr1.forEach((item, index) => {
if (item.id === 1) {
item.id = 6;
item.value = 6;
}
});
console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }
You could do it dynamically as well, by looping over each property you wish to reassign like so:
const arr1 = [
{ id: 1, value: 1 },
{ id: 2, value: 2 },
{ id: 3, value: 3 },
{ id: 4, value: 4 },
{ id: 5, value: 5 },
];
arr1.forEach((item, index) => {
if (item.id === 1) {
item.value *= 10 // modify success
}
});
console.log(arr1);
arr1.forEach((item, index) => {
if (item.id === 1) {
let toConvert = { id: 6, value: 6};
Object.entries(toConvert).forEach(([k, v]) => item[k] = v);
}
});
console.log(arr1);
.as-console-wrapper { max-height: 100% !important; top: auto; }
For the first case, it can be understood as:
const it = { value: 10 };
const item = it;
item.value = 11;
console.log(it); // item and it point to the same object, so the object's property value is modified
second case:
const it = { value: 10 };
let item = it; // Here item and it refer to the same object
item = { value: 6 }; // But here item points to the new object (direct assignment), and it still points to the original object
console.log(item, it); // So it is still { value: 10 }, and item is already { value: 6 }
I have an array of objects. Each object has a key quantity and value. I want to duplicate each object in the array based on its quantity. Next, I want to manipulate only one of the duplicated object in the array. But on manipulating value of 1 object, value of all duplicated objects change. Here is my code:
let arr = [
{ id: 1, quantity: 3, value: 10 },
{ id: 2, quantity: 1, value: 5 },
{ id: 2, quantity: 5, value: 5 },
];
const newArr = [];
for (const a of arr) {
if (a.quantity > 1) {
let quantity = a.quantity;
a.quantity = 1;
while (quantity--) {
newArr.push(a);
}
}
}
arr = newArr;
arr[0].value = 1;
When I changed the value of arr[0] to 1, value field of arr[1] and arr[2] also changed to 1.
I have tried copying the object using spread operator and JSON.parse(JSON.parse()), but none has worked.
Because newArr.push(a) .a push to newArr ref to element of arr
You can edit same as :
let arr = [
{ id: 1, quantity: 3, value: 10 },
{ id: 2, quantity: 1, value: 5 },
{ id: 2, quantity: 5, value: 5 },
]
const newArr = []
for (const a of arr) {
if (a.quantity > 1) {
let quantity = a.quantity;
a.quantity = 1;
while (quantity--) {
newArr.push({...a})
}
}
}
arr = [...newArr]
arr[0].value = 1
console.log(arr)
// example for Memory Management
let a = { id: 1, quantity: 3, value: 10 }
let b = { id: 1, quantity: 3, value: 10 }
let c = arr[0]
let d = {...arr[0]}
console.log(a === arr[0]) // false : different allocates memory for contain value
console.log(a === b) // false : different allocates memory for contain value
console.log(c === arr[0]) // true : refer to a memory
console.log(d === arr[0]) // false : different allocates memory for contain value
interface FormValues {
key: string;
value: any;
}
const array: FormValues[] = [
{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 'str' // string
},
{
key: 'C',
value: { a: 1, b: '2' } // object
},
{
key: 'C',
value: ['a','2'] // array
},
{
key: 'C',
value: ['a','2'] // array
}
{
key: 'B',
value: true // boolean
}
]
I want to filter the objects based on field value, which can have a value of any type.
I tried to do it like this; my solution is not working for nested object checks.
const key = 'value';
const arrayUniqueByKey = [...new Map(array.map(item => [item[key], item])).values()];
output :
[{
key: 'A',
value: 1 // number
},
{
key: 'A',
value: 'str' // string
},
{
key: 'C',
value: { a: 1, b: '2' } // object
},
{
key: 'C',
value: ['a','2'] // array
},
{
key: 'B',
value: true // boolean
}]
You need to decide what makes two distinct objects "equal". In JavaScript, all built-in comparisons of objects (which includes arrays) are by reference. That means ['a','2'] === ['a','2'] is false because two distinct array objects exist, despite having the same contents. See How to determine equality for two JavaScript objects? for more information.
I will take the approach that you would like two values to be considered equal if they serialize to the same value via a modified version of JSON.stringify() where the order of property keys are guaranteed to be the same (so {a: 1, b: 2} and {b: 2, a: 1} will be equal no matter how those are stringified). I use a version from this answer to do so:
function JSONstringifyOrder(obj: any, space?: number) {
var allKeys: string[] = [];
var seen: Record<string, null | undefined> = {};
JSON.stringify(obj, function (key, value) {
if (!(key in seen)) {
allKeys.push(key); seen[key] = null;
}
return value;
});
allKeys.sort();
return JSON.stringify(obj, allKeys, space);
}
And now I can use that to make the keys of your Map:
const arrayUniqueByKey = [...new Map(array.map(
item => [JSONstringifyOrder(item[key]), item]
)).values()];
And you can verify that it behaves as you'd like:
console.log(arrayUniqueByKey);
/* [{
"key": "A",
"value": 1
}, {
"key": "A",
"value": "str"
}, {
"key": "C",
"value": {
"a": 1,
"b": "2"
}
}, {
"key": "C",
"value": [
"a",
"2"
]
}, {
"key": "B",
"value": true
}] */
Playground link to code
This will combine any duplicate keys, creating a new property values to hold the array of combined values (from like keys).
const array = [{key: 'A', value: 1},{key: 'A', value: 'str'},{key: 'C', value: { a: 1, b: '2'}},{key: 'B',value: true}]
const arrayUniqueByKey = [array.reduce((b, a) => {
let f = b.findIndex(c => c.key === a.key)
if (f === -1) return [...b, a];
else {
b[f].values = [...[b[f].value], a.value];
return b
}
}, [])];
console.log(arrayUniqueByKey)
You can use Array.prototype.reduce() combined with JSON.stringify() and finaly get the result array of values with Object.values()
const array = [{key: 'A',value: 1,},{key: 'A',value: 1,},{key: 'A',value: 'str',},{key: 'C',value: { a: 1, b: '2' },},{key: 'C',value: ['a', '2'],},{key: 'C',value: ['a', '2'],},{key: 'B',value: true}]
const result = Object.values(array.reduce((a, c) => ((a[JSON.stringify(c)] = c), a), {}))
console.log(result)
I am kinda new to javascript and today I encountered problem. Thing is, I have an array of amount of people visited every day by each hour (as you can see bellow). And I would like to find out most popular hour of day. My plan was to create a map where key is index of hour (0, 1, 2, 3, 4, 5...) and value is sum of all people who visited across all days on that hour. Problem is I'm not able to do that with my JS knowledge. Can someone give me a direction how to approach this problem? Thank you very much.
[
{date: "25.05.2018",
value: {
1: 209
2: 123
3: 890
.
.
24: 789
}
},
{date: "26.05.2018",
value: {
1: 280
2: 398
3: 450
.
.
24: 76
}
}
]
My JAVA like solution:
const { data: { data: [{ values }] } } = insightsData;
const timesMap = new Map();
values.forEach(item => {
Object.entries(item.value).forEach(([key, value]) => {
const timeValue = timesMap.get(key);
if (timeValue) {
timesMap.set(key, timeValue + value);
} else {
timesMap.set(key, value);
}
});
});
You could use Array#reduce with Array#map if you have arrays with the same length.
reduce takes an array as accumulator r and uses a logical OR || with a zero as value if an item does not exist in the accumulator.
var counts = [{ date: "26.05.2018", value: [125, 100, 200] }, { date: "27.05.2018", value: [5, 6, 7] }, { date: "28.05.2018", value: [3, 4, 5] }],
result = counts.reduce(
(r, { value }) => value.map((v, i) => (r[i] || 0) + v),
[]
);
console.log(result);
With objects as value properties.
var counts = [{ date: "26.05.2018", value: { 0: 125, 1: 100, 2: 200 } }, { date: "27.05.2018", value: { 0: 5, 1: 6, 2: 7 } }, { date: "28.05.2018", value: { 0: 3, 1: 4, 2: 5 } }],
result = counts.reduce(
(r, { value }) => Object
.entries(value)
.reduce((s, [k, v]) => {
s[k] = (s[k] || 0) + v;
return s;
}, r),
{}
);
console.log(result);
You can do iteratre over the values and add them to sum, like this
const data = {date: "26.05.2018",
value: [
125,
100,
200,
]
}
let sum = 0;
Object.values(data.value).forEach(elem => sum = sum + elem)
console.log(sum)
https://jsfiddle.net/5wwzn4yt/