3

I have been trying to solve a puzzle of counting characters in a string and found the following code. The code works, but I'm not able to understand the replace part:

function getCharCounts(s) {
    var letters = {};
    s.replace(/\S/g, function(s){
        letters[s] = (isNaN(letters[s] ? 1 : letters[s]) + 1);
    });

    return letters;
}

console.log(getCharCounts('he111 144pressions'));​

Would someone please explain the code to me or write a simpler version?

Aaron Kurtzhals
  • 2,036
  • 3
  • 17
  • 21
JS-coder
  • 3,243
  • 2
  • 14
  • 14
  • Read: http://www.aivosto.com/vbtips/regex.html and http://stackoverflow.com/questions/2595392/what-does-the-question-mark-and-the-colon-ternary-operator-mean-in-objectiv – howderek Mar 04 '13 at 14:42
  • Are you sure it works? I tried it with "abbc" and got this: {"a": 2, "b": 1, "c": 2} http://jsbin.com/uzexer/1/edit – Steve Wellens Mar 04 '13 at 14:49
  • 1
    @SteveWellens this is because of the bracket error in the isNaN line. – jantimon Mar 04 '13 at 14:50
  • Why use isNaN, and not just `letters[s] = (letters[s] || 0) + 1`, I wonder? – raina77ow Mar 04 '13 at 15:02

2 Answers2

6
function getCharCounts(s) {

    // This variable will be visible from inner function.
    var letters = {};

    // For every character that is not a whitespace ('\S') 
    // call function with this character as a parameter.
    s.replace(/\S/g, function(s){

        // Store the count of letter 's' in 'letters' map.
        // On example when s = 'C':
        //  1. isNaN checks if letters[c] is defined. 
        //     It'll be defined if this is not a first occurrence of this letter.
        //  2a. If it's not the first occurrence, add 1 to the counter.
        //  2b. If it's the first occurrence, assigns 1 as a value.
        letters[s] = (isNaN(letters[s]) ? 1 : letters[s] + 1);
    });

    return letters;
}

Note: Brackets in isNaN() were wrong. Code above is corrected.

kamituel
  • 34,606
  • 6
  • 81
  • 98
  • 2
    In case the OP was wondering, the first parameter in replace is a regular expression - `/\S/g`. The `\S` is a non-space character card inside of a regular expression wrapper `/.../` with a global flag, `g`. – Jesse Mar 04 '13 at 14:58
  • 1
    Subtlety: `isNaN` checks whether or not letters[c] is (or converts to) `NaN` value. `undefined` does convert to `NaN` when cast to `Number`. `null` does not. – raina77ow Mar 04 '13 at 15:03
2

Here's a simpler example:

function getCharCounts(s) {
    var letters = {};
    var is_not_whitespace = /\S/;

    // Iterate through all the letters in the string
    for (var i = 0; i < s.length; i++) {
        // If the character is not whitespace
        if (is_not_whitespace.test(s[i])) {
            // If we have seen this letter before
            if (s[i] in letters) {
                // Increment the count of how many of this letter we have seen
                letters[s[i]]++;
            } else {
                // Otherwise, set the count to 1
                letters[s[i]] = 1;
            }
        }
    }

    // Return our stored counts
    return letters;
}
Xymostech
  • 9,710
  • 3
  • 34
  • 44
  • I think the original function is simpler since it uses the method call as the iterator, defines the regular expression in the method call, and uses an inline conditional statement. But I guess it depends on how you define simpler. – Jesse Mar 04 '13 at 15:08
  • 2
    @Jesse I guess I was going for simpler as in "uses less things that a beginner might not know about". If you don't understand what the callbacks for `.replace` do, or if you don't understand ternary operations, mine might be simpler. – Xymostech Mar 04 '13 at 22:44