Related
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))
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
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);
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);
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);