1

I'm doing a beginner exercise, find the mean/median/mode/range of an array of numbers. I'm on the mode now, and found this:

var store = ['1','2','2','3','4'];
var frequency = {};  // array of frequency.
var max = 0;  // holds the max frequency.
var result;   // holds the max frequency element.
for(var v in store) {
        frequency[store[v]]=(frequency[store[v]] || 0)+1; // increment frequency.
        if(frequency[store[v]] > max) { // is this frequency > max so far ?
                max = frequency[store[v]];  // update max.
                result = store[v];          // update result.
        }
}

It works but I don't understand it.

  • What does the || 0 do in the first line?
  • Why can't I change the key names?

frequency["key"+store[v]]=(frequency[store[v]] || 0)+1; returns {key1: 1, key2: 1, key3: 1, key4: 1} not {1: 1, 2: 2, 3: 1, 4: 1}, so the keys are playing an important role.

  • Is the if statement testing both the key and value?

Replacing any instance of frequency[store[v]]; with a variable (var freqTest = frequency[store[v]];, created inside or outside the loop) breaks something.

The whole thing is going over my head really.

delz
  • 89
  • 1
  • 9
  • 2
    First of all, you actually [shouldn't use `for…in` enumerations on arrays!](https://stackoverflow.com/q/500504/1048572) – Bergi Sep 26 '15 at 16:16
  • The `|| 0` uses 0 as a default value if `frequency[store[v]]` isn't defined. Simplified example: `var x = undefined || 2` results in `x === 2` – ray Sep 26 '15 at 16:17
  • @Bergi fair enough! I'll remember that. I just want to understand how it works is all. – delz Sep 26 '15 at 16:35

6 Answers6

2

What does the || 0 do in the first line?

It takes 0 as a default value when the lookup fails (when there is not yet a frequency property with that name), so that the map is initialised with 1s on the first appearance of a value, not NaN (from undefined + 1).

The assignment can (and for beginners, should) be expanded to

if (frequency[key]) // the property already exists, does not give `0` or `undefined`
    frequency[key] = frequency[key] + 1;
else // the property didn't exist and the access yielded `undefined`
    frequency[key] = 1; // 0 + 1

Why can't I change the key names?

You can, you just have to do it everywhere.

The code should be written much cleaner like this:

var store = ['1','2','2','3','4'];
var frequency = {};  // object (key-value-map) of frequency
var max = 0;  // holds the max frequency value
var result;   // holds the max frequency element name
for (var v=0; v<store.length; v++) {
    var key = "key" + store[v];
    frequency[key] = (frequency[key] || 0)+1; // increment frequency
//                              ^^^ here as well
    if (frequency[key] > max) { // is this frequency > max so far ?
        max = frequency[key];   // update max.
        result = store[v];      // update result.
//               ^^^^^^^^ alternatively use `key` also here
    }
}

Is the if statement testing both the key and value?

Testing? Hm, no. It does use the value from the store array as a key in the frequency object. It does then compare the property value with the max.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • The way you wrote it is much clearer. How does `frequency[key] = (frequency[key] || 0)+1;` return a number as a value though? Wouldn't it concatenate to `key1=key01` or `key1=key11`? – delz Sep 26 '15 at 16:33
  • `freqency[key]` is an integer (the count of occurrences), not a string like the `key` is. Notice the [bracket notation](http://stackoverflow.com/q/4968406/1048572) - it's like `frequency.key0 = frequency.key0 + 1;` – Bergi Sep 26 '15 at 16:35
  • I guess I don't get how it can be `frequency.key0 = frequency.key0 + 1; || 0 + 1;`, meaning `frequency.key0 = 1`. Maybe I need more coffee... – delz Sep 26 '15 at 16:52
  • You've got a spare `;` in there? Also, the original code does check for `frequency[key]` to be falsy, not `frequency[key] + 1`. – Bergi Sep 26 '15 at 16:55
  • Dumb question but why are `frequency[store[v]]=(frequency[store[v]] || 0)+1;` and `frequency[store[v]]=(frequency[store[v].valueOf()] || 0)+1;` the same thing? Is it not possible to compare keys? – delz Sep 27 '15 at 10:02
  • Well `store[v]` is a string (those `'1'`, `'2'`, … from your array), and their [`valueOf` method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/valueOf) returns that string itself again, so absolutely no difference. Would you have expected some? I am not sure what you mean by "compare keys" here? – Bergi Sep 27 '15 at 12:30
  • Poor example sorry. Say I do `var i = {0:2, 1:1};`, then `if (i[0] > i[1]) {return true;}`. It returns true because the value is higher, but the key is lower. I guess I'm just wondering why it's not more literal, `i[0]` is the "key value pair"/property in its entirety right? Why is it assumed that I want to compare values and not keys? – delz Sep 27 '15 at 14:01
  • I'd call `i[0]` "the value", as thats what it'll refer to. "*Why is it assumed that I want to compare values*" - that follows from the algorithm. You are looking for the maximum occurrence (absolute frequency), and then take the key for that as `result`. If you'd compare keys, you'd get the last-most-in-the-alphabet letter instead. – Bergi Sep 27 '15 at 15:17
2

The key in the entire logic is understanding this line

frequency[store[v]]=(frequency[store[v]] || 0)+1;

The left side is being used as a map for some number. When v is equal to 3 store[3] returns 2 and thus frequency[2] is accessed.

Now for the same iteration consider the right side. We already know that

frequency[store[3]] 

resolves to

frequency[2]

but what will this return? As frequency[2] would have also been set in iteration 2 we would be accessing the number from iteration 2. Lets now look at the value derived from iteration 2 then:

frequency[store[2]] = (frequency[store[2]] || 0)+1
frequency[2] = (frequency[2] || 0)+1
frequency[2] = (null || 0)+1
frequency[2] = 1

Ahhh... so the value for iteration 3 is actually

frequency[2] = (frequency[2] || 0) + 1
frequency[2] = (1 || 0) + 1
frequency[2] = (1) + 1
frequency[2] = 2

As you can see, the loop is using frequency[n] as a map and increments the value each time it is found. Then the value is stored in max if it is higher. This is a very smart way to find the highest repeating value while only iterating over the list one time.

josh26757
  • 34
  • 3
  • That way of writing it out is very helpful. I wish more people did that. Why does it choose frequency[2] over 0 in iteration 3 though? – delz Sep 26 '15 at 17:13
  • This is the best explanation so I'll give you the answer. I don't fully understand it though, not because of you guys, but because I'm too new to this. The nested values/increment/looping is too complex for my little head...lol – delz Sep 26 '15 at 17:20
  • I do not use JavaScript much, but the left option is selected first as long as it is valid. If the value has not been set such as the first and second iteration then zero is selected; otherwise, the value is incremented and checked against max. This is clever and optimized, but perhaps more complex than it needs to be. I must say I like the algorithm though. – josh26757 Sep 26 '15 at 23:20
  • I finally figured it out. I'm not sure what I was finding so difficult about it. I do kind of wish `frequency[store[v]]=(frequency[store[v]] || 0)+1;` and `frequency[store[v]]=(frequency[store[v].valueOf()] || 0)+1;`didn't mean the same thing. Or it was written that way the first time. I got hung up on that longer than I should have. – delz Sep 27 '15 at 10:00
0

a || 0 means if a is not undefined, take 1 otherwise 0

You can change the key names.

var store = ['1','2','2','3', '1', '1','4'];
var frequency = {};  // array of frequency.
var max = 0;  // holds the max frequency.
var result;   // holds the max frequency element.
for(var v in store) {
    frequency['key'+store[v]]=(frequency['key'+store[v]] || 0)+1; // increment frequency.
    if(frequency['key' + store[v]] > max) { // is this frequency > max so far ?
            max = frequency[store[v]];  // update max.
            result = 'key' + store[v];          // update result.
    }
}
Fredo
  • 320
  • 3
  • 15
0

The line you ask about frequency[store[v]]=(frequency[store[v]] || 0)+1 is sometimes referred to OR assignment; this stack overflow question has some good explanations and examples. For your code, consider this that I just typed into my browser's javascript console:

> var frequency = {};
<- undefined
> frequency[0];
<- undefined
> frequency[0] || 0
<- 0

As for you why you can't change the key names, you can, you just haven't changed them 'enough'. Changing the body to replace every key reference with "key"+store leaves the code in the same functioning state.

for(var v in store) {
    // Increment the frequency of the value v
    frequency["key"+store[v]]=(frequency["key"+store[v]] || 0)+1; 
    // is this frequency > max so far ?
    if(frequency["key"+store[v]] > max) {
        // If it is, we have a new, most frequently occurring number
        // Update the max to this new highest frequency
        max = frequency["key"+store[v]];
        // Update the result to the new most frequent value
        result = store[v];
    }
}

I added some additional comments in the code to make it clearer what's going on.

Community
  • 1
  • 1
Patrick M
  • 10,547
  • 9
  • 68
  • 101
0

To your first question: In JavaScript you can test if variables are defined by using them as booleans.

var foo;
if(foo) //true
    console.log('foo is false, 0, null or not defined');

So in this case you are testing if frequency already has an element for store[v]. If it does, use that, otherwise use 0 instead so that would be the same as

var valueInFrequency = frequency[store[v]] ? frequency[store[v]] : 0;

and then continue with valueInFrequency.

To your second question: As I explained just now, in

frequency[store[v]]=(frequency[store[v]] || 0)+1;

you either raise the current value by one or set it to 0 and then raise it by one. If you change the key you set the value to but then don't test for the new value, you end up simply overriding the existing value to 0 + 1.

Now to your last question: No it isn't. It uses store[v] as a key for frequency and then compares that value to max.

I hope I could answer your questions. If anything is still unclear, just ask!

LBBO
  • 85
  • 1
  • 9
0

I propose a better solution to this problem as the given solution.

Solution with emphasis to Array.prototype.forEach and the problem of getting more than one key if the max count is shared among more items.

What has changed:

  • result is now an array, because the maximal count of the distribution can affect more than one key/item.
  • The for () loop is replaced by Array.prototype.forEach and a callback which allows an iteration over all ements of an array in a more compact manner.
  • Only max is in the callback stored.
  • For the keys/item with the max count is another loop necessary.
  • First get the keys from the object with Object.keys
  • Then iterates over the keys and check for count === max and push the key
  • Display all found values.

To the question what x = x || y means: If the value of x is falsy (like undefined, null, 0, -0, '') the the value of y is used, because of the Logical Or operator.

var store = ['1', '2', '2', '3', '4', '5', '5'],
    distribution = {},
    max = 0,
    result = [];

store.forEach(function (a) {
    distribution[a] = (distribution[a] || 0) + 1;
    if (distribution[a] > max) {
        max = distribution[a];                
    }
});
Object.keys(distribution).forEach(function (k) {
    distribution[k] === max && result.push(k);
});
document.write('max: ' + max + '<br>');
document.write('key/s with max count: ' + JSON.stringify(result) + '<br>');
document.write('<pre>' + JSON.stringify(distribution, 0, 4) + '</pre>');
Community
  • 1
  • 1
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392