简体   繁体   中英

How does this for/in loop work exactly?

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 || 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.

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 1 s 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 .

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.

a || 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.
    }
}

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.

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!

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 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>'); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM