📜 ⬆️ ⬇️

The best gift book for well-read fans of JavaScript

Hello, dear habrozhiteli!

We are carrying out ambitious plans for publishing such a book :


')
As you understand, this book requires not only literary translation, but also a cool printing, good paper, wide format, etc. Therefore, we suggest to get acquainted with the wonderful publication about this book, which appeared in the blog of the author Angus Kroll several months after the publication of the original.

Happy reading, and please participate in the survey!


I wrote a book If Hemingway Wrote JavaScript . In it, I fantasize how 25 famous prose writers, poets and playwrights could solve simple problems in JavaScript. This is a tribute to my favorite writers and a declaration of love for the JavaScript language - after all, I don’t know what other language would give a programmer such freedom, allow him to unleash creativity, and also be so distinctive as to attract the attention of great writers.

In this post - the original material, which is not in the book (consider it a "bonus"). This is the first in-depth technical analysis of the decisions attributed to each author. Some solutions require more detailed explanations than others.
Enjoy reading!

Part 1: Simple Numbers

Task: write a function that returns all prime numbers up to the value of the given argument.

1. Jorge Luis Borges

https://github.com/angus-c/literary.js/tree/master/book/borges/prime.js

//   ( )  ,   , //     ,      var monstersAscendingAStaircase = function(numberOfSteps) { var stairs = []; stepsUntrodden = []; var largestGait = Math.sqrt(numberOfSteps); //     ; //     ,    for (var i = 2; i <= largestGait; i++) { if (!stairs[i]) { for (var j = i * i; j <= numberOfSteps; j += i) { stairs[j] = "stomp"; } } //      ,   –   for (var i = 2; i <= numberOfSteps; i++) { if(!stairs[i]) { stepsUntrodden.push(i); } } //     return stepsUntrodden; }; 


Borges's solution is a variant of the “ Sieve of Eratosthenes ” algorithm, in which multiples of each known prime number are labeled as composite (complex) numbers. In this case, Borges long-legged monsters take the place of divisors. Each monster takes a step one step wider than what followed it: 2, 3, 4, 5 ... up to the square root of the number corresponding to the highest step. (for some reason, Borges allows you to climb stairs and monsters with uneven steps). Those steps, on which no one will rise - and there are simple numbers.



