-1
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

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
Garima Jain
  • 117
  • 1
  • 8
  • 3
    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 a [`reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) based approach. One question though ... what is the additional `count` value good for when one has this information already in any item's `item.name.length` (or at least rename `count` to `nameCount`). – Peter Seliger Apr 29 '22 at 10:05
  • 1
    Does this answer your question? [How can I group an array of objects by key?](https://stackoverflow.com/questions/40774697/how-can-i-group-an-array-of-objects-by-key) Not only a duplicate, but a duplicate that accounts for 80 percent of the JavaScript questions. – Yogi Apr 29 '22 at 10:44

6 Answers6

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)
R4ncid
  • 6,944
  • 1
  • 4
  • 18
1

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 a reduce based approach. One question though ... what is the additional count value good for when one has this information already in any item's item.name.length (or at least rename count to nameCount)."

... 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; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • 1
    For noobs like me: Using [`??=`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment) within the assignment achieves two objectives here. It assigns the object `{stock, name: [], ....}` to `stockIndex[stock]` iff the latter is nullish. And, in either case, the `groupedMerger` variable is populated with the object (either pre-existing in `stockIndex[stock]`, or assigned using `??=`). It's so little code to do so many things. Happy that I'm learning JS. :-) If I've mis-stated, please enlighten. – jsN00b Apr 29 '22 at 10:42
0

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
*/
Diego D
  • 6,156
  • 2
  • 17
  • 30
0

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);
Rounin
  • 27,134
  • 9
  • 83
  • 108
0

You could achieve this in two steps

  1. First create an object with stock as the key name to find out all the records with unique stock.
  2. 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)
manpreet
  • 636
  • 5
  • 20
0

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; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392