
Na conferência regular para desenvolvedores de JavaScript do
HolyJS, realizada de 24 a 25 de maio em São Petersburgo, o estande da nossa empresa ofereceu a todos novas tarefas. Desta vez, havia três deles! As tarefas foram dadas por sua vez, e para a solução de cada uma subseqüente foi confiada a insígnia (JS Brave> JS Adept> JS Master), o que serviu como uma boa motivação para não parar. No total, coletamos cerca de
900 respostas e temos pressa em compartilhar uma análise das soluções mais populares e exclusivas.
Os testes propostos esperam do corajoso e do entendimento dos "recursos" básicos da linguagem e do conhecimento dos novos recursos do ECMAScript 2019 (na verdade, o último não é necessário). É importante que essas tarefas não sejam para entrevistas, não sejam práticas e sejam pensadas apenas para se divertir.
Tarefa 1 ~ Expressão de contagem regressiva
O que retornará a expressão? Reorganize qualquer caractere único para
- expressão retornou 2
- expressão retornada 1
+(_ => [,,~1])().length
Além disso : é possível obter 0 por permutações?
O que retornará a expressão?
Não demora muito para pensar aqui: a expressão retornará
3 . Temos uma função anônima que simplesmente retorna uma matriz de três elementos, os dois primeiros vazios. A chamada de função nos fornece essa matriz, tiramos o comprimento dela e o operador unário plus não resolve nada.
A expressão retorna 2
Uma solução rápida parece reduzir o número de elementos na matriz retornada. Para fazer isso, basta jogar uma vírgula:
[,~1].length
É impossível jogar fora um símbolo assim: ele precisa ser reorganizado para outro lugar na expressão original. Mas sabemos que a propriedade
length da matriz leva em consideração os elementos vazios listados no literal da matriz, exceto em
um caso :
Se um elemento é elidido no final de uma matriz, esse elemento não contribui para o comprimento da matriz.
Ou seja, se um elemento vazio estiver no final da matriz, ele será ignorado:
[,10,]
Assim, a expressão corrigida fica assim:
+(_ => [,~1,])().length
Existe outra opção para se livrar dessa vírgula? Ou seguiu em frente.
A expressão retorna 1
Não é mais possível obter um reduzindo o tamanho da matriz, pois você precisará fazer pelo menos duas permutações. Tem que procurar outra opção.
A própria função sugere a decisão. Lembre-se de que uma função em JavaScript é um objeto que possui propriedades e métodos próprios. E uma das propriedades
também é
comprimento , que determina o número de argumentos esperados pela função. No nosso caso, a função possui um único argumento (
sublinhado ) - o que você precisa!
Para que o
comprimento seja obtido de uma função, não de uma matriz, é necessário parar de chamá-la entre parênteses. Torna-se óbvio que um desses colchetes é um candidato a uma permutação. E algumas opções vêm à mente:
+((_ => [,,~1])).length
Talvez haja algo mais? Ou o próximo nível.
A expressão retorna 0
Na tarefa adicional, o número de permutações não é limitado. Mas podemos tentar cumpri-lo fazendo gestos mínimos.
Desenvolvendo a experiência anterior com o
comprimento de um objeto de função, você pode chegar rapidamente a uma solução:
+(() => [,_,~1]).length
Existem várias derivadas aqui, mas o ponto principal é que reduzimos o número de argumentos da função para zero. Para fazer isso, precisávamos de
três permutações : dois colchetes e o caractere
sublinhado , que se tornou um elemento da matriz.
Ok, mas talvez seja hora de parar de ignorar os operadores de adição (+) e NOT bit a bit (~) em nosso raciocínio? Parece que a aritmética nesse problema pode se colocar em nossas mãos. Para não voltar ao começo, aqui está a expressão original:
+(_ => [,,~1])().length
Para começar, calculamos
~ 1 .
O bit a bit NOT de
x retornará
- (x + 1) . Ou seja,
~ 1 = -2 . E também temos um operador de adição na expressão, o que, por assim dizer, sugere que em outro lugar você precisa encontrar mais 2 e tudo vai dar certo.
Mais recentemente, lembramos o “não efeito” do último elemento vazio em uma matriz literal em seu tamanho, o que significa que nosso empate está em algum lugar aqui:
[,,].length
E tudo é adicionado com muito sucesso: obtemos o elemento
~ 1 da matriz, reduzindo seu comprimento para 2, e o adicionamos como o primeiro operando para nossa adição ao início da expressão:
~1+(_ => [,,])().length
Assim, já alcançamos o objetivo em
duas permutações !
Mas e se essa não for a única opção? Um pequeno rolo de tambor ...
+(_ => [,,~1.])(),length
Também requer duas permutações: o ponto após a unidade (isso é possível, porque o tipo numérico é apenas
número ) e a vírgula antes do
comprimento . Parece bobagem, mas "às vezes" funciona. Por que às vezes?
Nesse caso, a expressão através do
operador vírgula é dividida em duas expressões e o resultado será o valor calculado da segunda. Mas a segunda expressão é apenas
comprimento ! O fato é que aqui estamos acessando o valor de uma variável em um contexto global. Se o tempo de execução for um navegador,
window.length . E o objeto da
janela possui
uma propriedade length que retorna o número de quadros na página. Se nosso documento estiver vazio, o
comprimento retornará 0. Sim, uma opção com uma suposição ... portanto, vamos nos debruçar sobre a anterior.
E aqui estão algumas opções mais interessantes descobertas (já para um número diferente de permutações):
(_ => [,,].length+~1)()
Não há comentários. Alguém encontra algo ainda mais divertido?
Motivação
Boa tarefa antiquada sobre "reorganizar uma ou duas partidas para formar um quadrado". O conhecido quebra-cabeça para o desenvolvimento da lógica e do pensamento criativo se transforma em obscurantismo nessas variações do JavaScript. Isso é útil? Provavelmente não, sim. Abordamos muitos recursos da linguagem, alguns até bastante conceituais, para chegar ao fundo das soluções. No entanto, projetos reais não se limitam ao
tamanho, da função e da expressão aos esteróides. Essa tarefa foi proposta na conferência como uma espécie de treino viciante para lembrar como é o JavaScript.
Eval combinatorics
Nem todas as respostas possíveis foram consideradas, mas para não perder nada, passamos ao
JavaScript real ! Se é interessante tentar encontrá-los você mesmo, havia dicas suficientes acima e é melhor não ler mais.

