Home > other >  Sort Array by Element Frequency JavaScript
Sort Array by Element Frequency JavaScript

Time:10-15

I want to sort an array by element frequency. My code works for arrays of strings, but not for arrays of numbers:

const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a   1 : a), 0);

function frequencySort(arr){
  let d = {}
  arr.forEach(i => d[i] = countOccurrences(arr,i))
  arr.sort(function(a,b){
    return d[b] - d[a]
  })
  
  return arr
}



frequencySort(['a','b','b','b','c','c'])) returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]

frequencySort([4, 6, 2, 2, 6, 4, 4, 4]) returns [ 4, 4, 4, 4, 6, 2, 2, 6 ]

Could anyone help me out? Thanks!

CodePudding user response:

The only reason your letters worked is because you didn't have the same number of any two letters, where in your numbers, you have 2 of both 2 and 6.

Here's your snippet, but with 2 a's and 2 c's. You'll see it's out of order just like the numbers.

You need a way to sort instances that have the same number of occurrences. I adapted your forEach loop to give the last index of each letter to your b object and then changed your sort to use that index in case the number of occurrences is the same.

const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a   1 : a), 0);

function frequencySort(arr){
  let d = {}
  arr.forEach((i,index) => d[i] = {
    num: countOccurrences(arr,i),
    i: index
  });
  arr.sort(function(a,b){
    let diff = d[b].num - d[a].num;
    if(diff == 0)
      diff = d[b].i - d[a].i;
    return diff;
  })
  
  return arr
}

console.log(frequencySort(['a','b','b','b','c','c', 'a']))
console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4]));

CodePudding user response:

It has nothing to do with the elements being letters or numbers. In you letters array, each letter has unique occurence count (3, 2, 1), therefore they are sorted the way you want to.

However, in your numbers array, "2" and "6" both occur 2 times each. Therefore, your sort callback function returns 0 for them, and they are treated as equal order by the sort function.

CodePudding user response:

In your array of numbers you have the same amount of the number 2 as 6 and your sorting function doesn't care about the actual values it just cares about their counts. So in your example 2 and 6 both have the same priority.

You want to adjust your sorting function to compare values of elements if they have the same amount of occurrences. You'll need to implement separate comparisons for all the data types you want to accept and decide if you want ascending/descending order.

Here is a basic example for number and string elements:

const countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a   1 : a), 0);

function frequencySort(arr){
  let d = {}
  arr.forEach(i => d[i] = countOccurrences(arr,i))
  arr.sort(function(a,b){
    const r = d[b] - d[a]
    if (r != 0) return r
    switch (typeof d[a]) {
    case 'number': return a - b
    case 'string': return a.localeCompare(b)
    default: return 0
    }
  })
  
  return arr
}



console.log(frequencySort(['a','b','b','b','c','c'])) // returns [ 'b', 'b', 'b', 'c', 'c', 'a' ]

console.log(frequencySort([4, 6, 2, 2, 6, 4, 4, 4])) // returns [ 4, 4, 4, 4, 2, 2, 6, 6 ]

CodePudding user response:

A possible approach would first collect all equal array items within an item specific group array by a reduce task ...

console.log(
  "grouped ['a','b','b','b','c','c'] ...",
  ['a','b','b','b','c','c'].reduce((index, item) => {
    const groupList =
      index[`${ (typeof item) }_${ item }`] ??= [];

    groupList.push(item);

    return index;
  }, {})
);
console.log(
  "grouped [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'] ...",
  [4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'].reduce((index, item) => {
    const groupList =
      index[`${ (typeof item) }_${ item }`] ??= [];

    groupList.push(item);

    return index;
  }, {})
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

The final computation then has to transform ... via Object.values ... the temporary result (as shown above) into an array of arrays of equal items where the former gets 1stly sorted by each array's length (indicates the items frequency) and 2ndly, for arrays of equal length', by a locale compare of each array's first item. The final result is the sorted array's flatted version ...

function sortItemsByFrequency(arr) {

  const groupedItems = arr.reduce((index, item) => {
    const groupList =
      index[`${ (typeof item) }_${ item }`] ??= [];

    groupList.push(item);

    return index;
  }, {});

  return Object
    .values(groupedItems)
    .sort((a, b) =>
      // - sort by frequency first indicated by an
      //   array's length.
      // - the higher frequency count wins.
      b.length - a.length ||
      // in case of equal frequency counts do a
      // locale compare of both array's first items.
      b[0].toLocaleString().localeCompare(a[0].toLocaleString())
    )
    .flat();
}

console.log(
  "sortItemsByFrequency(['a','b','b','b','c','c']) ...",
  sortItemsByFrequency(['a','b','b','b','c','c'])
);
console.log(
  "sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2']) ...",
  sortItemsByFrequency([4, 6, 2, 2, 6, 4, 4, 4,'4','2','2'])
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

  • Related