Reduce and sort object array in one step - javascript

I'm trying to get two values (label and data) from my source data array, rename the label value and sort the label and data array in the result object.
If this is my source data...
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
...the result should be:
{ label: ['car', 'plane', 'ship'], data: [12, 34, 56] }
So any should become car, thing should become plane and value should become ship.
But I also want to change the order of the elements in the result arrays using the label values, which should also order the data values.
Let's assume this result is expected:
{ label: ['ship', 'car', 'plane'], data: [56, 12, 34] }
With the following solution there is the need of two variables (maps and order). I thing it would be better to use only one kind of map, which should set the new label values and also the order. Maybe with an array?!
Right now only the label values get ordered, but data values should be ordered in the same way...
const maps = { any: 'car', thing: 'plane', value: 'ship' }; // 1. Rename label values
const result = sourceData.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(maps[c._id]);
a.data.push(c.count);
return a;
}, {});
result.label.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3}; // 2. Set new order
return order[a] - order[b];
})

You could move the information into a single object.
const
data = [{ _id: 'any', count: 12 }, { _id: 'thing', count: 34 }, { _id: 'value', count: 56 }],
target = { any: { label: 'car', index: 1 }, thing: { label: 'plane', index: 2 }, value: { label: 'ship', index: 0 } },
result = data.reduce((r, { _id, count }) => {
r.label[target[_id].index] = target[_id].label;
r.data[target[_id].index] = count;
return r;
}, { label: [], data: [] })
console.log(result);

Instead of separating the data into label and data and then sorting them together, you can first sort the data and then transform.
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
const maps = { any: 'car', thing: 'plane', value: 'ship' };
// Rename label values.
let result = sourceData.map(item => ({
...item,
_id: maps[item._id]
}));
// Sort the data.
result.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3};
return order[a._id] - order[b._id];
})
// Transform the result.
result = result.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(c._id);
a.data.push(c.count);
return a;
}, {});
console.log(result);

Related

Sum values depending on other value in array

I have an array of objects that represent transactions of shares:
[{
date : ...,
symbol: 'TSLA',
amount: 3,
price: 1000.00
},
{
date : ...,
symbol: 'AAPL',
amount: 1,
price: 1200.00
},
{
date : ...,
symbol: 'AAPL',
amount: 7,
price: 1300.00
}]
I need to get sum of amounts based of symbol of that array, so output would be:
[{
symbol: 'TSLA',
amount: 3,
},
{
symbol: 'AAPL',
amount: 8,
}]
Is there an efficient way to do this with build in operations in javascript, or is the only way to do it with 2 array and double loop?
I was thinking of saving symbols in separate Set, and then suming all amounts, but is there a better way?
I've tried this, but this seems to only copy the original array.
const checkIfExists = (array, value) => {
array.forEach((el, i) => {
if (el.symbol === value) {
return i;
}
});
return -1;
};
const calculateSameValues = (data) => {
let result = [];
data.forEach((el) => {
const index = checkIfExists(result, el.symbol);
if (index === -1) {
result.push({symbol: el.symbol, amount: el.amount});
} else result[index].amount += el.amount;
});
console.log(result);
};
Seems like my checkIfExists function was returning always -1.
I fixed it by saving index in seperate variable and than returning it.
Here's code:
const checkIfExists = (array, value) => {
let index = -1;
array.forEach((el, i) => {
if (el.symbol === value) {
console.log(i);
index = i;
}
});
return index;
};
Note that this still uses 2 loops, I was looking for something more efficient, but this works.
you can use array.reduce() something like this:
const arr = [{
symbol: 'TSLA',
amount: 3,
price: 1000.00
},
{
symbol: 'AAPL',
amount: 1,
price: 1200.00
},
{
symbol: 'AAPL',
amount: 7,
price: 1300.00
}]
const x = arr.reduce(function(acc, cur) {
const idx = acc.findIndex(el => el.symbol === cur.symbol);
const obj = {
symbol: cur.symbol,
amount: cur.amount,
}
if(idx < 0) {
acc.push(obj)
} else {
acc[idx].amount = acc[idx].amount + cur.amount;
}
return acc;
}, []);
console.log(x);

