Divertido JavaScript: Dia da Neve

Imagem


Outra tarefa artificial para programação anormal em JavaScript . Desta vez, por ocasião do próximo ano novo de 2019. Espero que seja igualmente interessante decidir o quão interessante foi para mim. Eu pergunto curioso sob gato. Todo champanhe e todo feliz!


Tarefas anteriores:



Redação


No ano passado, o Papai Noel coletou uma lista decente de nomes de desenvolvedores normais e agora planeja escrever um programa de parabéns. O formato é: happy new year, ${username}! . Mas aqui está a má sorte: o teclado trava e não permite que você digite muitos caracteres latinos. Depois de examinar o defeito, os elfos fizeram uma observação interessante de que, do que mais funciona, você pode adicionar Snowing day um Snowing day . A fonte de saída pode ser selecionada a seu critério.

Portanto, na entrada - alguma matriz de cadeias não vazias (o nome não pode estar vazio). É necessário escrever um programa usando apenas caracteres latinos: S , n , o , w , i , g , d , a , y (um total de 9 caracteres, um dos quais está em maiúsculas). O programa deve contornar a matriz passada e gerar a frase happy new year, ${username}! para cada nome happy new year, ${username}! usando algum tipo de fonte de saída: alert , console.log ou o que vier à mente. Bem, seria bom não poluir o contexto global.


Solução habitual


Se você não inventa nada, tudo é muito simples:


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

ou melhor:


 function happy(users) { users.forEach(user => console.log(`happy new year, ${user}!`)); } 

Usamos com nossa matriz, sejam usuários :


 let users = ['John', 'Jack', 'James']; happy(users); // happy new year, John! // happy new year, Jack! // happy new year, James! 

Mas aqui está exagerado: usar apenas caracteres permitidos na implementação em latim. Tente lidar por conta própria primeiro e depois participe da discussão.


Decisão divertida


O paciente pode ver a solução abaixo no JSFiddle agora.


Para resolver o problema, você precisa se livrar do excesso de latim da seguinte maneira:


  1. A função de palavra-chave em uma declaração de função .
  2. A palavra-chave let (ou var ) para declarar variáveis.
  3. Palavras-chave ao organizar um loop para iterar sobre uma matriz passada.
  4. A formação do texto da mensagem.
  5. Chame alguma função para gerar o resultado.

As funções de seta nos ajudarão facilmente com o primeiro problema:


 (arr => el.forEach(/* ... */))(users); 

No momento, não prestaremos atenção aos nomes de variáveis, pois podemos renomeá-los facilmente no final.


Usamos “setas” com IIFE sempre que uma função é necessária ou seu resultado imediatamente. Além disso, as funções permitem livrar-se das diretivas let e var de duas maneiras:


 (param => /* ... */)(value); ((param = value) => /* ... */)(); 

Nos dois casos, declaramos uma variável nos parâmetros da função. Somente no primeiro caso, passamos o valor quando a função é chamada, e no segundo - usamos o parâmetro da função padrão.


De fato, os problemas começam no terceiro ponto. Não temos caracteres suficientes para os loops clássicos de for , do , while , nem para as opções de travessia de for..in e for..of , nem para os métodos de array paraCada , mapa , filtro (onde você pode passar o retorno de chamada). Mas podemos implementar nossa função de iteração sobre uma matriz:


 function iterate(arr, consume) { function iter(i) { if (arr[i]) { consume(arr[i]); iter(++i); } } iter(0); } 

Atravessamos recursivamente os elementos até que a verificação da condição atual caia. Por que podemos confiar na transformação lógica aqui? porque o elemento da nossa matriz não é uma string vazia (apenas envolve falso ), mas quando saímos da matriz por meio de um incremento de índice, somos indefinidos (convertidos para false ).


Reescrevemos a função usando as expressões de seta:


 let iterate = (arr, consume) => ( (iter = i => { if (arr[i]) { consume(arr[i]); iter(++i); } }) => iter(0) )(); 

Mas não podemos usar a declaração if , pois não temos o caractere f . Para que nossa função satisfaça a condição, precisamos nos livrar dela:


 let iterate = (arr, consume) => ( (iter = i => arr[i] ? (consume(arr[i]), iter(++i)) : 0) => iter(0) )(); 

O operador ternário e a capacidade de combinar duas expressões em uma através do operador vírgula nos ajudaram nisso. Usaremos essa função posteriormente no layout da solução.


O quarto problema é que, em qualquer caso, precisamos obter uma string com caracteres ausentes. Obviamente, usaremos números para representar caracteres. Existem várias opções:


  • Uma função String.fromCharCode que espera que o número inteiro retorne e retorne uma sequência criada a partir da sequência Unicode especificada.
  • A sequência de \uhhhh permite a saída de qualquer caractere Unicode usando o código hexadecimal especificado.
  • Formato &#dddd; para caracteres html permite exibir um símbolo no documento da página usando o código decimal especificado.
  • A função toString do objeto de protótipo Number possui um parâmetro radix adicional - a base do sistema numérico.
  • Talvez haja algo mais ...

Você pode ir na direção das três primeiras opções e agora considerar a provavelmente a mais fácil para esta tarefa: Number.prototype.toString . O valor máximo do parâmetro radix é 36 (10 dígitos + 26 caracteres latinos em minúsculas):


 let symb = sn => (sn + 9).toString(36); 

Assim, podemos obter qualquer caractere latino pelo número do alfabeto, começando com 1. A única limitação é que todos os caracteres são minúsculos. Sim, basta exibir o texto na mensagem, mas não podemos adicionar alguns métodos e funções (o mesmo para cada ).


