Help me understand this one-liner for rot13

Help me understand this one-liner for rot13
0

#1

Hi there.

A few days ago I was solving the rot13 exercise, and found on stackoverflow this clever one-line solution:

function r(a,b){return++b?String.fromCharCode((a<"["?91:123)>(a=a.charCodeAt()+13)?a:a-26):a.replace(/[a-zA-Z]/g,r)}

The ternary operator means that whenever ++b is true, the left side of : is executed, which is the part of the code that actually does the work.

In the end of the one-liner, after the regular expression, the function r is passed to the replace function without any arguments, and somehow the whole thing works.

**What is going on with parameter b? How does ++b evaluate to true so that the working part of the code gets executed? **


#2

This is very clever. First, let’s expand it out so we can read it more easily:

function r(a,b){
    return ++b ? String.fromCharCode((a < "[" ? 91 : 123) > (a = a.charCodeAt()+13) ? a : a-26) 
            : a.replace(/[a-zA-Z]/g,r);
}

The intention is that you should be able to give the function a string, and it will iterate through each letter to perform the transformation. Iteration is normally done with a for loop (or other higher-order construct), but here the author leverages the power of regular expressions. string.prototype.replace takes two parameters - the first is either a regular expression or a substring, and the second is either a replacement string or a function that will process each substring produced from the first parameter (regex can be organized into capture groups, and pop out one variable per match within a given string). If we want to give it a function for the second parameter, that function has its own signature.

function replacer(match, p1, p2, p3, offset, string)

The ‘p’ parameters act as counters for the number of types of results, but more on this later. All we need to worry about are the match and p1 parameters. So, our main function takes just one parameter, and the replacer function takes two. By using conditional logic, we can build one function that acts as both

Let’s run the function with a string input:

r("dog");

The parameters are a === "dog" and b === undefined. Since incrementing undefined produces falsey output, the first ternary goes to the right of the : and we see our function called as a replacer, the output of which will be returned.

a.replace(/[a-zA-Z]/g,r)

At this point, we’re calling "dog".replace(/[a-zA-Z]/g, r). The regex will produce three captures - “d”, “o”, and “g”. r will be run on each of them because we passed it as the second parameter to replace(), and whatever output we get from it will be our replacement value. Recall our function signature for the replacer function, now with the extraneous parameters removed:

function replacer(match, p1)

r is our replacer, so each time, a is our match and b is our p1. You can think of p1 as the i in a for loop - it’s our current index within an array of matches. Back to our function call:

"dog".replace(/[a-zA-Z]/g, r)

The first time, a === "d" and b === 0. Now, within the function r, ++b returns a truthy value (the number 1), so we run the first clause in the ternary.

String.fromCharCode((a < "[" ? 91 : 123) > (a = a.charCodeAt()+13) ? a : a-26) 

This just tests whether a is lower- or upper-case, then either shifts the character up by 13, or returns upper- or lower-case “a”, as needed.

So, to answer your question most directly, it tests ++b in the beginning to see if anything has been passed at all, and if it hasn’t, it calls itself recursively.


#3

Great breakdown @PortableStick.

I did read the answer earlier but was giving myself some time to have the recursion part sink in.

If I understand correctly, it is the String.prototype.replace() function that gives the replacer function its arguments.


#4

Yes, absolutely. replace() will pass any function in that position the same arguments, but it’s up to that function to define them.