Filter array of objects based on multiple values from another array of objects

I have an array of objects,
c = [
{
name: 'abc',
category: 'cat1',
profitc: 'profit1',
costc: 'cost1'
},
{
name: 'xyz',
category: '',
profitc: 'profit1',
costc: ''
},
{
name: 'pqr',
category: 'cat1',
profitc: 'profit1',
costc: ''
}
]
Now I want to filter array based on another array of objects, the second array of objects is,
arr = [
{
type:'profitc'
value: 'profit1',
},
{
type:'category'
value: 'cat1',
}
]
Now the arr is shown in dropdown with multiple select option and the value of key value in the object is shown to the user i.e profit1, cat1, etc. So if a user selects profit1 and cat1, then I need to filter the array c such that, the output looks like this.
c = [
{
name: 'abc',
category: 'cat1',
profitc: 'profit1',
costc: 'cost1'
},
{
name: 'pqr',
category: 'cat1',
profitc: 'profit1',
costc: ''
}
]
I tried doing this.
let result = c.filter(e => {
let istruecat = true
//arr is chosen value from user.
arr.forEach(element => {
istruecat = e[element.type] == element.value;
})
return istruecat;
})
But when I do this I get all the objects from the c array. What am I doing wrong here? Is there a an way to do this using lodash.
-You compute istruecat based only on the last entry in arr. You should use reduce instead to accumulate value:
let result = c.filter(e => arr.reduce((acc, element) => acc && e[element.type] === element.value, true))
You could filter the array by checking all given key/values pairs with the data's objects.
var data = [{ name: 'abc', category: 'cat1', profitc: 'profit1', costc: 'cost1' }, { name: 'xyz', category: '', profitc: 'profit1', costc: '' }, { name: 'pqr', category: 'cat1', profitc: 'profit1', costc: '' }],
filters = [{ type: 'profitc', value: 'profit1', }, { type: 'category', value: 'cat1' }],
result = data.filter(o => filters.every(({ type, value }) => o[type] === value));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's an implementation of reducing the list c based in the given values by the filter array arr. Note that the output is a new list based on the initial content in c.
result = arr.reduce((acc, curr) => {
acc = acc.filter(item => {
return item[curr.type] === curr.value;
});
return acc;
}, c);
Or a recursive and imo readable solution:
function myFilter(c, [first, ...rest]) {
if (first) {
const {type, value} = first;
// Recursively call myFilter with one filter removed
return myFilter(c, rest)
.filter(x => x[type] === value);
} else {
// Nothing left to filter
return c;
}
}
myFilter(c, arr);

Finding multiple objects in an array and adding to count value for that object

