Entertaining javascript: snowy day

    Image


    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:



    Wording


    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 as follows: happy new year, ${username}!. But here's the bad luck: the keyboard fails and does not allow you to enter many Latin characters. Investigating the defect, the elves made an interesting observation that one can add from what is still working Snowing day. The source of the output can be selected at its discretion.

    So, at the entrance - some array of non-empty strings (the name cannot be empty). Need to write a program using only the characters of the Latin alphabet: S, n, o, w, i, g, d, a, y(total 9 characters, one uppercase). The program should bypass the transmitted array and output a phrase 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.


    Habitual decision


    If you do not invent anything, then everything is very simple:


    functionhappy(users) {
        for (let i = 0; i !== users.length; i += 1) {
            console.log(`happy new year, ${users[i]}!`);
        }
    }

    or better like this:


    functionhappy(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.


    Entertaining decision


    The 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:


    1. The function keyword in the function declaration.
    2. The let (or var ) keyword to declare variables.
    3. Keywords when looping for iteration over the transferred array.
    4. Formation of the message text.
    5. Call some function to display the result.

    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.


    We use the arrows with IIFE wherever we 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:


    functioniterate(arr, consume) {
      functioniter(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 returns to 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 a symbol f. 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:


    • The String.fromCharCode function , which expects whole numbers to be input and returns a string created from the specified Unicode sequence.
    • The escape sequence \uhhhhallows you to output any Unicode character using the specified hexadecimal code.
    • The format &#dddd;for html-symbols allows to display the symbol for the specified decimal code in the page document.
    • The toString function of the Number prototype object has an additional radix parameter , the base of the number system.
    • Maybe there is something else ...

    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 line toStringwe lack only two characters: tand 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 words through 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 improvised cycle would end with a 0 (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 from objects by referring to them through square brackets, as was the case with toString .


    Given 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 "mark" thiswe are again lacking characters. We can replace it with Window.self , but with it it is 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 a word Snowing, and windowit was not folded (due to the lack of a symbol d). Access to 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, the entire Latin alphabet is 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, 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 outwindow[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 the 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);

    VariableDescription
    _123{function}The iterate function to iterate over the elements of an array.
    _12{function}The local iter 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 .
    _ten{Array<string>}The original user array.
    _one{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.


    Conclusion


    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 much 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 considered 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!


    Also popular now: