Another contrived task of abnormal programming in JavaScript . This time on the occasion of the coming New Year 2019. I hope it will be as interesting to decide how I was interested in inventing. Curious please under the cat. All champagne and all with the coming!
Previous tasks:
Over the past year, Santa Claus has compiled a decent list of the names of normal developers and now plans to write a program for congratulations. The format is:happy new year, ${username}!
. But here's the ill luck: the keyboard fails and does not allow you to enter many Latin characters. Investigating the defect, the elves made an interesting observation that from what else works, you can foldSnowing day
. The source of the output can be chosen at your discretion.
So, at the entrance - some array of non-empty strings (the name cannot be empty). It is required to write a program using only Latin characters: S
, n
, o
, w
, i
, g
, d
, a
, y
(total 9 characters, one of which is in upper case). The program should bypass the transferred array and output the phrase happy new year, ${username}!
for each name happy new year, ${username}!
using any output source: alert , console.log or whatever comes to mind. Well, it would be good not to pollute the global context.
If you do not invent anything, then everything is very simple:
function happy(users) { for (let i = 0; i !== users.length; i += 1) { console.log(`happy new year, ${users[i]}!`); } }
or better this way:
function happy(users) { users.forEach(user => console.log(`happy new year, ${user}!`)); }
Use with our array, let it be users :
let users = ['John', 'Jack', 'James']; happy(users); // happy new year, John! // happy new year, Jack! // happy new year, James!
But here it is decided: to use only allowed characters in the implementation of the Latin alphabet. Try to do it yourself first, and then join the reasoning.
Impatient can see the solution below in JSFiddle right now.
To solve the problem, you need to get rid of the extra Latin in the following:
The arrow functions will easily help us with the first problem:
(arr => el.forEach(/* ... */))(users);
We will not now pay attention to the names of variables, since we will easily rename them at the very end.
Use the arrows with IIFE wherever you need a function or immediately its result. In addition, functions make it possible to get rid of the let and var directives in two ways:
(param => /* ... */)(value); ((param = value) => /* ... */)();
In both cases, we declare a variable in the parameters of the function. Only in the first case we pass the value when the function is called, and in the second we use the default function parameter.
Indeed, problems begin at the third point. We don’t have enough characters for the classic for , do , while cycles, nor for traversal options using for..in and for..of , or for the array methods forEach , map , filter (where you can transfer callbacks). But we can implement our array iteration function:
function iterate(arr, consume) { function iter(i) { if (arr[i]) { consume(arr[i]); iter(++i); } } iter(0); }
We recursively bypass the elements until the check of the current in the condition falls off. Why can we rely here on a logical transformation? because the element of our array is not an empty string (only it turns false ), and when we exit the array through the increment of the index, we get undefined (it is false ).
Rewrite the function with the help of "arrow" expressions:
let iterate = (arr, consume) => ( (iter = i => { if (arr[i]) { consume(arr[i]); iter(++i); } }) => iter(0) )();
But we cannot use the if statement , since we do not have the f
character. In order for our function to satisfy the condition, we must get rid of it:
let iterate = (arr, consume) => ( (iter = i => arr[i] ? (consume(arr[i]), iter(++i)) : 0) => iter(0) )();
The ternary operator and the ability to combine two expressions into one through a comma operator helped us with this. We will use this function further when building the solution.
The fourth problem is connected with the fact that in any case we need to get a string with missing characters. Obviously, we will use numbers to represent characters. There are several options here:
\uhhhh
allows \uhhhh
to output any Unicode character using the specified hexadecimal code.&#dddd;
for html-symbols, allows to display the symbol for the specified decimal code in the page document.You can independently dig in the direction of the first three options, but for now let's consider the easiest one for this task: Number.prototype.toString . The maximum value of the radix parameter is 36 (10 digits + 26 lowercase Latin characters):
let symb = sn => (sn + 9).toString(36);
Thus, we can get any Latin character by number in the alphabet, starting with 1. The only restriction is that all characters are lowercase. Yes, this is enough for us to display the text in the message, but we will not be able to add some methods and functions (the same forEach ).
But it's too early to rejoice, you first need to get rid of toString in the function's record. The first is to refer to the method as follows:
let symb = sn => (sn + 9)['toString'](36);
If you look closely, then for the string toString
we lack only two characters: t
and r
: everything else is in the word Snowing
. Getting them is quite simple, since their order is already hinting at true
. Using implicit type conversions, we can get this string and the characters we need as follows:
!0+''; // 'true' (!0+'')[0]; // 't' (!0+'')[1]; // 'r'
We achieve the function of receiving any Latin letter:
let symb = sn => (sn + 9)[(!0+'')[0] + 'oS' + (!0+'')[0] + (!0+'')[1] + 'ing'](36);
To get the words by an array of letter sequence numbers using symb , we use the standard function Array.prototype.reduce :
[1,2,3].reduce((res, sn) => res += symb(sn), ''); // 'abc'
Yes, it does not suit us. But in our solution we can do something similar using the iterate function:
let word = chars => (res => (iterate(chars, ch => res += symb(ch)), res))(''); word([1,2,3]); // 'abc'
Attentive ones will note that we have developed the iterate function for an array of strings, and use them here with numbers. That is why the initial index of our alphabet is 1, not 0. Otherwise, an impromptu cycle would end if it met 0 (the letter a
).
For ease of mapping characters to their sequence numbers, you can get a dictionary:
[...Array(26).keys()].reduce((map, i) => (map[symb(i + 1)] = i + 1, map), {}); // {a: 1, b: 2, c: 3, d: 4, e: 5, …}
But it is wiser to do even simpler and write the entire inverse word conversion function:
let reword = str => str.split('').map(s => parseInt(s, 36) - 9); reword('happy'); // [8,1,16,16,25] reword('new'); // [14,5,23] reword('year'); // [25,5,1,18]
We conclude with the function of forming the message itself:
let message = name => word([8,1,16,16,25]) + ' ' + word([14,5,23]) + ' ' + word([25,5,1,18]) + ', ' + name + '!';
It remains quite a bit - to deal with the conclusion in the fifth problem. Console , alert , confirm , prompt , innerHTML , document.write come to mind. But none of these options can not get close directly.
We also have the opportunity to receive any word using the word function. This means that we can call many functions of objects by referring to them through square brackets, as was the case with toString .
Considering that we use the arrow functions, this context remains global (and it is not necessary to forward it). At any place we can access many of its functions through a line:
this[word([1,12,5,18,20])]('hello'); // alert('hello'); this[word([3,15,14,19,15,12,5])][word([12,15,7])]('hello'); // console.log('hello');
But for the "outline" of this
we again lack the characters. We can replace it with Window.self , but with it it’s even worse in terms of the available alphabet. However, it is worth paying attention to the window object itself, the “outline” of which we are completely satisfied with, although it would have seemed a bit longer!
By the way, in the first version of the task the key phrase was only the word Snowing
, and the window
was not folded (due to the absence of the d
symbol). Access to the context was based on one of the jsfuck tricks:
(_ => 0)['constructor']('return this')()['alert']('hello');
Or you can also access anything in the global context directly:
(_ => 0)['constructor']('return alert')()('hello');
As you can see, in the examples, all Latin characters are in rows. Here we create a function from a string, and access to the Function (constructor) is obtained from the wasted created arrow function. But this is already a bust! Maybe someone knows more ways to access the context in our conditions?
Finally, we put it all together! The body of our "main" function will call iterate for the passed array, and the result will be the output of the result of the already built-in message generation function. For the message text and commands, one word function is used, which also needs iterate , and we will define it by default in the default parameters . Like this:
(users => ( (( // firstly we need the iterating function iterate = (array, consume) => ((iter = i => array[i] ? (consume(array[i]), iter(++i)) : 0) => iter(0))(), // then we determine the word-creating function word = chars => (res => (iterate(chars, ch => res += (ch + 9)[(!0+'')[0] + 'oS' + (!0+'')[0] + (!0+'')[1] + 'ing'](36) ), res) )('') ) => iterate(users, name => // using console.log in window for printing out window[word([3,15,14,19,15,12,5])][word([12,15,7])]( word([8,1,16,16,25]) + ' ' + word([14,5,23]) + ' ' + word([25,5,1,18]) + ', ' + name + '!' ) ))() ))(users);
Rename variables using the allowed alphabet:
(_10 => ( (( _123 = (ann, snow) => ((_12 = i => ann[i] ? (snow(ann[i]), _12(++i)) : 0) => _12(0))(), wo = ann => (w => (_123(ann, an => w += (an + 9)[(!0+'')[0] + 'oS' + (!0+'')[0] + (!0+'')[1] + 'ing'](36) ), w) )('') ) => _123(_10, _1 => window[wo([3,15,14,19,15,12,5])][wo([12,15,7])]( wo([8,1,16,16,25]) + ' ' + wo([14,5,23]) + ' ' + wo([25,5,1,18]) + ', ' + _1 + '!' ) ))() ))(users);
Variable | Description |
---|---|
_123 {function} | The iterate function to iterate over the elements of an array. |
_12 {function} | The iter local function that iterate recursively calls. |
snow {function} | Consume function as callback for iterate . |
ann {Array<Any>} | Array parameter |
an {Any} | Parameter element of the array. |
wo {function} | Word function to form words. |
w {string} | Local variable to accumulate the string in the word . |
_10 {Array<string>} | The original user array. |
_1 {string} | User from the source array, his name. |
That's all. Write your ideas and thoughts about this, as there are many options to do something differently or not at all.
Interestingly, to come up with a word or phrase for the condition of the problem turned out to be a real test. I wanted her to be short, and not strongly prompted, and suitable for a more or less concise solution.
The inspiration for this task was the functionality of JavaScript and the 6-character isotheric known to many. Like the previously reviewed tasks, this one may have several variations on the topic, and not the only solution. It is enough to come up with a simple wording and a key phrase. See you in the new year!
Source: https://habr.com/ru/post/433112/
All Articles