So I have this data:
fields = ['a', 'b', 'c']
data = [{r: 1, a: 2, b: 3, c: 4, h: 5}, {r: 4, a: 9, b: 1, c: 4, h: 5} ... ]
and I want to be able (preferred with lodash) to be able to get to this:
newData = [{r:1, h:5, values: [{name: 'a', value: 2},{name: 'b', value: 3}, {name: 'c', value: 4}], .....]
Meaning only the fields from the 'fields' object be taken out of each object in array (they always exist) and put into 'values' property that has an array of them in the format displayed here.
Would love to hear suggestions of the cleanest way to achieve this!
I did this :
function something(data, fields) {
const formattedData = _.map(data, (currData) => {
const otherFields = _.omit(currData, fields)
return {
...otherFields,
values: _.flow(
currData => _.pick(currData, fields),
pickedFields => _.toPairs(pickedFields),
pairs => _.map(pairs, pair => {
return { name: pair[0], value: pair[1] }
})
)(currData)
}
})
return formattedData
}
which works, but I'm wondering if it isn't a bit complicated.
The _.flow() method creates a function, which you can extract and name. In addition, the 1st function in the flow, accepts more than 1 parameter, so you don't need to pass it explicitly. Since _.toPairs() is unary, you don't need to wrap it in an arrow function.
The object creation is a bit annoying. I've used _.zipObject(), but it's still cumbersome.
Now you can use the function create by _.flow() in your main function, and it's pretty readable:
const { flow, pick, toPairs, map, partial, zipObject, omit } = _
const propsToObjs = flow(
pick,
toPairs,
pairs => map(pairs, partial(zipObject, ['name', 'value'])),
)
const fn = (data, fields) =>
map(data, currData => ({
...omit(currData, fields),
values: propsToObjs(currData, fields)
}))
const fields = ['a', 'b', 'c']
const data = [{r: 1, a: 2, b: 3, c: 4, h: 5}, {r: 4, a: 9, b: 1, c: 4, h: 5}]
const result = fn(data, fields)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Using lodash/fp, we can make the flow function even nicer, since lodash/fp functions are auto-curried and iteratee-first data-last (not the reversed order of parameters):
const { flow, pick, toPairs, map, partial, zipObject, omit } = _
const propsToObjs = flow(
pick,
toPairs,
map(zipObject(['name', 'value']))
)
const fn = fields => map(currData => ({
...omit(fields, currData),
values: propsToObjs(fields, currData)
}))
const fields = ['a', 'b', 'c']
const data = [{r: 1, a: 2, b: 3, c: 4, h: 5}, {r: 4, a: 9, b: 1, c: 4, h: 5}]
const result = fn(fields)(data)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
You could map through the objects in data and then check if the key is in the fields array:
fields = ["a", "b", "c"];
data = [
{ r: 1, a: 2, b: 3, c: 4, h: 5 },
{ r: 4, a: 9, b: 1, c: 4, h: 5 },
];
let newData = data.map((o) => {
let newObject = {};
newObject.values = [];
for (let k in o) {
if (fields.includes(k)) {
newObject.values.push({
name: k,
value: o[k]
});
} else {
newObject[k] = o[k];
}
}
return newObject;
});
console.log(newData);
You could destructure the object, pick the wanted properties and return the rest of the object with wanted values.
const
fields = ['a', 'b', 'c'],
data = [{ r: 1, a: 2, b: 3, c: 4, h: 5 }, { r: 4, a: 9, b: 1, c: 4, h: 5 }],
result = data.map(o => {
const values = fields.map(name => {
let value;
({ [name]: value, ...o } = o);
return { name, value };
});
return { ...o, values };
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
compare array of object with array of keys, filter array of object with array keys.
Input:
let a = ['aa'];
let b = [{ aa: 1, bb: 2, c: 30 },{ aa: 2, bb: 3, c: 40}];
output:
b = [{bb: 2, c: 30 },{bb: 3, c: 40}];
original array should be mutate.
Much similiar to #SachilaRanawaka 's answer, but works without modifying the original b array:
let a = ['aa'];
let b = [{ aa: 1, bb: 2, c: 30 },{ aa: 2, bb: 3, c: 40}];
function removeKey(obj, key) {
let clone = Object.assign({}, obj); // <-- shallow clone
if (key in clone) {
delete clone[key];
}
return clone;
}
function removeKeys(keys, objs) {
return objs.map(o => keys.reduce(removeKey, o));
}
console.log(removeKeys(a, b));
You could take a destructuring with getting the rest approach.
This approach does not mutate the original data.
const
unwanted = ['aa'],
data = [{ aa: 1, bb: 2, c: 30 }, { aa: 2, bb: 3, c: 40 }],
result = data.map(o => unwanted.reduce((q, k) => {
const { [k]: _, ...r } = q;
return r;
}, o));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can simply achieve this requirement with the help of Array.forEach() method.
Live Demo :
let a = ['aa'];
let b = [{ aa: 1, bb: 2, c: 30 },{ aa: 2, bb: 3, c: 40}];
b.forEach(obj => {
Object.keys(obj).forEach(key => {
a.forEach(item => delete obj[item])
});
});
console.log(b);
It can probably be solved with less lines of code, but this was the first i could think of.
let keysToRemove = ['aa'];
let array = [{ aa: 1, bb: 2, c: 30 },{ aa: 2, bb: 3, c: 40}];
let result = array.map((item) => {
let filtered = Object.keys(item)
.filter((key) => !keysToRemove.includes(key))
.reduce((obj, key) => {
obj[key] = item[key];
return obj;
}, {});
return filtered;
});
console.log(result);
use the map operator and use delete to delete properties from the object
let a = ['aa'];
let b = [{ aa: 1, bb: 2, c: 30 },{ aa: 2, bb: 3, c: 40}];
const result = b.map(item => {
Object.keys(item).forEach(key => {
if(a.includes(key)){
delete item[key]
}
})
return item
})
console.log(result)
Say I have the following array:
let arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
I would like to compute the cumulative sum of each key, but I would also like the output to be an array of the same length with the cumulative values at each step. The final result should be:
[{a: 1, b: 2}, {a: 3, b: 6}, {a: 11, b: 5}]
My issue is that I am not able to obtain the array as desired. I only get the final object with this:
let result = arr.reduce((accumulator, element) => {
if(accumulator.length === 0) {
accumulator = element
} else {
for(let i in element){
accumulator[i] = accumulator[i] + element[i]
}
}
return accumulator
}, [])
console.log(result); // {a: 11, b: 5}
What you're after sounds like the scan() higher-order function (borrowing the idea from ramda.js), which allows you to return an accumulated result for each element within your array. The scan method is similar to how the .reduce() method behaves, except that it returns the accumulator for each element. You can build the scan() function yourself like so:
let arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}];
const scan = ([x, ...xs], fn) => xs.reduce((acc, elem) => {
return [...acc, fn(acc.at(-1), elem)];
}, xs.length ? [x] : []);
const res = scan(arr, (x, y) => ({a: x.a+y.a, b: x.b+y.b}));
console.log(res);
You might consider further improvements such as providing an initial value to the scan method (similar to how reduce accepts one). Also, if you need better browser support the .at() method currently has limited browser support, so you may instead consider creating your own at() function:
const at = (arr, idx) => idx >= 0 ? arr[idx] : arr[arr.length + idx];
You can easily achieve the result using reduce as
let arr = [
{ a: 1, b: 2 },
{ a: 2, b: 4 },
{ a: 8, b: -1 },
];
const result = arr.reduce((acc, curr, i) => {
if (i === 0) acc.push(curr);
else {
const last = acc[i - 1];
const newObj = {};
Object.keys(curr).forEach((k) => (newObj[k] = curr[k] + last[k]));
acc.push(newObj);
}
return acc;
}, []);
console.log(result);
Something like this:
const arr = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
const result = arr.reduce((accumulator, element, index) => {
if(accumulator.length === 0) {
accumulator.push(element)
} else {
const sum = {};
for(let i in element) {
sum[i] = element[i] + (accumulator[index - 1][i] || 0)
}
accumulator.push(sum)
}
return accumulator
}, [])
console.log(result);
Another option is keep sum result using a Map, it helps if keys in elements of the array are not always same.
const arr = [{a: 1, b: 2}, {a: 2}, {a: 8, b: -1}];
const map = new Map();
const result = arr.map((element) => {
const sum = {};
for (let i in element) {
sum[i]= element[i] + (map.get(i) || 0);
map.set(i, sum[i]);
}
return sum;
});
console.log(result);
Here is a bit more concise reduce, probably not as readable as a consequence...
array.reduce((y,x,i) => ( i===0 ? y : [...y, {a: x.a + y[i-1].a, b: x.b + y[i-1].b}]),[array[0]])
let array = [{a: 1, b: 2}, {a: 2, b: 4}, {a: 8, b: -1}]
let culm = array.reduce((y,x,i) => ( i===0 ? y : [...y, {a: x.a + y[i-1].a, b: x.b + y[i-1].b}]),[array[0]])
console.log(culm)
Given:
const xs =
[ {a: 1, b: 2}
, {a: 2, b: 4}
, {a: 8, b: -1}];
Define a function sum such as:
const sum = ([head, ...tail]) =>
tail.reduce((x, y) =>
({a: (x.a+y.a), b: (x.b+y.b)}), head);
sum(xs);
//=> {a: 11, b: 5}
Then apply that function in a map on larger slices of xs:
xs.map((_, i, arr) => sum(arr.slice(0, i+1)));
//=> [ {a: 1, b: 2}
//=> , {a: 3, b: 6}
//=> , {a: 11, b: 5}]
As always I will explain my problem by example (that I solved but its a lot of code and its ugly, that's why I'm looking for a better solution). I'm trying to look at an object like this:
const object1 = {
a: {a:1},
b: 2,
c: 3,
d: 4,
};
I want to check if this object has any of the following properties [a,f] and if have one of them to create a new object with these properties
const object2 = {
a: {a:1},
};
const object1 = {
a: {a:1},
b: 2,
c: 3,
d: 4,
}
const arrOfItem = ['a', 'd']
const newObj = {}
for(let item in object1) {
if(arrOfItem.includes(item)) {
newObj[item]= object1[item]
}
}
console.log(newObj)
see if this works for you,
function makeObject (properties) {
const originalObject = {
a: {a:1},
b: 2,
c: 3,
d: 4,
};
let newObject = {}
properties.forEach(property => {
if(originalObject.hasOwnProperty(property)) {
newObject[property] = originalObject[property];
}
});
return newObject;
}
pass the properties as an array of strings to makeObject function
const d = ['a', 'f', 'd']
const object1 = {
a: {a:1},
b: 2,
c: 3,
d: 4,
};
const object2 = d.reduce((acc, ele) => {
if(object1[ele] !== undefined) acc[ele] = object1[ele];
return acc;
}, {});
console.log(object2);
I am trying to merge two arrays of objects without using the unionBy method from lodash.
Currently I have the following code working perfectly:
var array1 = [
{ a: 1, b: 'first'},
{ a: 2, b: 'second'}
];
var array2 = [
{ a: 3, b: 'third'},
{ a: 1, b: 'fourth'}
];
var array3 = __.unionBy(array2, array1, 'a');
This outputs:
[
{
"a": 3,
"b": "third"
},
{
"a": 1,
"b": "fourth"
},
{
"a": 2,
"b": "second"
}
]
This is the desired result but I can't use unionBy in my current work environment, so I'm looking for a result that uses either native JS or other lodash methods 3.6.0 or lower.
Concat and use Array#filter with a helper object to remove duplicates:
var array1 = [{"a":1,"b":"first"},{"a":2,"b":"second"}];
var array2 = [{"a":3,"b":"third"},{"a":1,"b":"fourth"}];
var result = array2.concat(array1).filter(function(o) {
return this[o.a] ? false : this[o.a] = true;
}, {});
console.log(result);
If ES6 is an option you can use a Set instead of the helper object:
const array1 = [{"a":1,"b":"first"},{"a":2,"b":"second"}];
const array2 = [{"a":3,"b":"third"},{"a":1,"b":"fourth"}];
const result = array2.concat(array1).filter(function(o) {
return this.has(o.a) ? false : this.add(o.a);
}, new Set());
console.log(result);
If you want to use an arrow function, you can't use the thisArg of Array.filter() to bind the Set as the this of the function (you can't bind this to arrow functions). You can use a closure instead (attribute for the method goes to #NinaScholz).
const array1 = [{"a":1,"b":"first"},{"a":2,"b":"second"}];
const array2 = [{"a":3,"b":"third"},{"a":1,"b":"fourth"}];
const result = [...array2, ...array1]
.filter((set => // store the set and return the actual callback
o => set.has(o.a) ? false : set.add(o.a)
)(new Set()) // use an IIFE to create a Set and store it set
);
console.log(result);
You could take a Set for filtering to get unique values.
var array1 = [{ a: 1, b: 'first' }, { a: 2, b: 'second' }],
array2 = [{ a: 3, b: 'third' }, { a: 1, b: 'fourth' }],
s = new Set,
array3 = array2.map(o => (s.add(o.a), o)).concat(array1.filter(o => !s.has(o.a)));
console.log(array3);
You can use an ES6 Map for this. Construct it with the data, keyed by the a property value, and then take the values out of the Map again:
var array1 = [{"a":1,"b":"first"},{"a":2,"b":"second"}],
array2 = [{"a":3,"b":"third"},{"a":1,"b":"fourth"}];
var result = [...new Map([...array1,...array2].map( o => [o.a, o] )).values()];
console.log(result);
You can merge the 2 arrays and then filter the ones with same property a:
var array1 = [{ a: 1, b: 'first'},{ a: 2, b: 'second'}],
array2 = [{ a: 3, b: 'third'},{ a: 1, b: 'fourth'}],
array3 = [...array2, ...array1].filter((item, pos, arr) =>
arr.findIndex(item2 => item.a == item2.a) == pos);
console.log(array3)
If you want to still be able to specify the property by which to union you can implement you own function like this:
var array1 = [{ a: 1, b: 'first'},{ a: 2, b: 'second'}],
array2 = [{ a: 3, b: 'third'},{ a: 1, b: 'fourth'}],
array3 = unionBy(array1, array2, 'a');
function unionBy(array1, array2, prop){
return [...array2, ...array1].filter((item, pos, arr) =>
arr.findIndex(item2 => item[prop] == item2[prop]) == pos);
}
console.log(array3);
Note: One advantage of my answer over some of the answers is that it preserves the order like in lodash which may or may not be important.
ES5 using Array.filter and Array.find
var array1 = [{ a: 1, b: "first" }, { a: 2, b: "second" }];
var array2 = [{ a: 3, b: "third" }, { a: 1, b: "fourth" }];
function merge(a, b, prop) {
var reduced = a.filter(function(itemA) {
return !b.find(function(itemB) {
return itemA[prop] === itemB[prop];
});
});
return reduced.concat(b);
}
console.log(merge(array1, array2, "a"));
ES6 arrow functions
var array1 = [{ a: 1, b: "first" }, { a: 2, b: "second" }];
var array2 = [{ a: 3, b: "third" }, { a: 1, b: "fourth" }];
function merge(a, b, prop) {
const reduced = a.filter(
itemA => !b.find(itemB => itemA[prop] === itemB[prop])
);
return reduced.concat(b);
}
console.log(merge(array1, array2, "a"));
Another ES6 one line experiment
var array1 = [{ a: 1, b: "first" }, { a: 2, b: "second" }];
var array2 = [{ a: 3, b: "third" }, { a: 1, b: "fourth" }];
const merge = (a, b, p) => a.filter( aa => ! b.find ( bb => aa[p] === bb[p]) ).concat(b);
console.log(merge(array1, array2, "a"));
You could use ES6 find and reduce function smartly!
var array1 = [{"a":1,"b":"first"},{"a":2,"b":"second"}];
var array2 = [{"a":3,"b":"third"},{"a":1,"b":"fourth"}];
var res = array1.concat(array2).reduce((aggr, el)=>{
if(!aggr.find(inst=>inst.a==el.a))
return [...aggr, el];
else
return aggr
},[])
console.log(res);
imagine that, we have two arrays. Each of the containing objects of different type. For example:
let first: Foo[] = [
{ a: 12, b: 'x', c: 0 },
{ a: 43, b: 'y', c: 0 }
];
let second: number[] = [11, 15];
I would like merge theirs objects in a way that I finally get one array looks like below:
let first: Foo[] = [
{ a: 12, b: 'x', c: 11 },
{ a: 43, b: 'y', c: 15 }
];
As you can see I just want to assign value from the second array to c property of object from first array.
I hope that you understand my explanation of problem. I believe in your skills, guys!
you could zip the two arrays into one,
const first: Foo[] = [
{ a: 12, b: 'x', c: 0 },
{ a: 43, b: 'y', c: 0 }
];
const second: number[] = [11, 15];
const result: Foo[] = first.map((e, i) => {
return <Foo>Object.assign({}, e, { c: second[i] });
});
As so often, Array.prototype.reduce provides a good base for an approach like e.g. this one ...
var listOfItems = [
{ a: 12, b: 'x', c: 0 },
{ a: 43, b: 'y', c: 0 }
];
var listOfValues = [11, 15];
function reassignValueToGivenKey(collector, item, idx) {
item = Object.assign({}, item); // do not mutate original reference.
item[collector.key] = collector.valueList[idx]; // reassign value.
collector.itemList.push(item); // collect processed items separately.
return collector;
}
var result = listOfItems.reduce(reassignValueToGivenKey, {
key: 'c',
valueList: listOfValues,
itemList: []
}).itemList;
console.log('listOfItems : ', listOfItems);
console.log('result : ', result);
.as-console-wrapper { max-height: 100%!important; top: 0; }
I think you should do it like this...
Maybe not the best, but should work in you case :)
This is very simple...
for(var i in second){
var elem = second[i];
first[i]['c'] = elem;
}