Mas é muito cedo para se alegrar, primeiro você precisa se livrar do toString na entrada da função. Primeiro, passamos ao método da seguinte maneira:


 let symb = sn => (sn + 9)['toString'](36); 

Se você olhar atentamente, para a string toString , precisamos apenas de dois caracteres: t e r : todo o resto está na palavra Snowing . Obtê-los é bem simples, pois a ordem deles já indica a true . Usando conversões implícitas de tipo, podemos obter essa sequência e os caracteres necessários da seguinte maneira:


 !0+''; // 'true' (!0+'')[0]; // 't' (!0+'')[1]; // 'r' 

Atingimos a função de obter qualquer letra latina:


 let symb = sn => (sn + 9)[(!0+'')[0] + 'oS' + (!0+'')[0] + (!0+'')[1] + 'ing'](36); 

Para obter palavras de uma matriz de números de sequência de letras usando symb , usamos a função Array.prototype.reduce padrão:


 [1,2,3].reduce((res, sn) => res += symb(sn), ''); // 'abc' 

Sim, isso não nos convém. Mas em nossa solução, podemos fazer algo semelhante com a função iterate :


 let word = chars => (res => (iterate(chars, ch => res += symb(ch)), res))(''); word([1,2,3]); // 'abc' 

Pessoas atentas notarão que desenvolvemos a função iterada para uma série de strings, mas a usamos aqui com números. É por isso que o índice inicial do alfabeto é 1, e não 0. Caso contrário, um ciclo improvisado terminaria quando atingisse 0 (letra a ).


Para facilitar o mapeamento de caracteres para seus números de série, você pode obter um dicionário:


 [...Array(26).keys()].reduce((map, i) => (map[symb(i + 1)] = i + 1, map), {}); // {a: 1, b: 2, c: 3, d: 4, e: 5, …} 

Mas é mais sensato simplificar ainda mais e escrever a função da transformação inversa das palavras como um todo:


 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] 

Concluímos a função de formar a própria mensagem:


 let message = name => word([8,1,16,16,25]) + ' ' + word([14,5,23]) + ' ' + word([25,5,1,18]) + ', ' + name + '!'; 

Ainda resta muito pouco para lidar com a conclusão do quinto problema. Console , alerta , confirmação , prompt , innerHTML , document.write vêm à mente. Mas nenhuma das opções listadas pode ser acessada diretamente.


Também tivemos a oportunidade de obter qualquer palavra usando a função de palavra . Isso significa que podemos chamar muitas funções de objetos, acessando-as entre colchetes, como foi o caso de toString .


Como usamos as funções de seta, esse contexto permanece global (e não há necessidade de encaminhá-lo). Em qualquer lugar, podemos acessar muitas de suas funções através de uma linha:


 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'); 

Mas, para o "desenho" this novamente nos falta caracteres. Podemos substituí-lo por Window.self , mas é ainda pior em termos do alfabeto disponível. No entanto, vale a pena prestar atenção no próprio objeto da janela , cujo "contorno" é bastante satisfatório para nós, mesmo que fosse cabra, e é muito mais longo!


A propósito, na primeira versão da tarefa, a frase-chave era apenas a palavra Snowing e a window não podia ser dobrada (devido à ausência do caractere d ). O acesso ao contexto foi baseado em um dos truques do jsfuck :


 (_ => 0)['constructor']('return this')()['alert']('hello'); 

Ou você também pode acessar diretamente algo em um contexto global:


 (_ => 0)['constructor']('return alert')()('hello'); 

Como você pode ver, nos exemplos, todo o latim está em linhas. Aqui, criamos uma função a partir de uma string e obtemos acesso à Function (construtor) a partir da função de seta desperdiçada. Mas isso é de alguma forma demais! Talvez alguém saiba como acessar o contexto em nossas condições?


Finalmente, juntamos tudo! O corpo da nossa função "principal" chamará iterate para a matriz passada e o consumidor produzirá o resultado da função de compilação de mensagens já incorporada. Para o texto da mensagem e comandos, é usada uma função de palavra , que também precisa ser repetida , e a determinaremos a seguir nos parâmetros padrão . Assim:


 (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); 

Renomeie as variáveis ​​usando o alfabeto permitido:


 (_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); 

VariávelDescrição do produto
_123 {function}A função de iteração para iterar sobre os elementos de uma matriz.
_12 {function}A função iter local que itera as chamadas recursivamente.
neve {function}Consuma a função como um retorno de chamada para iterar .
ann {Array<Any>}Parâmetro de matriz.
um {Any}Parâmetro do elemento de matriz.
wo {function}A palavra funciona para formar palavras.
w {string}Variável local para acumular uma sequência de caracteres no word .
_10 {Array<string>}A matriz original de usuários.
_1 {string}O usuário da matriz de origem, seu nome.

Isso é tudo. Escreva suas idéias e pensamentos sobre isso, pois há muitas opções para fazer algo diferente ou nada.


Conclusão


É interessante que inventar uma palavra ou frase para as condições do problema seja um teste real. Queria que ela fosse baixa, pouco sugestiva e adequada para uma solução mais ou menos concisa.


A inspiração para esta tarefa foi fornecida pela funcionalidade do JavaScript e pelo isotérico de 6 caracteres conhecidos por muitos. Como tarefas discutidas anteriormente, isso pode ter várias variações sobre o tópico, e não a única solução. Basta chegar a uma redação simples e uma frase-chave. Vejo você no ano novo!

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


All Articles