Showing posts with label map. Show all posts
Showing posts with label map. Show all posts

Friday, October 16, 2015

JavaScript Collections - Map

I still can't fold them back up!
Ready to be confused? Okay, so you know that JavaScript has a map function on Array objects. It creates a new Array by applying a given function to each element of the Array. But now it also has a Map collection object. Now when you talk about a Map in JavaScript, are you talking about a function on an Array or a key/value pair data structure?

Those of us who did a lot of programming in other languages before JavaScript are probably familiar with classic data structures like the Map. A Map is a (typically) indexed collection of keys and their associated values. If you know the key, you can grab the value. C++ has collections objects, as does Java. JavaScript has not had a native Map until recently, but I've never really missed it. Why? Keep reading.

In many of the newest browsers and in Node 4, we can now use Maps. Their functionality should come as no surprise to anyone familiar with basic data structures found in other languages.

var m = new Map();

m.set("key1", "value1");
m.set("key2", "value2");

console.log(m.size); // 2

console.log(m.get("key1")); // "value1"
console.log(m.get("key3")); // undefined
console.log(m.has("key2")); // true
console.log(m.has("key4")); // false

But this is JavaScript! We needn't limit ourselves to just one type of key or value! Check this out:

var p = new Map();

// function as a value...not too strange:

p.set("f1", function(x) { return x*42; });

console.log(p.get("f1")(2)); // 84

// function as a key...a bit odd:

var w = function(z) {
    return z % 3;
};

p.set(w, "weird");

console.log(p.has(w)); // true
console.log(p.get(w)); // weird

Sure, it's perfectly legal code...but why? I know I'm not the world's smartest software engineer, but I cannot think of an example where putting a function as the key in a Map would make any sense. If you can think of one, please comment below.

Now we're having fun with Maps and we're all excited about JavaScript getting this great new data structure. But this is not a whole lot different from what we could always do in JavaScript with objects, since objects behave pretty much like Maps.

var h = {};

h["key1"] = "value1";
h["key2"] = "value2";
h["func1"] = function(t) { return t / 8; };

console.log(h["key1"]);      // value1
console.log(h["key3"]);      // undefined
console.log(h["func1"](16)); // 2
console.log(h.func1(32));    // 4

See? Pretty much the same as my first Map example up at the top. Fine, plain Objects have no size field...so I suppose that's one thing that Maps offer. There are also some differences is how iteration is handled. Consider the following example:

var myMap = new Map();
myMap.set("k1", 33);
myMap.set("k3", 55);

var myObj = {};
myObj["k1"] = 33;
myObj["k3"] = 55;

for (e of myMap) {
    console.log(e);    // the key and value as an array
    console.log(e[0]); // just the key
    console.log(e[1]); // just the value
}

for (e in myObj) {
    console.log(e);        // just the key
    console.log(myObj[e]); // the value
}

Iterating over the Map with for (e of myMap) gives you values for e that are 2-element arrays containing both the key and the value for each Map entry. Iterating over the Object with for (e in myObj) gives you values of e that are just the key.

I'm not going to tell you that JavaScript Maps are the greatest thing since sliced bread, but maybe sliced bread was never that big of a deal anyway. It's not like people didn't have knives. And speaking of bread...

Amphibian.com comic for 16 October 2015

Wednesday, August 19, 2015

Loop or Map?

If I use map, can I fold it
back up when I'm done?
Here's something that has been coming up a lot lately: when processing an Array in JavaScript, should I use a for loop or the map function?

If you've looked at my code you'll notice a distinct lack of map. I don't use it very much. I honestly don't think I do a lot of Array iteration in general, but when I do, I typically just write a for loop. Good? Bad? Does it matter?

Let's look at a typical scenario in my comic code...taking an array of results from a database query and turning them into an array of data to return.

pool.query('SELECT filename, type FROM comic_img', function(err, rows) {

    var data = [];

    if (rows.length > 0) {
        for (var i = 0; i < rows.length; i++) {
            data.push({
                name: rows[i].filename,
                mimeType: rows[i].type
            });
        }
    }

}

That's the method using a for loop. Pretty straightforward. Make an empty array for the return data, loop over the rows of the query results, pushing new objects to the data array that are made from parts of the objects in the rows array.

Now let's do the same thing with the map function.

pool.query('SELECT filename, type FROM comic_img', function(err, rows) {

    var data = rows.map(function(val) {
        return {
            name: val.filename,
            mimeType: val.type
        };
    });

}

It's a little more compact, without the need for the definition and incrementation of i, comparison with rows.length, or access to the object by index. You could argue that the map version is more readable, which is nice. I do like my code to be very readable.

What you can't say is that the map version is any faster. Internally, map has to be doing a loop plus other stuff, which will undoubtedly make it slightly less performant than the basic for loop. The speed difference won't be that much different, so in most cases you shouldn't be too concerned.

There is another benefit to the map coding style. When you already have your code structured this way, it's easy to add asynchronicity. What if you just want the array processed but don't care about the order? Remember when I talked about the Node async module? It's simple to turn the plain map code into asynchronous map code:

var async = require('async');

var rows = getSomeArray();

async.map(rows, function(val, callback) {
    callback(null, {
        name: val.filename,
        mimeType: val.type
    });
}, function(err, data) {
    console.log(data);
    // do whatever you want with the resulting data
});

Instead of the processing function returning the new data, it passes it as the second argument (first would be any error that occurred) to its supplied callback function. And instead of having map return the new array, async has you specify a third argument which is a callback function. When everything is done, it gets called with the results. Sure, in this example there's not really any point to making the processing asynchronous, but if your processing involves anything with the file system or a web service call you have a perfect candidate.

That would be a lot more difficult to do with a for loop. You'd actually need several for loops. And probably some recursion. And magic. Just forget about it.

The moral of the story is, map is probably better in most cases than for loops. It's more readable and gives you a good foundation for expansion in the future. I'll try to do better at it myself. I'll also try to make better comics, but for today this is all you get:

Amphibian.com comic for 19 August 2015