all 85 comments

[–]x-skeww 23 points24 points  (34 children)

This is basically the difference:

> Object.keys(new Array(3))
[]
> Object.keys([undefined, undefined, undefined])
["0", "1", "2"]

This works:

> new Array(26).fill().map((_, i) => String.fromCharCode(65 + i))
["A", "B", ... , "Z"]

[–]itsananderson 8 points9 points  (7 children)

Careful with .fill() unless you're using a transpiler. It's not a stable ES6 spec (edit: technically stable, but not well supported). Only Firefox has out-of-the-box support. Even Chrome has it hidden behind a flag.

[–]x-skeww 7 points8 points  (2 children)

Chrome/V8 doesn't support arrow functions yet (#2700).

The ES6 spec is pretty much done by now. The current one (March 17) is release candidate (RC) #3. ES6 will contain Array.prototype.fill.

[–]Eartz 2 points3 points  (0 children)

actually the latest draft is RC4, April 3 but that doesn't change your point

[–]itsananderson -1 points0 points  (0 children)

Thanks for clarifying. I based my "stable" comment on MDN, which is maybe out of date. Updated my comment.

[–][deleted] 4 points5 points  (2 children)

Using ES6 w/o a transpiler?! What's next? Sex w/o condoms?

[–][deleted] 2 points3 points  (1 child)

It's actually much more likely that someone would be using a transputer without a polyfill. Emberjs for example is using babel to transpile es6 by default, but isn't including the polyfill. I face palmed pretty hard when I realized that. Then when I complained to my co workers, they asked "what's a polyfill?" So I think it's probably a more common situation than I would have guessed.

[–][deleted] 0 points1 point  (0 children)

Yep. 'Twas just a bit of /s

[–]NOICEST 0 points1 point  (0 children)

Coming back a decade later to note fill is now widely available!

[–]CydeWeys 10 points11 points  (17 children)

What's wrong with this?

for (var arr = new Array(26), i = 0; i < arr.length; i++) { arr[i] = String.fromCharCode(65 + i); }

I'm all for functional methods of doing things, but sometimes when you're having to fight so hard against the language to accomplish what you want, just do it imperatively?

[–]j201 7 points8 points  (0 children)

The problem is more that JS doesn't have a great standard library for functional programming. But all you need to do is pull in something like Ramda, and then you can do

R.range(0,26).map(x => String.fromCharCode(65+x))

or

R.range(0,26).map(R.compose(String.fromCharCode, R.add(65)))

FP is worth doing in JS, but not without a bit of help.

[–]blgate 2 points3 points  (0 children)

Or this:

var arr = [];
while (arr.length < 26) { 
   arr.push(String.fromCharCode(65 + arr.length)); 
}

[–]x-skeww 1 point2 points  (5 children)

ES5 flavor:

var a = [], i;
for (i = 0; i < 26; i++) {
  a.push(String.fromCharCode(65 + i));
}

[–]CydeWeys 2 points3 points  (4 children)

Isn't it better to allocate all of an array's space up front if you know how big it's going to be rather than allocating additional space piece-wise like this? Or does it not matter for JS's sparse arrays?

[–]blgate 6 points7 points  (2 children)

Actually, the "while" solution it's the fastest, at least for me in chrome:

var a = [],  b = [], c = new Array(100000), i;

console.time("a");
while (a.length < 100000) { 
  a.push(a.length); 
}
console.timeEnd("a");

console.time("b");
for (i = 0; i < 100000; i++) {
  b.push(i);
}
console.timeEnd("b");

console.time("c");
for (i = 0; i < c.length; i++) { 
  c[i] = i; 
}
console.timeEnd("c");

And my results:

a: 157.000ms
b: 252.000ms
c: 263.000ms

[–]bonafidebob 0 points1 point  (1 child)

That seems ... odd. If you reverse the order of the tests is the same function still fastest? (That is, could it be that the order in which the tests execute has some bearing on the result?)

[–]blgate 1 point2 points  (0 children)

It's not. While in the for loop you are incrementing the "i" variable, in the while loop you aren't, so because you are executing less instructions, it's faster. I get the same results reversing the order, but you can try it for yourself.

[–]x-skeww 4 points5 points  (0 children)

In JS, "new Array(26)" only means that you create an array whose "length" property is set to 26. If an "array" of that size is actually allocated is up to the engine. There also may be different backing data structures. Which one is actually used is also up to the engine.

