What I Learned from Pikabu JavaScript Challenge

Nikki Jack
9 min readOct 22, 2019

--

Pikabu.ru is the Russian equivalent of Reddit. The other day their targeted ad post invited me to take a “JobSeeker” JavaScript coding challenge to join their team as a Front-end developer.

pikabu_challenge

I hacked my way to the final “Congratulations, you made it!” stage with one test not consistently passing (more on that at the end). Spending hours on the challenge was completely worth it, because I got deeper knowledge on certain subjects and learned new things:

How it works

The challenge consists of a JobSeeker class with code in it, but some pieces are missing. More like a puzzle :) We’re allowed to fill in the blanks with ONLY ONE VALUE which is a variable, class or function name, string literal, integer, unary or binary operator. NO function calls e.g. foo(), NO dot notation e.g. arr.length. NO ternary statements.

In the times of Jest and Mocha testing frameworks it was refreshing to learn another simple way to test your functions. While console.log() is essential for debugging, it appears that console.assert() can also be used to test code.

The challenge uses a sequence of console.assert() statements to test the code. Asserts are independent from each other. If the first assert is not passing, the second assert will not be evaluated. We are going to walk through the tests one at a time.

Assert 1

The first call of buildArray() initiates a stack and returns a function. On all following calls in the test out() function is called. The reference to stack lives through all out() calls, closure at its best.

We notice two types of input, a number and a function. value1 on line 7 has to be number, so that all number args are saved in the stack. When the arg is a function, it is applied to stack. Thus someName on line 12 is apply.

Assert 2

Let’s determine the value first. On line 15, value — 1 === value / 15. Which entity of type Number can do that? If we assume NaN, then both sides of the equation will become NaN. But as we know NaN !== NaN. The value that works is Infinity.

Look at variable f on line 12. In the ASCII table character number 0b100 (4 in decimal) is EOT which stands for End Of Transmission. This character is not printable. So what do we put into someName then? The character’s Unicode representation '\u0004'.

Assert 3

Let us translate what the test wants from us.

  • regex needs Cyrillic characters. yourName in format first name and the first letter of the last name like Никки Д. yourCity expects one word for city preceded by г. , which is short for “city” in Russian → г. НьюЙорк
  • An empty <span> followed by <my-name> element.
  • Two elements with my-city class, we will test the second one (with index 1). dataset is a way to add custom attributes to an element. dataset.cityName means that this element should contain an attribute data-city-name.
  • :out-of-range pseudo class requires an <input> element with min and max attributes. If we make max = "18" then my age will indeed be out of range. [data-my-age] means that the input element needs to have this attribute. We also need to hardcode the age value into this element.
  • On line 14 the placeholders are replaced with the props from “data” object. Regex tells us that we should prepend the placeholders with ::

With information above, the value blank on line 13 should be filled with the following:

<span class="my-city"></span>
<my-name>::name</my-name>
<span class="my-city" data-city-name="::city"></span>
<input data-my-age type="number" min="17" max="18" value="::age" />

Assert 4

If strings are equal, then we have one unique string. Set object is often used to discern unique things. someName1 blank will be filled with Set. And indeed, the size of a Set built from array of two strings should be 1 if strings are duplicates, i.e. equal.

someName2 has to be map, because we are applying some function to each string in the array. Note that arr.map() is the same as arr['map']().

NFKC stands Normalization Form Compatibility Composition. It’s one the forms of Unicode Normalization, or putting strings in a certain Unicode format. someName3 becomes normalize. And the test passes: regular equality check confirms string are not equal, but the “normalized” comparison says they are.

Assert 5

Did you know that since May 2019 1_000_000_000 is a valid number literal in JS V8 engine? (Thus the warning on line 6 about Chrome, which runs on V8). Underscore is a numeric separator, just like comma or dot. Here’s a twist. They cannot be parsed from a string with neither Number() nor parseInt(). According to MDN, eval() invokes the JS interpreter. Sounds like just what we need to parse nums.join('_') to a number. This way someName1 becomes eval.

Since the formatNumbers function returns a string, someName2 is toString. By trial and error we can get that the radix of toString is 8. How to get 8 from two and two? With bitwise operator “shift left”. operator becomes <<.

Assert 6

Whoa! new new new new new ? We can sure make it work. The first reaction to the line 21 is that we need a constructor that processes an argument. Let’s make function name someName2 on line 11 into constructor, and someName3 will become arguments.

When there is an argument passed to constructor on line 12, it needs to return a constructor, because new keyword is encountered again and again in the test. JobSeeker as a function is a constructor. And that is what we put instead of value2 on line 12.

The cases when JobSeeker object is the instance of JobSeeker are 1) when no arguments is passed to the constructor, 2) when the argument is falsy. The constructor itself (when we return it) is not the instance of JobSeeker.

Variable i in the first test is the instance of JobSeeker. But on line 21 it is used with a decrement i--, looks like it expects a number, which is a primitive and not an object. Conveniently every object has a way of returning a primitive when it’s referred, with a function valueOf(), which we need to override for JobSeeker. This way someName1 on line 6 becomes valueOf.

