filtering nested objects in javascript - javascript

I have the following nested objects in javascript and I would like to filter based on a specific condition (value < 7). How can I do this in JS?
Input:
const data = [
{key: 'a', value: [{key: 'aa', value: 6}, {key: 'ab', value: 10}]},
{key: 'b', value: [{key: 'ba', value: 3}, {key: 'bb', value: 11}]}
]
Expected output:
const data = [
{key: 'a', value: [{key: 'aa', value: 6}]},
{key: 'b', value: [{key: 'ba', value: 3}]}
]
I have tried already some and filter but it is not working as expected.
Thanks!

Assuming you can use arrow functions:
const filteredData = data.map(item => {
item.value = item.value.filter(subItem => subItem.value < 7);
return item;
});
And if you're working in an environment where you can use the spread operator:
const filteredData = data.map(item => ({
...item,
value: item.value.filter(subItem => subItem.value < 7),
}));

Recursive function, without touching the original object
const data = [
{key: 'a', value: [{key: 'aa', value: 6}, {key: 'ab', value: 10}]},
{key: 'b', value: [{key: 'ba', value: 3}, {key: 'bb', value: 11}]}
];
const filterData = (dataOriginal, minValue) => {
//INFO: create copy of the object
const d = JSON.parse(JSON.stringify(dataOriginal));
//-----
return d.filter((item,i) => {
if(typeof item.value === 'number'){
return item.value < minValue;
} else if(Array.isArray(item.value)) {
const f = filterData(item.value, minValue);
d[i].value = f;
return !!f.length;
}
});
};
const filtered = filterData(data, 7);
console.log(filtered);

There are many ways, but how about e.g. this:
for (const o of data) o.value = o.value.filter(e => e.value < 7);

Related

How can i subtract object values having similar keys in an array of object? (not to be confused with removing duplicates) [duplicate]

This question already has answers here:
How to remove all duplicates from an array of objects?
(77 answers)
Closed 1 year ago.
Let's say I have an object
const testArray = [{key: 'a', value: 5}, {key: 'b', value: 1}, {key: 'a', value: 2}]
what I want is
newArray = [{key: 'a', value: 3}, {key: 'b', value: 1}]
What I tried is
testArray.reduce((acc, cur, index) => {
const exists = !!acc && acc.find(item => item.key === cur.key)
if (!exists){
acc.push(cur)
} else {
// can't figure out what i should do here
}
return acc;
}, [])
Or Else any other easy solution is appreciated.
Thanks
Note that when you use Array#find, the result value will keep the reference. So when you change the content it will affect the array acc.
const testArray = [{key: 'a', value: 5}, {key: 'b', value: 1}, {key: 'a', value: 2}];
const result = testArray.reduce((acc, cur) => {
const exists = acc.find(item => item.key === cur.key)
if (!exists) acc.push(cur);
else exists.value -= cur.value;
return acc;
}, []);
console.log(result);
You can take advantage of Array.prototype.reduce combine with Array.prototype.findIndex to update the result array like this
const testArray = [{key: 'a', value: 5}, {key: 'b', value: 1}, {key: 'a', value: 2}]
let result = testArray.reduce((accumulator, current, index)=>{
let itemExists = accumulator.findIndex(item => {
return item.key == current.key;
});
if(itemExists !== -1){
current = {...current, value: accumulator[itemExists].value - current.value};
accumulator[itemExists] = current;
return accumulator;
} else {
return [...accumulator,current];
}
},[])
console.log(result);
The idea is when the current item doesn't exists in the result array we just add it to the array otherwhise we update the value of the existing one by updating It value key with the existing one value key minus the value key of the current item value
You can use Map where the keys are the key property from the objects and values are the entire object.
Now for every object in the testArray check if its key is present in the Map, if it is present, then update only the value, and it is not present set the entire value.
Solution Using Map
const testArray = [{ key: "a", value: 5 }, { key: "b", value: 1 }, { key: "a", value: 2 }];
const res = Array.from(testArray.reduce(
(m, o) => (
m.has(o.key)
? m.set(o.key, { ...m.get(o.key), value: m.get(o.key).value - o.value })
: m.set(o.key, { ...o }),
m
),
new Map()
).values());
console.log(res)
Same solution but in a more readable format
const testArray = [{ key: "a", value: 5 }, { key: "b", value: 1 }, { key: "a", value: 2 }];
const res = Array.from(testArray.reduce(
(m, o) => {
if (m.has(o.key)) {
const currVal = m.get(o.key);
m.set(o.key, {...currVal, value: currVal.value - o.value})
} else {
m.set(o.key, {...o})
}
return m;
},
new Map()
).values());
console.log(res)
One Liner Using Objects
If the key is not present in the object then create an object where the value property is double the actual value.
Now for every object, just subtract the current value with the already existing value.
const
testArray = [{ key: "a", value: 5 }, { key: "b", value: 1 }, { key: "a", value: 2 }],
res = testArray.reduce((m, {key, value}) => (m[key] ??= ((value) => ({key, value}))(2*value), m[key].value -= value, m), {});
console.log(Object.values(res))