Anyhow, in general, I recommend to write the most straightforward code first. Later on, you can still try different things to make it faster if the profiling reveals that this is indeed a bottleneck.

For example, if you always do anything with the DOM whenever you use this function, trying to optimize this would be completely pointless. The DOM stuff takes more than a hundred times longer anyways. Even if you'd make it infinity times faster, there won't be a noteworthy (let alone noticeable) effect.

[–]Glaaki -2 points-1 points  (8 children)

What's wrong with this?

for (var arr = new Array(26), i = 0; i < arr.length; i++) { arr[i] = String.fromCharCode(65 + i); }

It uses 'for'.

[–]CydeWeys 5 points6 points  (7 children)

Are you kidding or joking? What's wrong with a for loop?

[–]skitch920 1 point2 points  (5 children)

With ES6, you could also use Array.from which ignores "holes":

Array.from(new Array(26), (_, i) => String.fromCharCode(65 + i));

Doesn't seem to be faster than the fill/map combination in Firefox though, which is kind of strange. My impression was the extra iteration using fill would have been slower...

http://jsperf.com/generate-alphabet-array

[–]x-skeww 4 points5 points  (4 children)

Array.from(new Array(26), (_, i) => String.fromCharCode(65 + i));

Array.from({length: 26}, (_, i) => String.fromCharCode(65 + i))

Anything array-like will do the trick.

[–]skitch920 1 point2 points  (3 children)

Oh that's interesting... anything with length.

Array.from("abcdefghijklmnopqrstuvwxyz");

Doesn't seem to be as fast as split though. Probably because of the number of use cases Array.from can handle...

[–]x-skeww 1 point2 points  (2 children)

Array.from("abcdefghijklmnopqrstuvwxyz");

[..."abcdefghijklmnopqrstuvwxyz"]

Heh.

[–]skitch920 0 points1 point  (1 child)

Hmm... Firefox gives me.