I have been toying with the best way to go about doing this and have found some options but not one that preserves the format I am trying to keep for the array/object.
The overview is I have an array that gets random objects pushed into it and there can be duplicates of the same object, but I want to change it so there is only 1 of each object and instead, having a count property for each object.
An example of what I am working with and what I am aiming to have.
arr = [
{ Name: Item1, Value: 20 },
{ Name: Item2, Value: 20 },
{ Name: Item1, Value: 20 }
];
result = [
{ Name: Item1, Value: 20, Count: 2 },
{ Name: Item2, Value: 20, Count: 1 }
];
As a side note, I am wondering if it better to do this after the array is filled or if it is better to do this while pushing the objects into the array?
Assuming the value of each item is the same, you can do a simple forEach loop. For each element, check to see if the object exists in 'result'. If so, increment the count, otherwise add it to the result array.
let result = [];
arr.forEach(item => {
let resObj = result.find(resObj => resObj.Name === item.Name);
resObj ? resObj.Count++ : result.push({'Name':item.Name, 'Value': item.Value, 'Count': 1});
});
console.log(result);
In this case, you don't want to use arr.map, since we're not updating the original array.
If items are continuously being added, you can maintain an "index" (by Name) of objects you've already added to the array so that whenever a new object is added, you can update the count if it's already present or push it to the array if it's not:
var arr = [];
var index = {};
function addItem(o) {
if (o.Name in index) {
index[o.Name].Count += 1;
} else {
index[o.Name] = o;
o.Count = 1;
arr.push(o);
}
}
addItem({
Name: 'Item1',
Value: 20
});
addItem({
Name: 'Item2',
Value: 20
});
addItem({
Name: 'Item1',
Value: 20
});
console.log(arr);
The benefit of this approach is that you don't have to recompute counts from scratch (which is an O(n) operation) every time you want to get the result array.
You can loop through the array and maintain 1 temporary object with "Name" as a key, If same name is found increment "count" by 1
let array = [{ Name: 'Item1', Value: 20 }, { Name: 'Item2', Value: 20 }, { Name: 'Item1', Value: 20 }]
let tempResult = {}
for (let d of array) {
tempResult[d.Name] = {
count: 1,
...d,
...(tempResult[d.Name] && { count: tempResult[d.Name].count + 1 })
}
}
let result = Object.values(tempResult)
console.log(result);
You could take a hash table and count same items.
var array = [{ Name: 'Item1', Value: 20 }, { Name: 'Item2', Value: 20 }, { Name: 'Item1', Value: 20 }],
result = Object.values(array.reduce((r, { Name, Value }) => {
r[Name] = r[Name] || { Name, Value, Count: 0 };
r[Name].Count++;
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Get list of duplicate objects in an array of objects

I am trying to get duplicate objects within an array of objects. Let's say the object is like below.
values = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' },
{ id: 11, name: 'someName3' },
{ id: 12, name: 'someName4' }
];
Duplicate objects should return like below:
duplicate = [
{ id: 10, name: 'someName1' },
{ id: 10, name: 'someName2' }
];
You can use Array#reduce to make a counter lookup table based on the id key, then use Array#filter to remove any items that appeared only once in the lookup table. Time complexity is O(n).
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const lookup = values.reduce((a, e) => {
a[e.id] = ++a[e.id] || 0;
return a;
}, {});
console.log(values.filter(e => lookup[e.id]));
Let's say you have:
arr = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
So, to get unique items:
unique = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) === i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]);
Then, result will be
unique = [
{ id:10, name: 'someName1' },
{ id:11, name: 'someName3' },
{ id:12, name: 'someName4' }
]
And, to get duplicate ids:
duplicateIds = arr
.map(e => e['id'])
.map((e, i, final) => final.indexOf(e) !== i && i)
.filter(obj=> arr[obj])
.map(e => arr[e]["id"])
List of IDs will be
duplicateIds = [10]
Thus, to get duplicates objects:
duplicate = arr.filter(obj=> dublicateIds.includes(obj.id));
Now you have it:
duplicate = [
{ id:10, name: 'someName1' },
{ id:10, name: 'someName2' }
]
Thanks https://reactgo.com/removeduplicateobjects/
You haven't clarified whether two objects with different ids, but the same "name" count as a duplicate. I will assume those do not count as a duplicate; in other words, only objects with the same id will count as duplicate.
let ids = {};
let dups = [];
values.forEach((val)=> {
if (ids[val.id]) {
// we have already found this same id
dups.push(val)
} else {
ids[val.id] = true;
}
})
return dups;
With lodash you can solve this with filter and countBy for complexity of O(n):
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const counts = _.countBy(data, 'id')
console.log(_.filter(data, x => counts[x.id] > 1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
You could do the same with ES6 like so:
const data = [{ id: 10,name: 'someName1' }, { id: 10,name: 'someName2' }, { id: 11,name: 'someName3' }, { id: 12,name: 'someName4' } ]
const countBy = (d, id) => d.reduce((r,{id},i,a) => (r[id] = a.filter(x => x.id == id).length, r),{})
const counts = countBy(data, 'id')
console.log(data.filter(x => [x.id] > 1))
You can use an array to store unique elements and use filter on values to only return duplicates.
const unique = []
const duplicates = values.filter(o => {
if(unique.find(i => i.id === o.id && i.name === o.name)) {
return true
}
unique.push(o)
return false;
})
With lodash you can use _.groupBy() to group elements by their id. Than _.filter() out groups that have less than two members, and _.flatten() the results:
const values = [{id: 10, name: 'someName1'}, {id: 10, name: 'someName2'}, {id: 11, name:'someName3'}, {id: 12, name: 'someName4'}];
const result = _.flow([
arr => _.groupBy(arr, 'id'), // group elements by id
g => _.filter(g, o => o.length > 1), // remove groups that have less than two members
_.flatten // flatten the results to a single array
])(values);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
An alternative based in #ggorlen solution with new Map() as accumulator (for better performance) and without unary operator ++ (not advised by default in projects with ESLint).
const values = [{ id: 10, name: "someName1" }, { id: 10, name: "someName2" }, { id: 11, name: "someName3" }, { id: 12, name: "someName4" },];
const lookup = values.reduce((a, e) => {
a.set(e.id, (a.get(e.id) ?? 0) + 1);
return a;
}, new Map());
console.log(values.filter(e => lookup.get(e.id) > 1));
Try this
function checkDuplicateInObject(propertyName, inputArray) {
var seenDuplicate = false,
testObject = {};
inputArray.map(function(item) {
var itemPropertyName = item[propertyName];
if (itemPropertyName in testObject) {
testObject[itemPropertyName].duplicate = true;
item.duplicate = true;
seenDuplicate = true;
}
else {
testObject[itemPropertyName] = item;
delete item.duplicate;
}
});
return seenDuplicate;
}
referred from : http://www.competa.com/blog/lets-find-duplicate-property-values-in-an-array-of-objects-in-javascript/

Comparing two Objects by id and creating the new one

So I have two objects with this structure:
const obj1 = { data:
[ {
id: 1,
name: 'Linda'
},
{
id: 2,
name: 'Mark'
}
];
const obj2 = [
{
id: 1,
salary: "2000, 60 USD"
},
undefined
],
[
{
id: 2,
salary: "4000, 50 USD"
},
undefined
]
I need to make a function to combine both of these into one object, based on id.
So the final results would be:
const finalObj = { data:
[ {
id: 1,
name: 'Linda',
salary: "2000, 60 USD"
},
{
id: 2,
name: 'Mark',
salary: "4000, 50 USD"
}
];
I have checked other questions, but could not find anything that would help. It can be done with lodash afaik, but don't know how.
I have tried the following:
finalObj = obj1.data.map(x => {
return {
...x,
...obj2
}
But it didn't map correctly.
Thanks.
EDIT: Updated obj2 response.
You can array#concat both your array and then using array#reduce and an object lookup with id, merge your objects. Then return all the values from this object.
const obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' }]},
obj2 = { data: [{ id: 1, salary: "2000, 60 USD"}, { id: 2, salary: "4000, 50 USD"}]},
result = Object.values(obj1.data.concat(obj2.data).reduce((r,o) => {
r[o.id] = r[o.id] || {};
r[o.id] = {...r[o.id], ...o};
return r;
},{}));
console.log(result);
You could take a Map for collecting all properties of the same id in an object. Later get the values of the map.
const join = o => o && map.set(o.id, Object.assign(map.get(o.id) || {}, o));
var obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' } ]},
obj2 = [{ id: 1, salary: "2000, 60 USD" }, undefined, { id: 2, salary: "4000, 50 USD" }, undefined],
map = new Map,
result;
obj1.data.forEach(join);
obj2.forEach(join);
result = { data: Array.from(map.values()) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another way
const res = {
...obj1, // take the initial object
data: obj1.data.map(item => ({ // rewrite data property by iterate each item
// and merge item with corresponding
// object from obj2
...item, // take the item object
...obj2.find(({ id }) => id === item.id) // find corresponding object
}))
};
Here is an approach which would combine the objects and not overwrite the properties but only add the ones that are missing as well as avoid the undefined etc:
const names = {data: [{ id: 1, name: 'Linda' },{ id: 2, name: 'Mark' }]}
const salaries = [{ id: 1, salary: "2000, 60 USD" }, undefined]
var result = _.mapValues(names.data, x => {
let hit = _.find(salaries, y => y ? y.id === x.id : null)
return hit ? _.defaults(x, hit) : x
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
We are using mapValues to get to the values of names.data and look through them and for each of them get a hit in the salaries. If the hit exists we default the props of the hit with the current data object and return. Hope this helps.

Categories

Resources