Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iteration over a typescript map failing

I'm using the function below to compare two maps. What's interesting is that the code inside for loop is never executed. So, console.log(key,val) code is never executed. Of course I made sure that the maps I'm comparing are not empty and are of same size to force execution of the code inside the for loop. Am I making a really silly mistake or am missing a deep concept?

private compareMaps(map1, map2) {
        var testVal;
        if (!(map1 && map2)) {
            return false;
        }
        if (map1.size !== map2.size) {
            return false;
        }
        for (var [key, val] of map1) {
            testVal = map2.get(key);
            console.log(key, val);
            if (testVal !== val || (testVal === undefined && !map2.has(key))) {
                return false;
            }
        }
        return true;
    }
like image 346
quantdaddy Avatar asked Dec 19 '22 05:12

quantdaddy


1 Answers

What's interesting is that the code inside for loop is never executed. Am I making a really silly mistake or am missing a deep concept?

You're missing the fact that...

for...of does not work on Map in TypeScript prior to ES6

When it targets ECMAScript prior to ES6, the TypeScript compiler transpiles the for...of statement into a for loop.

This code:

for (var [key, val] of map) {
    console.log(key);
}

Becomes this code:

for (var _i = 0, map_1 = map; _i < map_1.length; _i++) {
    var _a = map_1[_i], key = _a[0], val = _a[1];
    console.log(key);
}

What to do?

Option 1: When we must target ECMAScript prior to ES6, the forEach function can be a suitable alternative to the for...of loop. Beware though that forEach has subtle differences from for...of (e.g. exiting early, async/await).

map.forEach((val, key) => { 
    console.log(key + ":" + val);
});

Option 2: When we must target ECMAScript prior to ES6, and we must have the exact behavior of for...of, the answer by John Weisz makes sense: convert the Map to an Array and iterate the Array with for...of.

Option 3: When we can target ECMAScript ES6 or later, use the for...of loop directly on the Map.

Aside: Map Equality

If the requirement is Map equality, consider the every function. That takes care of the need to break in the iteration, because every immediately returns when it finds the first false.

If order matters to the comparison, use every like this:

function areEqual<K,V>(map1: Map<K,V>, map2: Map<K,V>) { 
    if(!map1 || !map2) return false;
    const array1 = Array.from(map1.entries());
    const array2 = Array.from(map2.entries());
    return array1.every(([k1, v1], index) => {
        const [k2, v2] = array2[index];
        return k1 === k2 && v1 === v2;
    });
}

If order does not matter, then we want set equality; use every like this:

function areSetEqual<K, V>(map1: Map<K, V>, map2: Map<K, V>) { 
    if(!map1 || !map2) return false;
    const array1 = Array.from(map1.entries());
    const array2 = Array.from(map2.entries());
    return array1.length === array2.length &&
        array1.every(([k1, v1]) => map2.get(k1) === v1);
}

Here is a demo of the functions in action:

const map1 = new Map([["key1", "val1"], ["key2", "val2"]]);
const map2 = new Map([["key1", "val1"], ["key2", "val2"]]);
const map3 = new Map([["key2", "val2"], ["key1", "val1"]]);

console.log(areEqual(map1, map2)); // true
console.log(areEqual(map1, map3)); // false
console.log(areSetEqual(map1, map2)); // true
console.log(areSetEqual(map1, map3)); // true
like image 123
Shaun Luttin Avatar answered Dec 24 '22 01:12

Shaun Luttin