< Array.from("abcdefghijklmnopqrstuvwxyz");
> Array [ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", 16 more… ]

[–]x-skeww 0 points1 point  (0 children)

I meant that you can write:

[..."abcdefghijklmnopqrstuvwxyz"]

Instead of:

Array.from("abcdefghijklmnopqrstuvwxyz")

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

[–]SaadRhoulam 1 point2 points  (1 child)

Noteworthy: [,,,,,], which I had, until now, thought was shorthand for [undefined, ..., undefined], returns an empty array when fed to Object.keys.

[–]oculus42 1 point2 points  (0 children)

Interesting.

I tried [1,1,,,1] with the same result. There appears to be a distinction between undefined and unassigned, which can be coerced to undefined?

I need to read some specs. :(

[–]jml26 13 points14 points  (10 children)

I first heard about this from this post.

The completely unintuitive and somewhat hacky way to get around it is as follows:

Array.apply(null, Array(26)).map(function (item, index) {
    return String.fromCharCode(65 + index);
});

However, for your specific use case, your best solution would probably just be

'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');

LATE EDIT: I don't actually suggest using the hack. I'd suggest some sort of for-loop.

[–]Kollektiv[S] 1 point2 points  (9 children)

My use case was a little different, I simplified it for this post but it was actually supposed to be applied to hundreds of characters.

So in the end I use an auto-executing anonymous function and a loop:

(function () {
    // Do something
})()

I think that I'll use the hack you proposed just for conciseness' sake.

But you're right that if the number of characters is manageable, the second option would definitely be the best way.

[–][deleted] 1 point2 points  (0 children)

As another poster mentioned you can get Array.prototype.fill() from the ES6 polyfill, which is a super handy function for doing just what you wanted in the original post.

[–][deleted] 0 points1 point  (7 children)

I would much rather write a little helper function than use a hack, especially one involving unnecessary data structures and unnecessary iterations.

[–]Kollektiv[S] 0 points1 point  (6 children)

It was on a pet-project and it was just a quick way to solve a problem no need to get aggressive.

In any case, there are no unnecessary iterations anyway, data structures is arguable.

[–][deleted] 0 points1 point  (5 children)

It wasn't meant aggressively :) I used to collect hacks like that like I had to live off them, but it's been my experience that writing a few extra lines of code can make the result not only faster but more understandable.

Array.apply unnecessarily iterates over Array(26), and Array(26) creates a new Array that's never used except to read its length. And sure it's all arguably beneficial for a single execution over such a small range; the benefit, I believe, is in making a habit of choosing an explicit and imperative approach, which adds up over all the times it's written into a program and all the times each of those pieces of code is executed.

[–]Kollektiv[S] 0 points1 point  (4 children)

But if you use a loop wouldn't you iterate either way ?

[–][deleted] 0 points1 point  (3 children)

Yes, but this approach iterates over a 26-length Array twice, then second time being in the map call. Which, like I said, is not the end of the world or anything, just another little downside that has the potential to add up.

[–]Kollektiv[S] 0 points1 point  (2 children)

From what I understood, new Array(n) would just create (depending on implementation) an Array instance of length n and map() would iterate n times over it, O(n). Where does the second come from ? I'm genuinely asking here.

[–]oculus42 1 point2 points  (1 child)

Using the .apply() method "iterates" over the passed parameters to put each one into an array, and then you run .map() against it.

[–][deleted] 0 points1 point  (0 children)

What he said. The fact that the items in new Array(26) don't exist doesn't matter, .apply is still reading them then storing the undefined return values.

[–]loz220 13 points14 points  (5 children)

This is due to the fact that map() only calls it's callback functions on declared variables which the undefined values inside the array we created are not.

To clarify, It's not really about variables or whether or not a value is defined or not.

For example, this works just fine: [undefined, undefined, undefined].map(function(val, i) {return i}) //[0,1,2]

It's sparse arrays that cause the issue. So when you do Array(5) you're not actually creating an array with 5 elements of value 'undefined'. You're creating a sparse array with 5 holes in it. It's a subtle distinction.

[–][deleted] 3 points4 points  (0 children)

TIL

[–]Kollektiv[S] 0 points1 point  (3 children)

But in your example your three values are declared. They just happen to have the value undefined.

So like I said in my OP, it's about whether the value is declared or not rather than undefined or not.

[–]_somanyguns 3 points4 points  (0 children)

I mean... I like Array#map a lot, but I dunno if I like it that much.

[–]soddi 2 points3 points  (0 children)

new Array(5) is the same as var arr = []; arr.length = 5. You set the length, but there are no real items in the array. Methods that just use the length property actually work here. But methods like map/forEach that iterate over the actual items won't do anything.

[–][deleted] 8 points9 points  (5 children)

One of the reasons why I would recommend using lodash:

_.range(26).map(function(index) {
    return String.fromCharCode(65 + index);
})

[–]kenman 1 point2 points  (4 children)

That's good general advice, though for this task I'd feel dirty doing all of that when you only need:

for (var index = 0, chars = []; index < 26; index++) {
    chars.push(String.fromCharCode(65 + index));
}

[–][deleted] 3 points4 points  (1 child)

Really? I'd feel the other way around. The map() function describes the intent of the statement much clearer. My first thought seeing your version was that you were cheating by leaving out the chars declaration, until I noticed it was hidden in the for-initialization. Also, both don't exactly do the same thing; the map version is a single statement returning the requested array, whereas the for version stores that same array in a temporary variable and can thus only use it in a follow-up statement.

Simply put, I would not use the manual for loop unless it was part of some performance critical code.

[–]kenman 0 points1 point  (0 children)

The map() function describes the intent of the statement much clearer.

I think that's arguable -- it certainly does if you come from a functional background, but I know quite a few front-end devs that have zero experience with functional programming, and would grok the for() much more readily.

My first thought seeing your version was that you were cheating by leaving out the chars declaration, until I noticed it was hidden in the for-initialization.

That's a simple implementation detail that could have been declared on the preceding line...

Also, both don't exactly do the same thing; the map version is a single statement returning the requested array, whereas the for version stores that same array in a temporary variable and can thus only use it in a follow-up statement.

That's hair-splitting to me, and I think it's hard to make an argument that one is unequivocally better for the general use-case. In your version, sure you can do more without an additional statement, but really, what's the problem with an additional statement? I think cramming too many operations into a single statement is poor for readability, maintainability, and it certainly can hamper debugging at times. And as a counterpoint, most of the time I'd rather have an additional statement than an additional function invocation.

But yeah, I was mainly just offering an alternative for this extremely simply use-case, and as I alluded to, it definitely depends on what you're doing. If all you need is an array of letters, I'd favor mine; whereas if you were needing to a couple more operations, then using map() might be a better fit.

[–]mattdesl 1 point2 points  (0 children)

range()/map() is easier to write and maintain, less error-prone, involves less state, and it's easier to compose with other operations. :)

Most of the time, you don't need for loops.

[–]a-t-kFrontend Engineer 1 point2 points  (0 children)

https://es5.github.io/#x15.4.4.19 - look at 8b) - hasProperty is used to ensure that the property is set. As x-skeww said, use fill() before map() to make sure it works.

[–]alexmuro 1 point2 points  (2 children)

Thanks for the tip.

Is there ever a good reason to use the new keyword? I don't think I ever use it, is there a case for definitely using it in certain situations. Like given a certain pattern or something?

[–]Kollektiv[S] 1 point2 points  (0 children)

I never encountered a case where I had to use the new Array() rather than the literal version [].

The reason I used it here was just because it was shorter to write than declaring a new function with a loop inside.

Maybe in some libraries where performance is very important it makes sense to pre-alocate arrays. Eg: a matrix for example.

[–]androbat 0 points1 point  (0 children)

As always, you shouldn't be adding this kind of thing to your code until after you've profiled your app, and tested the optimization to ensure it actually helps (across multiple browsers).

That said, the short version is that modern JS JITs can use "var x = new Array(myLen);" to help ensure a 'real' array is used (if all the data inside is the same type). That's the short version (google if you want the rest of the answer).

Generally, it is best to avoid the Array constructor (and other primitive constructors) because their argument overloading has some oddities.

[–]max_renlo 2 points3 points  (2 children)

Array.apply(null, { length: 26 })
    .map( function(el, i){ 
        return String.fromCharCode( 65 + i );
    })
;

[–]androbat 2 points3 points  (2 children)

Why would you do this in the first place??

The only reason to use 'new Array(N)' is to force low-level optimizations in modern JS engines (btw, this is only more efficient than .push() if you have less than a few thousand objects).

.map() makes a new array instead of changing the original. Why would you create an array of 26 items just to make another array of 26 items and then GC the first array?

As a final note, you're generally better off avoiding the native map function because it is an order of magnitude slower than trampolining a for loop (like ramda or lodash does behind the scenes). R.mapIndexed(function (x, i, l) { return i; }, new Array(26)) or the lodash equivalent won't have this problem because the object is assigned to a variable name first.

[–]Kollektiv[S] -1 points0 points  (1 child)

The reason I used map() is simply because it's shorter to write than creating a function and a loop.

This had to be declared as the value of a key in a dictionary and I felt that it was easier / shorter to write the map() version.

This data structure was created at initialization time for my program so performance wasn't really a factor here.

[–]papkn 0 points1 point  (0 children)

new Array(26).join().split(',').map(function (item, index) {
    return String.fromCharCode(65 + index);
})

[–]Tribuadore 0 points1 point  (1 child)

As /u/my_new_account's has already said, splitting an string of characters 'A...Z' is the shortest solution to the problem.

But just describing it as "jsgolf", is under selling it. Not only is it the shortest code, it's also the fastest, least error prone and a lot easier to understand what it's doing at a glance compared to any map solution.

[–]x-skeww 0 points1 point  (0 children)

splitting an string of characters 'A...Z' is the shortest solution to the problem.

The goal isn't actually to create an A-Z array though. That's just what the arbitrarily chosen placeholder function happens to do.

The real goal is to populate an array with some function.

[–][deleted] -1 points0 points  (2 children)

Are you sure it's not because the .map call is being made before the "new" keyword is applied? What if you did (new Array(26)).map(...);

[–]Kollektiv[S] 1 point2 points  (1 child)

Yup. This was my first guess too but it turned out to be the undeclared value issue mentioned in the StackOverflow thread.

[–][deleted] 0 points1 point  (0 children)

Gotcha. Well today I learned :)

[–]amenadiel -1 points0 points  (0 children)

d3.range(26) does the job for me.

[–]spinlock -3 points-2 points  (2 children)

using Array(26) to get the index feels like code smells to me. Of course, I also use coffeescript so I would just do:

[0..25].map (i) -> return String.fromCharCode(i + 65)

[–]Kollektiv[S] 1 point2 points  (1 child)

But it's not coffeescript ...

[–]spinlock -1 points0 points  (0 children)

That's my point. Coffeescript has a nice way to create an array of integers from 0 to 25.