HolyJS 2019: Informe de SEMrush (Parte 1)



En la conferencia regular para desarrolladores de JavaScript de HolyJS que se celebró del 24 al 25 de mayo en San Petersburgo, el stand de nuestra compañía ofreció a todos nuevas tareas. ¡Esta vez había 3 de ellos! Las tareas se dieron a su vez, y para la solución de cada una de ellas se utilizó la insignia (JS Brave> JS Adept> JS Master), que sirvió como una buena motivación para no detenerse. En total, hemos recopilado alrededor de 900 respuestas y tenemos prisa por compartir un análisis de las soluciones más populares y únicas.

Las pruebas propuestas esperan de los valientes y la comprensión de las "características" básicas del lenguaje, y el conocimiento de las nuevas características de ECMAScript 2019 (de hecho, esto último no es necesario). Es importante que estas tareas no sean para entrevistas, no sean prácticas y estén pensadas solo para divertirse.

Tarea 1 ~ Expresión de cuenta regresiva


¿Qué devolverá la expresión? Reorganizar cualquier personaje individual para

  1. expresión devuelta 2
  2. expresión devuelta 1

+(_ => [,,~1])().length 

Además : ¿es posible obtener 0 por permutaciones?

¿Qué devolverá la expresión?


No es largo pensar aquí: la expresión devolverá 3 . Tenemos una función anónima que simplemente devuelve una matriz de tres elementos, los dos primeros están vacíos. La llamada a la función nos da esta matriz, tomamos su longitud y el operador unario plus no resuelve nada.

La expresión devuelve 2


Una solución rápida parece reducir el número de elementos en la matriz devuelta. Para hacer esto, solo tira una coma:

 [,~1].length // 2 

Es imposible arrojar un símbolo así: necesita ser reordenado a otro lugar en la expresión original. Pero sabemos que la propiedad de longitud de la matriz tiene en cuenta los elementos vacíos enumerados en el literal de la matriz, excepto en un caso :

Si un elemento se elide al final de una matriz, ese elemento no contribuye a la longitud de la matriz.

Es decir, si un elemento vacío está al final de la matriz, entonces se ignora:

 [,10,] // [empty, 10] 

Por lo tanto, la expresión corregida se ve así:

 +(_ => [,~1,])().length // 2 

¿Hay otra opción para deshacerse de esta coma? O siguió conduciendo.

La expresión devuelve 1


Ya no es posible obtener uno reduciendo el tamaño de la matriz, ya que tendrá que hacer al menos dos permutaciones. Hay que buscar otra opción.

La función en sí misma sugiere la decisión. Recuerde que una función en JavaScript es un objeto que tiene sus propias propiedades y métodos. Y una de las propiedades también es la longitud , que determina la cantidad de argumentos esperados por la función. En nuestro caso, la función tiene un único argumento ( guión bajo ): ¡lo que necesita!

Para que la longitud se tome de una función, no de una matriz, debe dejar de llamarla entre paréntesis. Es obvio que uno de estos soportes es un contendiente para una permutación. Y se me ocurren un par de opciones:

 +((_ => [,,~1])).length // 1 +(_ => ([,,~1])).length // 1 

Tal vez hay algo más? O el siguiente nivel.

La expresión devuelve 0


En la tarea adicional, el número de permutaciones no está limitado. Pero podemos intentar cumplirlo haciendo gestos mínimos.

Desarrollando experiencia previa con la longitud de un objeto de función, puede llegar rápidamente a una solución:

 +(() => [,_,~1]).length // 0 

Aquí hay varias derivadas, pero el punto es que redujimos el número de argumentos de función a cero. Para hacer esto, necesitábamos hasta tres permutaciones : dos corchetes y el carácter de subrayado , que se convirtió en un elemento de la matriz.

Ok, pero tal vez es hora de dejar de ignorar los operadores de suma (+) y NO (~) en nuestro razonamiento. Parece que la aritmética en este problema puede jugar en nuestras manos. Para no voltear al principio, aquí está la expresión original:

 +(_ => [,,~1])().length 

Para empezar, calculamos ~ 1 . El NOT bit a bit de x devolverá - (x + 1) . Es decir, ~ 1 = -2 . Y también tenemos un operador de suma en la expresión, que, por así decirlo, sugiere que en otro lugar necesitas encontrar 2 más y todo saldrá bien.

Más recientemente, recordamos el "no efecto" del último elemento vacío en una matriz literal en su tamaño, lo que significa que nuestro deuce está en algún lugar aquí:

 [,,].length // 2 

Y todo se suma con mucho éxito: obtenemos el elemento ~ 1 de la matriz, reduciendo su longitud a 2, y lo agregamos como el primer operando para nuestra adición al comienzo de la expresión:

 ~1+(_ => [,,])().length // 0 

¡Por lo tanto, hemos logrado el objetivo en dos permutaciones !

Pero, ¿y si esta no es la única opción? Un pequeño redoble de tambor ...

 +(_ => [,,~1.])(),length // 0 

También requiere dos permutaciones: el punto después de la unidad (esto es posible porque el tipo numérico es solo un número ) y la coma antes de la longitud . Parece una tontería, pero "a veces" funciona. ¿Por qué a veces?