How to use reduce to find maximum from array of objects with array of objects

If I have the following
arr = [
{key: "a",
values : [{key: "aa", value: 2}, {key: "bb", value: 5}]},
{key: "b",
values : [{key: "cc", value: 7}, {key: "dd", value: 3}]}
]
How to use reduce in javascript to find the maximum from the nested objects? The answer should be 7 in the above case.
I currently am able to use a loop to achieve this:
let max = 0;
let findDataMax = function(d) {
for (let i = 0; i < d.length; i++) {
let currArr = d[i].values;
let tempArr = []
currArr.forEach((d) => tempArr.push(+d.value));
if (Math.max(...tempArr) > max) {
max = Math.max(...tempArr);
}
}
}
let arr = [
{key: "a", values : [{key: "aa", value: 2}, {key: "bb", value: 5}]},
{key: "b",values : [{key: "cc", value: 7}, {key: "dd", value: 3}]}
];
findDataMax(arr);
console.log(max);
I would prefer to use other methods other than reduce for this, but if you have to, then you can set the accumulator as -Infinity to begin with (this way any value compared with the accumulator will be bigger than -Infinity). For each object in your array, you can find the max by mapping the array of values to an array of value numbers from each object, and then spreading these numbers into a call to Math.max(). You can then compare whether or not this is larger than the current maximum, and if it is, return that as the new value to use as the accumulator, otherwise, use the old accumulator value:
const arr = [ {key: "a", values : [{ key: "aa", value: 2}, { key: "bb",value: 5}]}, {key: "b", values : [{ key: "cc", value: 7}, { key: "dd", value: 3}]} ];
const max = arr.reduce((max, {values}) => {
const newMax = Math.max(...values.map(({value}) => value));
return newMax > max ? newMax : max;
}, -Infinity);
console.log(max);
As previously mentioned, I would probably use a different approach to .reduce(), such as .flatMap() to grab all object value numbers, which you can then spread into a call to Math.max():
const arr = [ {key: "a", values : [{ key: "aa", value: 2}, { key: "bb",value: 5}]}, {key: "b", values : [{ key: "cc", value: 7}, { key: "dd", value: 3}]} ];
const max = Math.max(...arr.flatMap(({values}) => values.map(({value}) => value)));
console.log(max);
I don't know if the use of the reduce function is a clean solution for this problem but here you have it:
const arr = [{ key: 'a', values: [{ key: 'aa', value: 2 }, { key: 'bb', value: 5 }] }, { key: 'b', values: [{ key: 'cc', value: 7 }, { key: 'dd', value: 3 }] }];
// O(n * b)
const maxValue = arr.reduce((prev, item) => item
.values.reduce((subPrev, subItem) => (subItem.value > subPrev ? subItem.value : subPrev), prev), 0);
console.log(maxValue); // 7

Values from three arrays - Add or subtract