What will our custom valueOf return? On line 21 we need the last call of new Jobseeker(i--) to return the instance of JobSeeker. For that we need the value of i on the last call to be falsy, or in numeric terms, a zero. This happens when we put 4 into value1 on line 7. Our custom valueOf ends up being called only once, when we refer to it for the first time. Then it’s treated as a number and keeps being decremented.

Assert 8 — my personal favorite

The most important variable here is rgb. It’s a Uint8ClampedArray of pixels that we get from the canvas context. Each pixel occupies four array slots with values of RGBA each in its own slot. When we iterate through pixels we have to increase the counter by 4, like in line 29.

We have a path for the image in the test, let’s download it from the server and have a look.

Q3091skic8.png

Thanks for the hint, Pikabu. Line 30 confirms that keys array has three elements. We shall put 342 34 11 into value2 on line 9. After applying these filters for each pixel’s RGB values (lines 29–34) we get the following drawing on canvas:

Q3091skic8.png after filters

Here is the maze/labyrinth! This image contains hints about where the start and where the exit is, and that the side of each step is 12 pixels. When we look at lines 40 and 65, we see that one of the options for step is size. We make value1 on line 8 into size 12.

In order to get to the yellow square, the other squares can help us with directions. Red — go down, fuchsia — go right, green — go up, blue — go left, white — continue going in the same direction. Black squares are the walls and must be avoided.

Our rgb array fills the canvas image with pixels from top left to bottom right. The directions array on line 40 is interpreted as[up, right, down, left]. Let us note that “up” has index 0, “right” has index 1, “down”— index 2, and “left” — index 3.

There is also a keyassociated with each color (line 43–45). We have RGB values for colors from the picture. Let’s see how a key is calculated:

It appears that ~~ is another way to say Math.floor. Neat. After calculating the key for each color let’s summarize what we have:

╔═════════╦═══════════╦═══════╦══════════╦═════════════════════════╗
║ color ║ key ║ index ║ meaning ║ in code (lines 42-68) ║
╠═════════╬═══════════╬═══════╬══════════╬═════════════════════════╣
║ black ║0 or 0b0 ║ - ║ wall ║ returns false ║
║ white ║7 or 0b111 ║ - ║ continue ║ keep looping do..while ║
║ yellow ║6 or 0b110 ║ - ║ finish ║ returns true ║
║ green ║2 or 0b010 ║ 0 ║ go up ║ changes step/direction ║
║ fuchsia ║5 or 0b101 ║ 1 ║ go right ║ changes step/direction ║
║ red ║4 or 0b100 ║ 2 ║ go down ║ changes step/direction ║
║ blue ║3 or 0b011 ║ 3 ║ go left ║ changes step/direction ║
╚═════════╩═══════════╩═══════╩══════════╩═════════════════════════╝

Inside the do…while loop the cases for black and yellow are covered. What we need to add to value3 on line 55 is 0b111, which is the key for white. Because we only have to change the direction(step) when the pixel is NOT white.

And finally, let’s figure out what the map variable does. It is used on line 57, where parts of it are compared to the pixel color key. 7 in binary is 111 . In 7 & something it acts like a mask where it copies the last three bits of something. map is shifted right by dir. dir can be 9, 6, 3 or 0. Looks like we need to combine the keys for direction-changing colors in a certain order, so that when while loop on line 57 stops, index i would end up representing the direction we need. Now we know thatmap is mapping the color keys to correct indexes in directions array. From the table above we see that the order of colors mapped onto directions array is [green, fuchsia, red, blue] . So the binary number that we put into map blank on line 7 is a sequence of binary color keys in the correct order : 10101100011.

  10      101      100    011
green fuchsia red blue

Assert 7 — oopsie

The only HTML element that has a content property is HTMLTemplateElement. It means that we need to create a <template> element, and on line 7 instead of name we will put template.

el.content returns a DocumentFragment, which is a Document Object that has no parent. Thus, we can query it with querySelector() and querySelectorAll() .

On lines 11–13 we are swapping two <p>elements. To pass the test we need to swap 3 and 4. We need to select the <p>element that contains 4. We notice that the <div> with 4 is the only one that has three children, and the <p>we need is preceded by other two <p>elements. So on line 11value1 selector string will be p + p + p .

On line 18 we are selecting certain type of <p> elements and for each remove their parent. That’s exactly what we need to do with all the <div>s that contain X and Y. We observe that those <div>s have exactly two two children. To select a <p> that has exactly one sibling, this <p> is at the same the first child and the second child from the end. So the value3 selector string will combine two pseudo classes: p:first-child:nth-last-child(2).

Now that’s where the trouble comes. On lines 15–16 we need to select an element that contains 5 and put value 6 into it to pass the test. What can we observe about the <p> that contains 5? It’s the only child. But the problem is that the <p> with 1 is also the only child. Since querySelector() is used, it will choose the first “only child” that it sees. And that would be 1, not 5. If there is a way to select the second occurrence of :only-child with querySelector(), please share.

Here’s a hacky solution to pass this test. Since the mix variable is created randomly, then there’s a chance it would be repeated zero times i.e. could be empty. If mix is empty, then we just select the last <p> which is 5! Let’s make value2 selector div:last-of-type p and keep running the test until Math.random returns zero. ¯\_(ツ)_/¯

Thanks Pikabu. It’s been real. 🤓

--

--