En este caso, la expresión a través del operador de coma se divide en dos expresiones y el resultado será el valor calculado de la segunda. ¡Pero la segunda expresión es solo longitud ! El hecho es que aquí estamos accediendo al valor de una variable en un contexto global. Si el tiempo de ejecución es un navegador, entonces window.length . Y el objeto de ventana tiene una propiedad de longitud que devuelve el número de fotogramas en la página. Si nuestro documento está vacío, la longitud devolverá 0. Sí, una opción con una suposición ... por lo tanto, detengámonos en la anterior.

Y aquí hay algunas opciones más interesantes descubiertas (ya para un número diferente de permutaciones):

 (_ => [,,].length+~1)() // 0 +(~([,,].len_gth) >= 1) // 0 ~(_ => 1)()+[,,].length // 0 ~(_ => 1)().length,+[,] // 0 ~[,,]+(_ => 1()).length // 0 

No hay comentarios ¿Alguien encuentra algo aún más divertido?

Motivación


Buena tarea pasada de moda sobre "reorganizar uno o dos partidos para hacer un cuadrado". El conocido rompecabezas para el desarrollo de la lógica y el pensamiento creativo se convierte en oscurantismo en tales variaciones de JavaScript. ¿Esto es útil? Más probablemente no que sí. Tocamos muchas características del lenguaje, algunas incluso bastante conceptuales, para llegar al fondo de las soluciones. Sin embargo, los proyectos reales no tienen que ver con la longitud desde la función y la expresión hasta los esteroides. Esta tarea fue propuesta en la conferencia como una especie de entrenamiento adictivo para recordar cómo es JavaScript.

Eval combinatorics


No se consideraron todas las respuestas posibles, pero para no perder nada, recurrimos a JavaScript real . Si es interesante tratar de encontrarlos usted mismo, hay suficientes sugerencias arriba, y es mejor no leer más.



Entonces, tenemos una expresión de 23 caracteres, en el registro de cadena de los cuales haremos permutaciones. En total, necesitamos realizar n * (n - 1) = 506 permutaciones en el registro original de la expresión para obtener todas las variantes con un símbolo permutado (como lo requieren las condiciones de los problemas 1 y 2).

Definimos una función de combinación que toma una expresión de entrada como una cadena y un predicado para probar la idoneidad del valor obtenido al ejecutar esta expresión. La función aplicará fuerza bruta a todas las opciones posibles y evaluará la expresión a través de eval , guardando el resultado en un objeto: clave - el valor recibido, valor - una lista de mutaciones de nuestra expresión para este valor. Algo así sucedió:

 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) { /* do nothing */ } } })); return res; } 

Donde la función de reemplazo de la cadena pasada de la expresión devuelve una nueva cadena con el carácter reorganizado de la posición i a la posición j . Y ahora, sin mucho miedo, haremos:

 console.dir(combine('+(_ => [,,~1])().length', val => typeof val === 'number' && !isNaN(val))); 

Como resultado, obtuvimos conjuntos de soluciones:

 { "1": [ "+(_ => [,,~1]()).length", "+((_ => [,,~1])).length", "+(_ =>( [,,~1])).length", "+(_ => ([,,~1])).length" ], "2": [ "+(_ => [,~1,])().length" ] "3": [/* ... */] "-4": [/* ... */] } 

Las soluciones para 3 y -4 no nos interesan, para las dos encontramos la única solución, y para la unidad hay un nuevo caso interesante con [,, ~ 1] () . ¿Por qué no TypeError: bla-bla no es una función ? Y todo es simple: esta expresión no tiene ningún error para el analizador sintáctico, y en tiempo de ejecución simplemente no se ejecuta, ya que la función no se llama.

Como puede ver, en una permutación es imposible resolver el problema por cero. ¿Podemos probar en dos? Resolviendo el problema mediante una búsqueda tan exhaustiva, en este caso tendremos la complejidad O (n ^ 4) de la longitud de la cadena y "evaluaremos" tantas veces perseguido y castigado, pero prevalece la curiosidad. No es difícil refinar independientemente la función de combinación dada o escribir una enumeración mejor que tenga en cuenta las características de una expresión particular.

 console.dir(combine2('+(_ => [,,~1])().length', val => val === 0)); 

Al final, el conjunto de soluciones para cero sería:

 { "0": [ "+(_ => [,~.1])(),length", "+(_ => [,~1.])(),length", "~1+(_ => [,,])().length" ] } 

Es curioso que en el razonamiento reorganizamos el punto después de la unidad, pero olvidamos que el punto en frente de la unidad también es posible: registro 0.1 con cero omitido.

Si realiza todas las permutaciones posibles de dos caracteres cada una, puede encontrar que hay muchas respuestas para valores en el rango de 3 a -4:

 { "3": 198, "2": 35, "1": 150, "0": 3, "-1": 129, "-2": 118, "-3": 15, "-4": 64 } 

Por lo tanto, la ruta de solución de Expresión de cuenta regresiva en dos permutaciones puede ser más larga que la ruta propuesta de 3 a 0 en una.

Esta fue la primera parte del análisis de nuestras tareas en HolyJS 2019, y pronto debería aparecer la segunda, donde consideraremos las soluciones de las pruebas segunda y tercera. Estaremos en contacto!

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


All Articles