24

What is an elegant way to take a javascript array, order by the frequency of the values, and then filter for uniques?

So,

["apples", "oranges", "oranges", "oranges", "bananas", "bananas", "oranges"]

becomes

["oranges, "bananas", "apples"]

8 Answers 8

39

Compute the frequency of each item first.

{
    apples: 1,
    oranges: 4,
    bananas: 2
}

Then create an array from this frequency object which will also remove the duplicates.

["apples", "oranges", "bananas"]

Now sort this array in descending order using the frequency map we created earlier.

function compareFrequency(a, b) {
    return frequency[b] - frequency[a];
}

array.sort(compareFrequency);

Here's the entire source (using the newly introduced Array functions in ECMA 5) and combining the de-duplication and frequency map generation steps,

function sortByFrequency(array) {
    var frequency = {};

    array.forEach(function(value) { frequency[value] = 0; });

    var uniques = array.filter(function(value) {
        return ++frequency[value] == 1;
    });

    return uniques.sort(function(a, b) {
        return frequency[b] - frequency[a];
    });
}

Same as above using the regular array iteration.

function sortByFrequencyAndRemoveDuplicates(array) {
    var frequency = {}, value;

    // compute frequencies of each value
    for(var i = 0; i < array.length; i++) {
        value = array[i];
        if(value in frequency) {
            frequency[value]++;
        }
        else {
            frequency[value] = 1;
        }
    }

    // make array from the frequency object to de-duplicate
    var uniques = [];
    for(value in frequency) {
        uniques.push(value);
    }

    // sort the uniques array in descending order by frequency
    function compareFrequency(a, b) {
        return frequency[b] - frequency[a];
    }

    return uniques.sort(compareFrequency);
}
Sign up to request clarification or add additional context in comments.

Comments

6

// returns most frequent to least frequent

Array.prototype.byCount= function(){
    var itm, a= [], L= this.length, o= {};
    for(var i= 0; i<L; i++){
        itm= this[i];
        if(!itm) continue;
        if(o[itm]== undefined) o[itm]= 1;
        else ++o[itm];
    }
    for(var p in o) a[a.length]= p;
    return a.sort(function(a, b){
        return o[b]-o[a];
    });
}

//test

var A= ["apples","oranges","oranges","oranges","bananas","bananas","oranges"];
A.byCount()

/* returned value: (Array) oranges,bananas,apples */

Comments

3

I was actually working on this at the same time - the solution I came up with is pretty much identical to Anurag's.

However I thought it might be worth sharing as I had a slightly different way of calculating the frequency of occurrences, using the ternary operator and checking if the value has been counted yet in a slightly different way.

function sortByFrequencyAndFilter(myArray)
{
    var newArray = [];
    var freq = {};

    //Count Frequency of Occurances
    var i=myArray.length-1;
    for (var i;i>-1;i--)
    {
        var value = myArray[i];
        freq[value]==null?freq[value]=1:freq[value]++;
    }

    //Create Array of Filtered Values
    for (var value in freq)
    {
        newArray.push(value);
    }

    //Define Sort Function and Return Sorted Results
    function compareFreq(a,b)
    {
        return freq[b]-freq[a];
    }

    return newArray.sort(compareFreq);
}

1 Comment

The loop I use to count for the frequency of occurrences checks against a constant value and loops through the array in reverse. This would perform faster on large arrays as well.
1

Basic strategy:

Create an object to use as a hash table to track the frequency of each item in the array to be sorted.

Create a new array containing the item, frequency pairs.

Sort this array on frequency in descending order.

Extract the items from that array.

Code:

function descendingUniqueSort(toBeSorted) {
    var hash = new Object();
    toBeSorted.forEach(function (element, index, array) { 
                           if (hash[element] == undefined) {
                               hash[element] = 1;
                           }
                           else {
                               hash[element] +=1;
                           }});
    var itemCounts = new Array();
    for (var key in hash) {
       var itemCount = new Object();
       itemCount.key = key;
       itemCount.count = hash[key];
       itemCounts.push(itemCount);
    }
    itemCounts.sort(function(a,b) { if(a.count<b.count) return 1; 
        else if (a.count>b.count) return -1; else return 0;});

    return itemCounts.map(function(itemCount) { return itemCount.key; });
 }

Comments

1
var arr = ["apples", "oranges", "oranges", "oranges", "bananas", "bananas", "oranges"].sort();
var freq = {};
for (var s in arr) freq[s] = freq[s] ? freq[s] + 1 : 0;
arr.sort(function(a, b) { return freq[a] > freq[b] ? -1 : 1; });
for (var i = arr.length - 1; i > 0; i--) if (arr[i] == arr[i - 1]) arr.splice(i,1);
alert(arr.join(","));

Comments

1

for the first step to compute

{
    oranges: 4,
    bananas: 2,
    apples: 1
}

you can use countBy function of underscroe.js

var all=["apples", "oranges", "oranges", "oranges", "bananas", "bananas", "oranges"];
var frequency=_.countBy(all,function(each){return each});

so frequency object will contain frequency of all unique values, and you can get an unique list by simply calling _.uniq(all), and to sort that unique list by the _.sortBy method of underscore and using your frequency object you can use

_.sortBy(_.uniq(all),function(frequencyKey){return -frequency[frequencyKey]});

-ve sign is used here to sort the list in decending order by means of frequency value as per your requirement.

You can check the the documentation of http://underscorejs.org/ for further optimization by your own trick :)

Comments

1

Create a counter of the array's elements using reduce:

arr.reduce(
  (counter, key) => {counter[key] = 1 + counter[key] || 1; return counter}, 
  {}
);

Sort the counter object using sort on Object.entries and finally show only keys.

const arr = ["apples", "oranges", "oranges", "oranges",
  "bananas", "bananas", "oranges"
];

// create a counter object on array
let counter = arr.reduce(
  (counter, key) => {
    counter[key] = 1 + counter[key] || 1;
    return counter
  }, {});
console.log(counter);
// {"apples": 1, "oranges": 4, "bananas": 2}

// sort counter by values (compare position 1 entries)
// the result is an array
let sorted_counter = Object.entries(counter).sort((a, b) => b[1] - a[1]);
console.log(sorted_counter);
// [["oranges", 4], ["bananas", 2], ["apples", 1]]

// show only keys of the sorted array
console.log(sorted_counter.map(x => x[0]));
// ["oranges", "bananas", "apples"]

Comments

1

Let me put a minimal code to get unique values (and with frequencies) in ES6.

var arr = ["apples", "oranges", "oranges", "oranges", "bananas", "bananas", "oranges"];
console.log([...new Set(arr)])
console.log([...new Set(arr)].map(fruit=>({[fruit]:arr.filter(v=>(v===fruit)).length})))

It is also applied to array of objects to aggregate some properties.

var arr = [{"fruit":"apples"}, {"fruit":"oranges"}, {"fruit":"oranges"}, {"fruit":"oranges"}, {"fruit":"bananas"}, {"fruit":"bananas"}, {"fruit":"oranges"}];
console.log(arr.reduce((x,y)=>{if(x[y.fruit]) {x[y.fruit]++;return x;} else {var z={};z[y.fruit]=1;return Object.assign(x,z);}},{}))

1 Comment

How does this give you frequencies? It only consolidates

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.