'How does one, from an array of objects, collect and aggregate the merged variant of all objects with a specific equal property value?
const portfolio = [
{ name: 'Mark', stock: 'FB' },
{ name: 'Steve', stock: 'AAPL' },
{ name: 'Tim', stock: 'AAPL' },
{ name: 'Steve', stock: 'MSFT' },
{ name: 'Bill', stock: 'MSFT' },
{ name: 'Bill', stock: 'AAPL' },
];
// Output
const shareholder = [
{ stock: 'AAPL', name: ['Steve', 'Bill', 'Tim'], count: 3 },
{ stock: 'MSFT', name: ['Steve', 'Bill'], count: 2 },
{ stock: 'FB', name: ['Mark'], count: 1 },
];
if I create one function which take input array as param and this will return output array in jS
Solution 1:[1]
One way using reduce and Object.values
const portfolio = [{
name: 'Mark',
stock: 'FB'
},
{
name: 'Steve',
stock: 'AAPL'
},
{
name: 'Tim',
stock: 'AAPL'
},
{
name: 'Steve',
stock: 'MSFT'
},
{
name: 'Bill',
stock: 'MSFT'
},
{
name: 'Bill',
stock: 'AAPL'
},
];
const result = Object.values(portfolio.reduce((res, {
stock,
name
}) => {
const existing = res[stock] || {
stock,
names: [],
count: 0
}
res[stock] = {
stock,
names: [...existing.names, name],
count: existing.count + 1
}
return res
}, {}))
console.log(result)
Solution 2:[2]
From the above comment ...
"What the OP wants is filtering and grouping array items (by a specific
key) together with value aggregation of some/one other key/s. One usually would use areducebased approach. One question though ... what is the additionalcountvalue good for when one has this information already in any item'sitem.name.length(or at least renamecounttonameCount)."
... used techniques/methods ...
const portfolio = [
{ name: 'Mark', stock: 'FB' },
{ name: 'Steve', stock: 'AAPL' },
{ name: 'Tim', stock: 'AAPL' },
{ name: 'Steve', stock: 'MSFT' },
{ name: 'Bill', stock: 'MSFT' },
{ name: 'Bill', stock: 'AAPL' },
];
const shareholderList = Object.values( // get only the values from ...
// ... create an index/map of stock specific shareholder items/objects
portfolio.reduce((stockIndex, { name, stock }) => {
// access an already existing object or
// create a new grouped (by `stock` value) to be merged and aggregated object.
const groupedMerger = (stockIndex[stock] ??= { stock, names: [], nameCount: 0 });
// aggregate list of `stock` specific shareholder names.
groupedMerger.names.push(name);
// increment count of `stock` specific shareholder names.
++groupedMerger.nameCount;
// the programmatically built index/map of stock specific shareholder items/objects.
return stockIndex;
}, {})
).sort((a, b) => b.nameCount - a.nameCount); // sort shareholder items by theirs `nameCount`s.
console.log({ shareholderList });
.as-console-wrapper { min-height: 100%!important; top: 0; }
And in order to demonstrate how each part works and how everything works together, the above main approach will be compacted into a (re-usable) function statement.
function aggregateStockIndex(index, { name, stock }) {
const groupedMerger = (index[stock] ??= { stock, names: [], nameCount: 0 });
groupedMerger.names.push(name);
++groupedMerger.nameCount;
return index;
}
const portfolio = [
{ name: 'Mark', stock: 'FB' },
{ name: 'Steve', stock: 'AAPL' },
{ name: 'Tim', stock: 'AAPL' },
{ name: 'Steve', stock: 'MSFT' },
{ name: 'Bill', stock: 'MSFT' },
{ name: 'Bill', stock: 'AAPL' },
];
const stockIndex = portfolio
.reduce(aggregateStockIndex, {});
const shareholderList = Object.values(
// stockIndex
portfolio.reduce(aggregateStockIndex, {})
).sort((a, b) => b.nameCount - a.nameCount);
console.log({
stockIndex,
shareholderList,
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
Solution 3:[3]
I shared a function named groupPortfolio that takes a portfolio as an argument expecting to be a list of people holding a share of a stock.
The function first groups those people as a map binding each stock to which holders it belong to and eventually uses that map to create the final array as a list of stocks where each one has a list of people holding its share and the corresponding amount of people in that list.
function groupPortfolio(portfolio){
//groups the portfolio item in [stock] => shareholders[]
let grouped = {};
for(let o of portfolio){
if( !Object.keys(grouped).includes(o.stock) )
grouped[o.stock] = [];
grouped[o.stock].push( o.name );
}
//creates the shareholders array starting from the grouped stocks
let shareholders = [];
for( let stockGroup of Object.keys(grouped) ){
shareholders.push(
{ stock: stockGroup, name: grouped[stockGroup], count: grouped[stockGroup].length }
);
}
return shareholders;
}
const portfolio1 = [
{name: 'Mark', stock: 'FB'},
{name: 'Steve', stock: 'AAPL'},
{name: 'Tim', stock: 'AAPL'},
{name: 'Steve', stock: 'MSFT'},
{name: 'Bill', stock: 'MSFT'},
{name: 'Bill', stock: 'AAPL'},
];
let shareholder1 = groupPortfolio(portfolio1);
console.log( shareholder1 );
/*
0:
stock: "FB"
name: ['Mark']
count: 1
1:
stock: "AAPL"
name: (3) ['Steve', 'Tim', 'Bill']
count: 3
2:
stock: "MSFT"
name: (2) ['Steve', 'Bill']
count: 2
*/
Solution 4:[4]
You can use two simple for loops to convert the first array into the second array.
Working Example:
const portfolio = [
{name: 'Mark', stock: 'FB'},
{name: 'Steve', stock: 'AAPL'},
{name: 'Tim', stock: 'AAPL'},
{name: 'Steve', stock: 'MSFT'},
{name: 'Bill', stock: 'MSFT'},
{name: 'Bill', stock: 'AAPL'},
];
let shareholder = [];
// CYCLE THROUGH PORTFOLIO
for (let i = 0; i < portfolio.length; i++) {
// DETERMINE IF STOCK ENTRY ALREADY EXISTS
let stockIndex = shareholder.length;
for (let j = 0; j < shareholder.length; j++) {
if (portfolio[i].stock === shareholder[j].stock) {
stockIndex = j;
}
}
// ADD NEW ENTRY IF STOCK ENTRY DOES NOT EXIST
if (stockIndex === shareholder.length) {
shareholder[stockIndex] = {stock: portfolio[i].stock, name: [], count: 0};
}
// ADD DETAILS TO NEW OR EXISTING STOCK ENTRY
shareholder[stockIndex].name.push(portfolio[i].name);
shareholder[stockIndex].count++;
}
console.log(shareholder);
Solution 5:[5]
You could achieve this in two steps
- First create an object with
stockas the key name to find out all the records with unique stock. - Loop over the object created in step#1 to convert to an Array structure
I couldn't think of ay solution which could achieve it in single iteration.
Please see the code snippet below.
const portfolio = [
{name: 'Mark', stock: 'FB'},
{name: 'Steve', stock: 'AAPL'},
{name: 'Tim', stock: 'AAPL'},
{name: 'Steve', stock: 'MSFT'},
{name: 'Bill', stock: 'MSFT'},
{name: 'Bill', stock: 'AAPL'},
];
let shareholderObj = {};
let shareholderArr = [];
portfolio.forEach((el) => {
const stock = el.stock
if(shareholderObj[stock]){
shareholderObj[stock].name.push(el.name)
}
else{
shareholderObj[stock] = {
name: [el.name]
}
}
})
for (let [key, value] of Object.entries(shareholderObj)) {
shareholderArr.push({
stock: key,
name: value.name,
count: value.name.length
})
}
console.log(shareholderArr)
Solution 6:[6]
A simplified approach by taking the new length of name as count.
const portfolio = [{ name: 'Mark', stock: 'FB' }, { name: 'Steve', stock: 'AAPL' }, { name: 'Tim', stock: 'AAPL' }, { name: 'Steve', stock: 'MSFT' }, { name: 'Bill', stock: 'MSFT' }, { name: 'Bill', stock: 'AAPL' }],
shareholder = Object
.values(portfolio.reduce((r, { name, stock }) => {
r[stock] ??= { stock, name: [] };
r[stock].count = r[stock].name.push(name);
return r;
}, {}))
.sort((a, b) => b.count - a.count)
console.log(shareholder);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | R4ncid |
| Solution 2 | |
| Solution 3 | Diego De Vita |
| Solution 4 | Rounin - Standing with Ukraine |
| Solution 5 | manpreet |
| Solution 6 | Nina Scholz |