Pay attention to line 12: each monster starts climbing from the square of its multiplier:
 for (var j = i * i; j <= numberOfSteps; j += i) { 


The fact is that the composite numbers between n and n² will already be traversed by monsters with a smaller step.

2. Lewis Carroll

https://github.com/angus-c/literary.js/tree/master/book/carroll/prime.js

 function downTheRabbitHole(growThisBig) { var theFullDeck = Array(growThisBig); var theHatter = Function('return this/4').call(2*2); var theMarchHare = Boolean('The frumious Bandersnatch!'); var theVerdict = 'the white rabbit'.split(/the march hare/).slice(theHatter); //   … eval(theFullDeck.join('if (!theFullDeck[++theHatter]) {\ theMarchHare = 1;\ theVerdict.push(theHatter);\ ' + theFullDeck.join('theFullDeck[++theMarchHare * theHatter]=true;') + '}') ); return theVerdict; } 


Like Carroll, his code is a mixture of riddle and nonsense. Let's look at it line by line, starting with a variable declaration.
In principle, line 2 is quite traditional (if you close your eyes to using the Array constructor). Carroll creates an empty array whose length matches the reported argument. It's called theFullDeck
theFullDeck
, since the solution represents a deck of cards, and as a result, only those that correspond to prime numbers will lie face down.

In line 3, a function is created (using the little-used Function constructor), and then this function is called using call, with 2 * 2 (that is, 4) being passed as the argument this. Therefore, theHatter is initialized with a value of 1.

Line 4 of theMarchHare
theMarchHare
set to true
true
. When the Boolean constructor is called as a function, its argument is converted to true
true
or false
false
. In this case, the non-empty string 'The frumious Bandersnatch!' converted to true
true
. (By the way, such an assignment is not very necessary here, because in line 10 of theMarchHare
theMarchHare
assigned new value).

Finally - probably the top of the absurd - in line 6 Carroll assigns theVerdict
theVerdict
an empty array, and does it as figuratively as possible:

 var theVerdict = 'the white rabbit'.split(/the march hare/).slice(theHatter); 


It is not so much striking. Argument for split
split
Is a regular expression not matching 'the white rabbit', so when calling split
split
we get an array that contains only 'the white rabbit'. Subsequent slice
operation slice
enters the copy of the array all the elements of the original array, starting with the specified index. Since in our singleton array there is no index 1 (this is the value of theHatter
theHatter
), no members from it are copied, and we get an empty array.

Simply put, you can rewrite variable declarations like this:

 function downTheRabbitHole(growThisBig) { var theFullDeck = Array(growThisBig); var theHatter = 1; var theMarchHare = true; var theVerdict = []; 


And now full of frenzy:

 //   … eval(theFullDeck.join('if (!theFullDeck[++theHatter]) {\ theMarchHare = 1;\ theVerdict.push(theHatter);\ ' + theFullDeck.join('theFullDeck[++theMarchHare * theHatter]=true;') + '}') ); 


Before moving on to the notorious eval
function eval
let's talk about nested join
instructions join
. The join function turns an array into a string, while its argument serves as glue between the elements of the array. If you call join
join
applied to an empty array, we get a string consisting of solid glue (repeated n - 1 times, where n is the length of the array):

 Array(4).join('hi'); //'hihihi' 


If you put two join into each other, then the corresponding glue is embedded:

 Array(4).join('A' + Array(4).join('a')); //'AaaaAaaaAaaa' 


When we include variables in the glue, we begin to understand what's what:

 var arr = [], count = 0; Array(4).join('arr.push(' + Array(4).join('count++,') + '-1);'); //"arr.push(count++,count++,count++,-1);arr.push(count++,count++,count++,-1);arr.push(count++,count++,count++,-1)" 

Now, interpreting JavaScript, how to generate JavaScript, it remains only to figure out how to start all this. We go to the vile eval
eval
...

 var arr = [], count = 0; eval(Array(4).join('arr.push(' + Array(4).join('count++,') + '-1);')); arr; //[0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1] …            .       : //   ... eval(theFullDeck.join('if (!theFullDeck[++theHatter]) {\ theMarchHare = 1;\ theVerdict.push(theHatter);\ ' + theFullDeck.join('theFullDeck[++theMarchHare * theHatter]=true;') + '}') );   eval ( )  : if (!theFullDeck[++theHatter]) { theMarchHare = 1; theVerdict.push(theHatter); theFullDeck[++theMarchHare * theHatter] = true; theFullDeck[++theMarchHare * theHatter] = true; theFullDeck[++theMarchHare * theHatter] = true; } if (!theFullDeck[++theHatter]) { theMarchHare = 1; theVerdict.push(theHatter); theFullDeck[++theMarchHare * theHatter] = true; theFullDeck[++theMarchHare * theHatter] = true; theFullDeck[++theMarchHare * theHatter] = true; } if (!theFullDeck[++theHatter]) { theMarchHare = 1; theVerdict.push(theHatter); theFullDeck[++theMarchHare * theHatter] = true; theFullDeck[++theMarchHare * theHatter] = true; theFullDeck[++theMarchHare * theHatter] = true; } // etc... 


…etc. (The code generated in this way can be very long. By requesting all the prime numbers up to 100, we get more than 10,000 lines of code - you can imagine how this will affect the performance - but for Wonderland, I think it will come down)

So, gradually everything becomes clear. It turns out that Carroll used a variant of the sieve of Eratosthenes, which we saw in Borges. theFullDeck
- this is an array with all the numbers that need to be checked, theHatter
theHatter
and theMarchHare
theMarchHare
- nested counters, multiplied with each increment, to generate all possible composite numbers. All cards whose indices are compound numbers are reversed (because theFullDeck
theFullDeck
with this index is true
true
). Only those cards that correspond to prime numbers remain open.



3. Douglas Adams

https://github.com/angus-c/literary.js/tree/master/book/adams/prime.js

 //  ,    ,       JavaScript... function kevinTheNumberMentioner(_){ l=[] /*   --> */ with(l) { //    ,      ... for (ll=!+[]+!![];ll<_+(+!![]);ll++) { lll=+!![]; while(ll%++lll); //              (ll==lll)&&push(ll); } forEach(alert); } //        ... return [!+[]+!+[]+!+[]+!+[]]+[!+[]+!+[]]; } 


It’s generally difficult to read the Adams code, since it borrows a lot from jsfuck , an ingenious, but deadly laconic language that uses only 6 characters. However, this is a real javascript - if you run it in the console, everything will work. Let's translate a simple snippet:

 for (ll=!+[]+!![];ll<_+(+!![]);ll++) { 


Here we see the for
loop for
and ll and _ are the variable names. All the rest is literal and shaped brain removal.
In the first condition of this instruction, ll gets the value !+[]+!![]
!+[]+!![]
. Having examined this expression, we see in it two empty array literals. Before the first one stands +, because of which the array is forcibly reduced to the number 0. Right in front of it, !, forcibly leading 0 to its Boolean opposite, that is, to true
true
. So !+[]
!+[]
results in true
true
.

Now consider the second array literal. He is preceded by two !!
!!
that will simply force him to the boolean. Since arrays are always objects, the boolean of an array is always true
true
. So, !![]
!![]
always gives true
true
.

Putting these two expressions !+[]+!![]
!+[]+!![]
, in fact, we have true + true
true + true
. Here, + forces both operands to the number 1, so the final result of the whole expression is 2.
The two remaining conditions of the for
loop for
now understandable without difficulty. Again, we have !![]
!![]
, this time before it goes +, forcibly leading true
true
to 1. So, ll<_+(+!![])
ll<_+(+!![])
gives ll < _ + 1
ll < _ + 1
.
The last condition is the usual JavaScript postfix, so the whole for
loop for
gives:

for (ll = 2; ll <_ + 1; ll ++) {

And here is the entire solution, translated into plain worldly JavaScript (I also gave variables to more meaningful names)

 //  ,    ,       JavaScript… function kevinTheNumberMentioner(max){ var result = []; /*   --> */ with(result) { //    ,      ... for (candidate = 2; candidate < max + 1; candidate++) { var factor = 1; while (candidate % ++factor); //              (candidate == factor) && push(candidate); } forEach(alert); } //        ... return '42'; } 


Nice, now we have at least recognizable JavaScript, but there are still some oddities in it.
Instructions with
with
It refers to the most reprehensible from the point of view of "observers of the purity of JavaScript", but still in line 3 - it is she. Javascript will try to enclose all the properties not referenced in the with
block with
given object. Consequently, the restless methods of the push
array push
and forEach
forEach
will be in the scope of the result
result
.

Another interesting instruction is the while
while
in the ninth row. This cycle has no body, therefore factor
factor
just continues to increase by one until it is fully divided, giving the candidate
candidate
as private. The next line checks if the values ​​of the candidate
are now equal. candidate
and factor
factor
. In this case, the number has no smaller dividers, therefore, it is simple and is added to the result
result
.
In line 13 we search through the results and publicly declare each prime number as an alert
alert
. Finally, the program returns 42.



4. Charles Dickens

https://github.com/angus-c/literary.js/tree/master/book/dickens/prime.js

 function MrsPrimmerwicksProgeny(MaxwellNumberby) { Number.prototype.isAPrimmerwick = function() { for (var AddableChopper = 2; AddableChopper <= this; AddableChopper++) { var BittyRemnant = this % AddableChopper; if (BittyRemnant == 0 && this != AddableChopper) { return console.log( 'It is a composite. The dear, gentle, patient, noble', +this, 'is a composite'), false; } } return console.log( 'Oh', +this, +this, +this, 'what a happy day this is for you and me!'), true; } var VenerableHeap = []; for (var AveryNumberby = 2; AveryNumberby <= MaxwellNumberby; AveryNumberby++) { if (AveryNumberby.isAPrimmerwick()) { VenerableHeap.push(AveryNumberby); } } return VenerableHeap; } 


What if you could ask the number if it is simple:

 6..isPrime(); //  7..isPrime(); //  


What would Charles Dickens do? Would Number.prototype
Number.prototype
. Its own extension is called isAPrimmerwick
isAPrimmerwick
(in fact, all objects here have fancy Dickens names) and is defined in lines 2-14. In lines 17-21, we simply ask each number whether it is simple and add simple numbers to the results array called VenerableHeap
VenerableHeap
.
isAPrimmerwick
method isAPrimmerwick
isAPrimmerwick
basically simple. We divide the existing number into all possible dividers. If this or that number is divided without remainder, then it is composite, otherwise - simple.

In each return instruction (lines 6 and 11) there are a couple of curious moments. First, since the number calls the method in its prototype, you can refer to it with this
this
(but prefixed with + to force it from a numeric object to a primitive). Secondly, Dickens uses a comma operator to simultaneously call console.log
console.log
and return a boolean value.



5. David Foster Wallace

https://github.com/angus-c/literary.js/tree/master/book/wallace/prime.js

 var yearOfTheLighteningQuickAtkinSieve = function(tops) { //BP #40 07-14 //ELEPHANT BUTTE, NM var NSRS/*[1]*/ = [0,0,2,3]; /*     ,   i  j (    1)   1    (,   ). */ for(var i = 1; i < Math.sqrt(tops); i++){ for(var j = 1; j < Math.sqrt(tops); j++){ if (i*i + j*j >= tops) { break; } /*   (.e. i  j)      quadratic,       (n). */ var n = 4*i*i + j*j; /*    (.e. n)   12,    1  5,       (.e.  n)   [2]. */ if(n <= tops && (n%12 == 1 || n%12 == 5)){ NSRS[n] = NSRS[n] ? 0 : n; } /*   (.e. JavaScript)     , and again the result     ()  n. */ n = 3*i*i + j*j; /*   (.e. n)    12,       7   ,       (.e.  n)   */ if(n <= tops && (n % 12 == 7)){ NSRS[n] = NSRS[n] ? 0 : n; } /*   (.e. ),     ,   ,  (.e. JavaScript)   .    ,        (  )    (  )  n. */ n = 3*i*i - j*j; /*        (   )   ,     ,      (i)   .e.   ( )    (j) [3]. */ if (i>j) { if((n <= tops) && (n % 12 == 11)){ NSRS[n] = NSRS[n] ? 0 : n; } } } } /*    (     )  (.e. JavaScript)          ,     :    (.  )   (..  ) */ for(i = 5; i < Math.sqrt(tops); i++){ if(NSRS[i] == 1){ for(j = i*i; j < tops; j += i*i){ NSRS[j] = 0; } } } return NSRS.filter(Number); // [4] } /* [1]      . [2]   ,    [a]   0,   0    . [3]       [a]    . [4] `Array.prototype.filter`    ,    EcmaScript-262 (5- ) [b].  `Number` -     ,     .  Array.prototype.filter   (.e. ) ,  0,   (.e. )     ,  `Array.prototype.filter`. [a] .e. ,        true. [b] http://es5.imtqy.com/#x15.4.4.20 */ 


Thanks to the lengthy comments that Wallace is so famous for, there is nothing particularly to tell - one only needs to note that his decision is based on a highly optimized (and too complicated to go into the explanation here) Etkin sieve.

The code is especially interesting with exquisite logic and Wallace's precise, but relaxed style. However, in line 54 there is an interesting javascript traffic:

 return NSRS.filter(Number); // [4] 


Result - NSRS
NSRS
. Here it is a sparse array containing all prime numbers, which, however, are interspersed with undefined values ​​(in front it is filled with zeros):

 [0, 0, 2, 3, undefined, 5, undefined, 7/*, etc.. */] 


Array.prototype.filter
function Array.prototype.filter
creates a new array that contains only those elements of the original array for which the given function returns true
true
. This is about Number
Number
built-in language function that tries to force your argument to a number. Number
forcibly leads undefined
undefined
to NaN
NaN
, leaving all real numbers intact. Since both mean: NaN
NaN
and 0 means "false", the new array will contain only prime numbers:

 [0, 0, 2, 3, undefined, 5, undefined, 7].filter(Number); //[2, 3, 5, 7] 


Conclusion

That's all. Hope you enjoyed it.

Source: https://habr.com/ru/post/275245/


All Articles