I have three arrays:
Each array has a "key" and a "value" for every element, for example
array:
0: {key: "000001", value: 10}
1: {key: "000002", value: 20}
// other values
array1:
0: {key: "000001", value: 5}
1: {key: "000003", value: 15}
// other values
array3:
0: {key: "000001", value: 10}
1: {key: "000003", value: 3}
// other values
And this structure is the same for the three different arrays.
Now I need to check if in these three array there are keys equal and sum or subtract the field "value"
For example:
array, array1 and array2 have the key= "000001" in all the three
arrays so I sum the three value = 25.
In this way I will write only the "key" field and the sum of the "value"
I hope I was clear
I have tried in this way, but it doesn't work as I would like:
let outputTot = [];
output.filter((newDataOutput) => {
return output1.filter((newDataOutput1) => {
return output2.filter((newDataOutput2) => {
if(newDataOutput.key == newDataOutput1.key && newDataOutput.key == newDataOutput2.key){
outputTot.push({
'key': newDataOutput.key,
'value': newDataOutput.value + newDataOutput1.value + newDataOutput2.value
})
}
else if(newDataOutput.key == newDataOutput1.key){
outputTot.push({
'key': newDataOutput.key,
'value': newDataOutput.value + newDataOutput1.value
})
}
else if(newDataOutput.key == newDataOutput2.key){
outputTot.push({
'key': newDataOutput.key,
'value': newDataOutput.value + newDataOutput2.value
})
}
else if(newDataOutput1.key == newDataOutput2.key){
outputTot.push({
'key': newDataOutput1.key,
'value': newDataOutput1.value + newDataOutput2.value
})
}
})
})
})
I had thought of calculating all 4 cases but obviously it doesn't work like that.
How could I do?
EDIT:
What I expect:
my outputTot like:
> [0] key: "000001", value: 25
> [1] key: "000002", value: 20
> [2] kye: "000003", value: 18
I assume, you need reduce function to achieve the expected output. You can first group by the data using key and then take Object.values to get array out of it.
const arr = [{key: "000001", value: 10},{key: "000002", value: 20}];
const arr1 = [{key: "000001", value: 5},{key: "000002", value: 20}];
const arr2 = [{key: "000001", value: 10},{key: "000003", value: 3}];
const result = Object.values([...arr,...arr1,...arr2].reduce((a,{key, value}, i)=>{
a[key] ??= {key, value:0};
i==2 ? a[key].value-=value : a[key].value+=value;
return a;
},{}));
console.log(result);

JS find all sequences in array

Giving an array (fixed length) of objects with the following structures:
{type: 'A', value: 1}
or
{type: 'B', text: 'b'}
What is the easiest way to find all the sequences of objects of type 'A' and return their indices?
An example:
For the following array:
[
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
]
The output should be the following array:
[
{startIndex: 0, startValue: 1, length: 2},
{startIndex: 3, startValue: 11, length: 3},
{startIndex: 7, startValue: 10, length: 1},
]
I guess the naive implementation would be to iterate with forEach and have many complex conditions but is there a simpler technique?
Thanks.
You could use reduce like this. Add a variable prev to keep track of what the previous type was. If the current type is the type you are looking for: Add an object if the previous item had a different type. Else, just increment the length property
let input = [{type:'A',value:1},{type:'A',value:2},{type:'B',text:'b1'},{type:'A',value:11},{type:'A',value:12},{type:'A',value:13},{type:'B',text:'b2'},{type:'A',value:10},{type:'B',text:'b3'}],
prev;
const output = input.reduce((acc, { type, value }, i) => {
if (type === 'A') {
if (prev !== type) {
acc.push({ startIndex: i, startValue: value, length: 1 })
} else {
acc[acc.length - 1].length++
}
}
prev = type
return acc;
}, [])
console.log(output)
You could reduce the array and chweck the value and the type for creating a new group.
var array = [{ type: 'A', value: 1 }, { type: 'A', value: 2 }, { type: 'B', text: 'b1' }, { type: 'A', value: 11 }, { type: 'A', value: 12 }, { type: 'A', value: 13 }, { type: 'B', text: 'b2' }, { type: 'A', value: 10 }, { type: 'B', text: 'b3' }],
result = array.reduce((r, { type, value }, i, a) => {
var previous = a[i - 1] || {},
last = r[r.length - 1];
if (!isFinite(value) || type !== 'A') return r;
if (previous.type !== type) {
r.push({ startIndex: i, startValue: value, length: 1 });
return r;
}
if (value === a[last.startIndex].value + last.length) last.length++;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Just use a forEach loop. its simple to read and doesnt complicate your code.
let arr = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
]
function findSequences(arr, character) {
let seq = []
let seqStarted = false;
let seqI = 0;
arr.forEach((el, i) => {
if (el.type == character) {
if (seqStarted == true) {
seq[seqI].length += 1;
} else {
seqStarted = true;
seq.push({
startIndex: i,
startValue: el.value,
length: 1
});
}
} else {
if (seqStarted) {
seqStarted = false;
seqI++;
}
}
})
return seq;
}
console.log(findSequences(arr, 'A'))
I don't think it can get any simpler that just iterating over the array using a for loop and constructing your desired answer. Also, this solution has linear complexity, as you're traversing the array just once. Not sure why you'd need "many complex conditions" though.
Something like this seems pretty OK to me:
const arr = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
];
const result = [];
for (let [index, el] of arr.entries()) {
if (el.type === 'A') {
// account for the first entry found
if (result.length === 0) {
result.push({startIndex: index, length: 1, startValue: el.value});
} else {
const lastSequence = result[result.length - 1];
// check if the we are in a sequence
if (lastSequence.startIndex + lastSequence.length === index) {
lastSequence.length += 1;
} else {
// if we are not in a sequence - create a new one
result.push({startIndex: index, length: 1, startValue: el.value});
}
}
}
}
console.log(result);
For your specified array:
var arr =[
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
];
arr.reduce((result, current, index) => {
if(current.type == 'A'){
if(result.length == 0 || index - result[result.length - 1].length != result[result.length - 1]. startIndex){
result.push({startIndex: index, startValue: current.value, length: 1})
}
else{
result[result.length - 1].length++
}
}
return result;
}, [])
You can use declarative approach something like this instead imperative with forEach: You can use declarative approach something like this instead imperative with forEach:
const a = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
];
var filtered = a.map((v,i) => {
return v.type == 'A' ? {startIndex: i,startValue: v.value} : null
}).filter(x => x).reduce((acc,v) => {
if (acc.filter(x => x.startIndex + x.length == v.startIndex).length > 0){
acc[acc.length-1].length ++;
} else {
v.length = 1;
acc.push(v);
}
return acc;
},[]);
console.log(filtered);
Though there are already lots of good answers here, I thought I'd add one which addresses a more general case of segmenting a list. With this code, it's possible to specify a segmenter function, which compares two items and determines whether a new segment should be started.
Once you have these segments, getting to the final answer you require is quite simple.
const data = [
{type: 'A', value: 1}, {type: 'A', value: 2}, {type: 'B', text: 'b1'},
{type: 'A', value: 11}, {type: 'A', value: 12}, {type: 'A', value: 13},
{type: 'B', text: 'b2'}, {type: 'A', value: 10}, {type: 'B', text: 'b3'}
]
const segmentBy = segmenter => items => {
const segmentReducer = (prev = [], curr) => {
let lastSegment = [];
let lastItem = null;
try {
lastSegment = prev[prev.length - 1];
lastItem = lastSegment[lastSegment.length - 1];
} catch (e) {
return [...prev, [curr]];
}
const requiresNewSegment = segmenter(lastItem, curr);
if (requiresNewSegment) {
return [...prev, [curr]];
}
return [...prev.slice(0, prev.length - 1), [...lastSegment, curr]];
};
return items.reduce(segmentReducer, []);
};
const addIndex = a => a.map((x, i) => ({...x, index: i}))
const segmentByType = segmentBy((a, b) => a.type !== b.type);
const segments = segmentByType(addIndex(data));
const result = segments
.map(segment => ({
startIndex: segment[0].index,
startValue: segment[0].value || null,
length: segment.length
}))
.filter(x => x.startValue !== null)
console.dir(result);

