Advertisement
If you have a new account but are having problems posting or verifying your account, please email us on hello@boards.ie for help. Thanks :)
Hello all! Please ensure that you are posting a new thread or question in the appropriate forum. The Feedback forum is overwhelmed with questions that are having to be moved elsewhere. If you need help to verify your account contact hello@boards.ie

Javascript question about counting elements in an array?

Options
  • 14-12-2013 9:11pm
    #1
    Registered Users Posts: 225 ✭✭


    So if I have an array like this ("a", "b", "b", "c", "a", "b", "b", "c", "a")

    I want to rewrite it as ("b" : 4, "a" : 3, "c" : 2)

    Is there a quick way to do this?

    I have tried using a for loop and returned a string like this: "a:3, b:4, c:2" but the output is not in order of most frequent unfortunately, it seems random, which is useless.

    So if anyone has a quick solution from the start or a means of manipulating that string output to make it in the format I want (or basically displaying the most common element in order of most common), that would be really helpful!


Comments

  • Registered Users Posts: 2,022 ✭✭✭Colonel Panic


    Sort the array, then get you loop on.


  • Registered Users Posts: 11,979 ✭✭✭✭Giblet


    var array = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    var counts = {};
    
    array.reduce(function(fold, out){
        counts[out] = (counts[out] || 0) + 1;
        return counts;                
    });
    

    Reduce is new enough, but you can get implementations of it for older browsers.


  • Registered Users Posts: 225 ✭✭TheSetMiner


    Giblet wrote: »
    var array = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    var counts = {};
    
    array.reduce(function(fold, out){
        counts[out] = (counts[out] || 0) + 1;
        return counts;                
    });
    

    Reduce is new enough, but you can get implementations of it for older browsers.
    I will try this in the morning. Thank you. if it is this simple I am eternally grateful haha


  • Registered Users Posts: 225 ✭✭TheSetMiner


    The latter didn't work.

    I am looking at sorting it in order of most common elements and then looping it but notice that the sort() command sorts them alphabetically so I am gettting an output of:

    ("a" : 3, "b" : 4, "c" : 2)

    instead of

    s ("b" : 4, "a" : 3, "c" : 2)

    So I have deduced that I need to somehow sort the original list in order of most commonly appearing elements.

    If I can get the input to be ("b", "b", "b", "b", "a", "a", "a", "c", "c") then I am sorted, pun intended.

    So is there a way to use the sort() command to achieve this?


  • Registered Users Posts: 11,979 ✭✭✭✭Giblet


    Did you sort after running my command? You don't need to sort after reduce.


  • Advertisement
  • Registered Users Posts: 225 ✭✭TheSetMiner


    Giblet wrote: »
    Did you sort after running my command? You don't need to sort after reduce.

    No I did it exactly as you wrote but it didn't work for some bizarre reason. I am fiddling around with it now trying to get it to work.

    If I can get it sorted by most common elements, the loop will work perfectly.

    Is there any way your code wouldn't work on chrome?


  • Registered Users Posts: 225 ✭✭TheSetMiner


    I tried putting your block of code into a function and returning the count object but it is just undefined. I thought there would be a simple built in function to reorder an array with most common elements first, this is a frustrating one!


  • Registered Users Posts: 11,979 ✭✭✭✭Giblet


    Ah ok, it's not count you need to worry about, but the return value of array.reduce
    var array = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    var counts = {};
    
    var sortedOutput = array.reduce(function(fold, out){
        counts[out] = (counts[out] || 0) + 1;
        return counts;                
    });
    


  • Registered Users Posts: 225 ✭✭TheSetMiner


    It is now giving back [object Object] as opposed to any kind of array? Am I missing something?


  • Registered Users Posts: 6,153 ✭✭✭Talisman


    I have tried using a for loop and returned a string like this: "a:3, b:4, c:2" but the output is not in order of most frequent unfortunately, it seems random, which is useless.
    The reason it's random is because there is no requirement in the ECMAScript specification for the object properties to be stored in a specified order.

    If you want to guarantee the order of the list then an array is the proper data structure to use.


  • Advertisement
  • Registered Users Posts: 225 ✭✭TheSetMiner


    It is not random exactly. It is the order in which I feed the array into the loop. I can use .sort() but then it feeds it in in alphabetical order and returns the results in alphabetical order. So if I can only feed them in in order of most common, then the output will be exactly what I want. Any ideas?


  • Registered Users Posts: 6,153 ✭✭✭Talisman


    Yes the order is determined by that which they are inserted. The same behavior is seen in Ruby and Python when working with similar data structures.

    Here's what I have come up with, it generates a more complex structure (objects within an array) but the order is correct:
    var elements = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    // counts object records the totals for each unique element
    var counts = {};
    // result array which stores sorted objects
    var result = [];
    
    var elementCount = function (x) {
        counts[x] = (counts[x] || 0) + 1;
    };
    
    // populate counts structure
    forEach(elements, elementCount);
    
    Object.keys(counts)
        .map(function (k) { return [k, counts[k]]; })
        .sort(function (a, b) {
            if (a[1] < b[1]) return 1;
            if (a[1] > b[1]) return -1;
            return 0;
        })
        .forEach(function (d) {
            var o = {};
            o['element'] = d[0];
            o['count'] = d[1];
            result.push(o);
        });
    
    console.log(JSON.stringify(counts));
    // {"a":3,"b":4,"c":2}
    
    console.log(result);
    // [Object { element="b", count=4}, Object { element="a", count=3}, Object { element="c", count=2}]
    
    console.log( result[0].element );
    // b
    
    console.log( result[0].count );
    // 4
    

    The code makes use of forEach and map which aren't available in older browsers but you can create helper functions to add support for them. See Map, Filter, and Fold in JavaScript for code that you could use.


  • Registered Users Posts: 225 ✭✭TheSetMiner


    Talisman wrote: »
    Yes the order is determined by that which they are inserted. The same behavior is seen in Ruby and Python when working with similar data structures.

    Here's what I have come up with, it generates a more complex structure (objects within an array) but the order is correct:
    var elements = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    // counts object records the totals for each unique element
    var counts = {};
    // result array which stores sorted objects
    var result = [];
    
    var elementCount = function (x) {
        counts[x] = (counts[x] || 0) + 1;
    };
    
    // populate counts structure
    forEach(elements, elementCount);
    
    Object.keys(counts)
        .map(function (k) { return [k, counts[k]]; })
        .sort(function (a, b) {
            if (a[1] < b[1]) return 1;
            if (a[1] > b[1]) return -1;
            return 0;
        })
        .forEach(function (d) {
            var o = {};
            o['element'] = d[0];
            o['count'] = d[1];
            result.push(o);
        });
    
    console.log(JSON.stringify(counts));
    // {"a":3,"b":4,"c":2}
    
    console.log(result);
    // [Object { element="b", count=4}, Object { element="a", count=3}, Object { element="c", count=2}]
    
    console.log( result[0].element );
    // b
    
    console.log( result[0].count );
    // 4
    

    The code makes use of forEach and map which aren't available in older browsers but you can create helper functions to add support for them. See Map, Filter, and Fold in JavaScript for code that you could use.

    forEach is not defined...
    any other suggestions?


  • Registered Users Posts: 6,153 ✭✭✭Talisman


    forEach is not defined...
    any other suggestions?

    My apologies, I forgot to mention that the code for the forEach function was on the page I had provided the link to.
    var forEach = function (obj, iterator, thisArg) {
    
    	// test for native forEach support
    	if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
    		obj.forEach(iterator, thisArg);
    
    	// arrays
    	} else if (obj.length === +obj.length) {
    		for (var i = 0, l = obj.length; i < l; i += 1) {
    			iterator.call(thisArg, obj[i]);
    		}
    
    	// objects
    	} else {
    		for (var key in obj) {
    			if(obj.hasOwnProperty(key)) {
    				iterator.call(thisArg, obj[key]);
    			}
    		}
    	}
    };
    

    If you don't want to use such helper functions and instead use native browser support then the code below will work provided the browser is recent enough.
    var elements = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    var counts = {};
    var result = [];
    
    elements.forEach(function (x) {
        counts[x] = (counts[x] || 0) + 1;
    });
    
    Object.keys(counts)
        .map(function (k) { return [k, counts[k]]; })
        .sort(function (a, b) {
            if (a[1] < b[1]) return 1;
            if (a[1] > b[1]) return -1;
            return 0;
        })
        .forEach(function (d) {
            var o = {};
            o['element'] = d[0];
            o['count'] = d[1];
            result.push(o);
        });
    
    console.log(JSON.stringify(counts));
    console.log(result);
    
    console.log( result[0].element );
    console.log( result[0].count );
    


  • Registered Users Posts: 1,311 ✭✭✭Procasinator


    To work in older browsers, you could write it like this:
    var elements = ["a", "b", "b", "c", "a", "b", "b", "c", "a"];
    var counts = {};
    for (var i = 0; i < elements.length; i++) {
        counts[elements[i]] = (counts[elements[i]] || 0) + 1;
    }
    
    var elementFrequencies = [];
    
    for (var key in counts) {
        if (counts.hasOwnProperty(key)) {
            elementFrequencies.push({ element: key, frequency: counts[key]});
        }
    }
    
    elementFrequencies.sort(function(a, b) {
        if (a.frequency > b.frequency) return -1;
        if (a.frequency < b.frequency) return 1;
        return 0;
    });
    


Advertisement