Portanto, temos uma expressão de 23 caracteres, no registro de string do qual faremos permutações. No total, precisamos executar
n * (n - 1) = 506 permutações no registro original da expressão para obter todas as variantes com um símbolo permutado (conforme exigido pelas condições dos problemas 1 e 2).
Definimos uma função de
combinação que recebe uma expressão de entrada como uma sequência e um predicado para testar a adequação do valor obtido pela execução dessa expressão. A função aplicará força bruta em todas as opções possíveis e avaliará a expressão por meio de
eval , salvando o resultado em um objeto:
chave - o valor recebido,
valor - uma lista de mutações de nossa expressão para esse valor. Algo assim aconteceu:
const combine = (expr, cond) => { let res = {}; let indices = [...Array(expr.length).keys()]; indices.forEach(i => indices.forEach(j => { if (i !== j) { let perm = replace(expr, i, j); try { let val = eval(perm); if (cond(val)) { (res[val] = res[val] || []).push(perm); } } catch (e) { } } })); return res; }
Onde a função de
substituição da sequência passada da expressão retorna uma nova sequência com o caractere reorganizado da posição
i para a posição
j . E agora, sem muito medo, faremos:
console.dir(combine('+(_ => [,,~1])().length', val => typeof val === 'number' && !isNaN(val)));
Como resultado, obtivemos conjuntos de soluções:
{ "1": [ "+(_ => [,,~1]()).length", "+((_ => [,,~1])).length", "+(_ =>( [,,~1])).length", "+(_ => ([,,~1])).length" ], "2": [ "+(_ => [,~1,])().length" ] "3": [/* ... */] "-4": [/* ... */] }
As soluções para 3 e -4 não nos interessam, para as duas que encontramos a única solução e para a unidade há um novo caso interessante com
[,, ~ 1] () . Por que não
TypeError: bla-bla não é uma função ? E tudo é simples: essa expressão não tem erro para o analisador sintático e, em tempo de execução, simplesmente não é executada, pois a função não é chamada.
Como você pode ver, em uma permutação é impossível resolver o problema para zero. Podemos tentar em dois? Resolvendo o problema com uma pesquisa tão exaustiva, neste caso, teremos a complexidade
O (n ^ 4) do comprimento da corda e "avaliaremos" tantas vezes perseguidas e punidas, mas a curiosidade prevalecerá. Não é difícil refinar
independentemente a função
combinada fornecida ou escrever uma enumeração melhor que leve em consideração os recursos de uma expressão específica.
console.dir(combine2('+(_ => [,,~1])().length', val => val === 0));
No final, o conjunto de soluções para zero seria:
{ "0": [ "+(_ => [,~.1])(),length", "+(_ => [,~1.])(),length", "~1+(_ => [,,])().length" ] }
É curioso que, no raciocínio, reorganizamos o ponto após a unidade, mas esquecemos que o ponto na frente da unidade também é possível: registro
0,1 com zero omitido.
Se você executar todas as permutações possíveis de dois caracteres cada, poderá descobrir que há muitas respostas para valores no intervalo de 3 a -4:
{ "3": 198, "2": 35, "1": 150, "0": 3, "-1": 129, "-2": 118, "-3": 15, "-4": 64 }
Assim, o caminho da solução
Countdown Expression em duas permutações pode ser maior que o caminho proposto de 3 a 0 em um.
Esta foi a primeira parte da análise de nossas tarefas no HolyJS 2019 e, em breve, a segunda deverá aparecer, onde consideraremos as soluções do segundo e terceiro testes. Entraremos em contato!