transform array of objects in javascript

Im new in JS and I hope you help me) I need to transform array of objects in this way:
const arr = [
{id: 'key1', value: 1 },
{id: 'key2', value: [1,2,3,4]}
...
]
const transformedArr = [
{key1: 1},
{key2: 1},
{key2: 2},
{key2: 3},
{key2: 4},
....
]
How can I do it?
Since you are new to JS ..
Good Old JS with for loops might be easy for you to understand
const arr = [
{id: 'key1', value: 1 },
{id: 'key2', value: [1,2,3,4]}
]
const transformedArr =[]
for(var i = 0 ; i < (arr.length); i++){
var valArr = arr[i].value
if( Array.isArray(valArr) ){ // to check if the value part is an array
for(var j=0 ; j < valArr.length ; j++){
transformedArr.push({id: arr[i].id,value:valArr[j] })
}
}else{
transformedArr.push({id: arr[i].id,value:valArr })
}
}
console.log(transformedArr)
You can use ES6 spread syntax ... with map() method.
const arr = [
{id: 'key1', value: 1 },
{id: 'key2', value: [1,2,3,4]}
]
var result = [].concat(...arr.map(({id, value}) => {
return Array.isArray(value) ? value.map(e => ({[id]: e})) : {[id]: value}
}))
console.log(result)
You can also use reduce() instead of map() method.
const arr = [
{id: 'key1', value: 1 },
{id: 'key2', value: [1,2,3,4]}
]
var result = arr.reduce(function(r, {id, value}) {
r = r.concat(Array.isArray(value) ? value.map(e => ({[id]: e})) : {[id]: value})
return r;
}, [])
console.log(result)
This proposal features Array.concat, because it add items without array and arrays to an array.
const
array = [{ id: 'key1', value: 1 }, { id: 'key2', value: [1, 2, 3, 4] }],
result = array.reduce(
(r, a) => r.concat(
[].concat(a.value).map(
v => ({ [a.id]: v })
)
),
[]
);
console.log(result);

Categories