Dhaval Jardosh
Dhaval Jardosh

Reputation: 7309

Sort an Array of Objects based on Occurrence in Javascript and ES6

Array of objects with the following properties

[ 
  { testerId: '1',name:'Kaka'},
  { testerId: '3',name:'Messi'},
  { testerId: '3',name:'Messi'},  // 1 = 3 times (Kaka)
  { testerId: '3',name:'Messi'},  // 2 = 5 times (Ramos)
  { testerId: '3',name:'Messi'},  // 3 = 4 times (Messi)
  { testerId: '2',name:'Ramos'},  // 4 = 2 times (Neuer)
  { testerId: '2',name:'Ramos'},  // 5 = 6 times (Ronaldo)
  { testerId: '1',name:'Kaka'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '4',name:'Neuer'},
  { testerId: '4',name:'Neuer'},
  { testerId: '1',name:'Kaka'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '5',name:'Ronaldo'},
]

Output: [Ronaldo,Ramos,Messi,Kaka,Neuer]

It's supposed to be in increasing order of occurrence of testerId.

Upvotes: 1

Views: 2587

Answers (4)

trincot
trincot

Reputation: 350841

You could use Map to help group by testerId.

Here is a functional programming style implementation:

const myArray = [{ testerId: '1',name:'Kaka'},{ testerId: '3',name:'Messi'},{ testerId: '3',name:'Messi'},{ testerId: '3',name:'Messi'},{ testerId: '3',name:'Messi'},{ testerId: '2',name:'Ramos'},{ testerId: '2',name:'Ramos'},{ testerId: '1',name:'Kaka'},{ testerId: '5',name:'Ronaldo'},{ testerId: '5',name:'Ronaldo'},{ testerId: '5',name:'Ronaldo'},{ testerId: '5',name:'Ronaldo'},{ testerId: '5',name:'Ronaldo'},{ testerId: '4',name:'Neuer'},{ testerId: '4',name:'Neuer'},{ testerId: '1',name:'Kaka'},{ testerId: '2',name:'Ramos'},{ testerId: '2',name:'Ramos'},{ testerId: '2',name:'Ramos'},{ testerId: '5',name:'Ronaldo'}]

const result = Array.from(
    myArray.reduce((map, item) => 
        (map.get(item.testerId).count++, map) 
    , new Map(myArray.map(o => 
        [o.testerId, Object.assign({}, o, { count: 0 })]
    ))), ([k, o]) => o
).sort( (a, b) => b.count - a.count )
.map( o => o.name );

console.log(result);

Explanation

Some have commented that they find this hard to read. In my opinion this is a matter of habit and once one becomes familiar with it, such code is not so hard to understand.

So here is an explanation:

The first task consists of creating a Map. The Map constructor can take an array of pairs which represent the keys and values. This array is created from myArray with the .map() method. For each object in myArray, the key testerID property serves as key, and a copy of the object, extended with a new property count, serves as the corresponding value. The (shallow) copy is made with Object.assign

This new Map is then passed on to the second argument of the reduce method, so it becomes the initial value of the reduce callback function. That callback simply increases the appropriate counter property, and returns the mutated map again (using the comma operator).

The result of this reduce() call will thus be a map with all the counters set at the right value. This map is then converted back to an array of pairs with Array.from(). This method can take a callback function which performs a mapping on those key/value pairs. The ([k, o]) => o mapping just converts the pairs to the value part, throwing the key away.

This array of values (objects with three properties) is then sorted by descending count property.

Finally, the name is extracted from those objects with a map, giving the resulting array of names.

Upvotes: 1

CRice
CRice

Reputation: 32216

The built in javascript .sort method will be helpful here. You can pass it a comparator based on the name counts in your object.

So first create a map of name to number of occurrences, then an array of just the names themselves. Then the sort is pretty straightforward:

const myArray = [ 
  { testerId: '1',name:'Kaka'},
  { testerId: '3',name:'Messi'},
  { testerId: '3',name:'Messi'},  // 1 = 3 times (Kaka)
  { testerId: '3',name:'Messi'},  // 2 = 5 times (Ramos)
  { testerId: '3',name:'Messi'},  // 3 = 4 times (Messi)
  { testerId: '2',name:'Ramos'},  // 4 = 2 times (Neuer)
  { testerId: '2',name:'Ramos'},  // 5 = 6 times (Ronaldo)
  { testerId: '1',name:'Kaka'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '4',name:'Neuer'},
  { testerId: '4',name:'Neuer'},
  { testerId: '1',name:'Kaka'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '5',name:'Ronaldo'},
]

const myArrayCounts = myArray.reduce((counts, item) => {
  if (counts[item.name] === undefined) counts[item.name] = 0;
  counts[item.name]++;
  return counts;
}, {});

const myArrayNames = myArray
  .map(x => x.name)
  .filter((x, i, l) => l.indexOf(x) === i); // Fancy deduplication filter

console.log(myArrayNames.sort((v1, v2) => {return myArrayCounts[v2] - myArrayCounts[v1]}))

Upvotes: 0

Olian04
Olian04

Reputation: 6872

I would solve this in 3 steps:

1) Count the # of occurrences for each element in the input.

2) Sort the # of occurrences in descending order.

3) Extract the name field

let input = [ 
  { testerId: '1',name:'Kaka'},
  { testerId: '3',name:'Messi'},
  { testerId: '3',name:'Messi'},  
  { testerId: '3',name:'Messi'},  
  { testerId: '3',name:'Messi'},  
  { testerId: '2',name:'Ramos'},  
  { testerId: '2',name:'Ramos'}, 
  { testerId: '1',name:'Kaka'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '4',name:'Neuer'},
  { testerId: '4',name:'Neuer'},
  { testerId: '1',name:'Kaka'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '5',name:'Ronaldo'},
];

//1)
let count = input.reduce((res, val) => {
  if(res[val.name]) {
    res[val.name]++;
  } else {
    res[val.name] = 1;
  }
  return res;
}, {});

let output = Object.entries(count)
  .sort((a, b) => b[1]-a[1]) //2)
  .map(v => v[0]); //3)

console.log(output);

Upvotes: 0

zfrisch
zfrisch

Reputation: 8670

You can use a set object and a counter object to keep track of your entries. Then simply sort by the returned count.

let arr = [ 
  { testerId: '1',name:'Kaka'},
  { testerId: '3',name:'Messi'},
  { testerId: '3',name:'Messi'},  // 1 = 3 times (Kaka)
  { testerId: '3',name:'Messi'},  // 2 = 5 times (Ramos)
  { testerId: '3',name:'Messi'},  // 3 = 4 times (Messi)
  { testerId: '2',name:'Ramos'},  // 4 = 2 times (Neuer)
  { testerId: '2',name:'Ramos'},  // 5 = 6 times (Ronaldo)
  { testerId: '1',name:'Kaka'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '5',name:'Ronaldo'},
  { testerId: '4',name:'Neuer'},
  { testerId: '4',name:'Neuer'},
  { testerId: '1',name:'Kaka'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '2',name:'Ramos'},
  { testerId: '5',name:'Ronaldo'},
],
mySet = new Set(),
  countObj = {};

for (let obj of arr) {
  if (mySet.has(obj.name)) countObj[obj.name]++;
  else {
    countObj[obj.name] = 1;
    mySet.add(obj.name);
  }
}
arr = arr.sort(function(a, b) {
  return (countObj[a.name] - countObj[b.name]);
});
console.log(arr);

Upvotes: 0

Related Questions