
Auf der regulären Konferenz für
HolyJS- JavaScript-Entwickler
, die vom 24. bis 25. Mai in St. Petersburg stattfand, bot unser Firmenstand allen neue Aufgaben. Diesmal waren es 3! Die Aufgaben wurden der Reihe nach gegeben, und für die Lösung jedes nachfolgenden wurden die Insignien (JS Brave> JS Adept> JS Master) herangezogen, was als gute Motivation diente, nicht aufzuhören. Insgesamt haben wir rund
900 Antworten gesammelt und haben es eilig, eine Analyse der beliebtesten und einzigartigsten Lösungen zu teilen.
Die vorgeschlagenen Tests setzen voraus, dass die grundlegenden "Merkmale" der Sprache mutig und verständlich sind und dass die neuen Merkmale von ECMAScript 2019 bekannt sind (letzteres ist in der Tat nicht erforderlich). Es ist wichtig, dass diese Aufgaben nicht für Interviews, nicht praktisch und nur zum Zweck des Spaßes gedacht sind.
Aufgabe 1 ~ Countdown-Ausdruck
Was wird den Ausdruck zurückgeben? Ordnen Sie ein einzelnes Zeichen neu an
- Ausdruck zurückgegeben 2
- Ausdruck zurückgegeben 1
+(_ => [,,~1])().length
Zusätzlich : Ist es möglich, durch Permutationen 0 zu erhalten?
Was wird den Ausdruck zurückgeben?
Es dauert nicht lange, hier nachzudenken: Der Ausdruck wird
3 zurückgeben . Wir haben eine anonyme Funktion, die einfach ein Array von drei Elementen zurückgibt, von denen die ersten beiden leer sind. Der Funktionsaufruf gibt uns dieses Array, wir nehmen die Länge daraus und der unäre Operator plus löst nichts.
Der Ausdruck gibt 2 zurück
Eine schnelle Lösung scheint die Anzahl der Elemente im zurückgegebenen Array zu verringern. Um dies zu tun, werfen Sie einfach ein Komma weg:
[,~1].length
Es ist unmöglich, ein Symbol einfach so wegzuwerfen: Es muss an einer anderen Stelle im ursprünglichen Ausdruck neu angeordnet werden. Wir wissen jedoch, dass die
Längeneigenschaft des Arrays die leeren Elemente berücksichtigt, die im Array-Literal aufgeführt sind, mit Ausnahme
eines Falls :
Wenn ein Element am Ende eines Arrays entfernt wird, trägt dieses Element nicht zur Länge des Arrays bei.
Wenn sich also ein leeres Element am Ende des Arrays befindet, wird es ignoriert:
[,10,]
Der korrigierte Ausdruck sieht also folgendermaßen aus:
+(_ => [,~1,])().length
Gibt es eine andere Möglichkeit, dieses Komma zu entfernen? Oder fuhr weiter.
Der Ausdruck gibt 1 zurück
Es ist nicht mehr möglich, eine zu erhalten, indem die Größe des Arrays verringert wird, da Sie mindestens zwei Permutationen durchführen müssen. Müssen nach einer anderen Option suchen.
Die Funktion selbst weist auf die Entscheidung hin. Denken Sie daran, dass eine Funktion in JavaScript ein Objekt ist, das über eigene Eigenschaften und Methoden verfügt. Eine der Eigenschaften ist
auch die Länge , die die Anzahl der von der Funktion erwarteten Argumente bestimmt. In unserem Fall hat die Funktion ein einziges Argument (
Unterstrich ) - was Sie brauchen!
Damit die
Länge einer Funktion und nicht einem Array entnommen werden kann, müssen Sie den Aufruf in Klammern beenden. Es wird offensichtlich, dass eine dieser Klammern ein Anwärter auf eine Permutation ist. Und ein paar Optionen kommen in den Sinn:
+((_ => [,,~1])).length
Vielleicht gibt es noch etwas? Oder das nächste Level.
Der Ausdruck gibt 0 zurück
In der zusätzlichen Aufgabe ist die Anzahl der Permutationen nicht begrenzt. Aber wir können versuchen, es zu erfüllen, indem wir minimale Gesten machen.
Wenn Sie frühere Erfahrungen mit der
Länge eines Funktionsobjekts entwickeln, können Sie schnell zu einer Lösung kommen:
+(() => [,_,~1]).length
Hier gibt es mehrere Ableitungen, aber der springende Punkt ist, dass wir die Anzahl der Funktionsargumente auf Null reduziert haben. Dazu benötigten wir bis zu
drei Permutationen : zwei Klammern und den
Unterstrich , der zu einem Element des Arrays wurde.
Ok, aber vielleicht ist es an der Zeit, die Additionsoperatoren (+) und bitweise NICHT (~) in unseren Überlegungen nicht mehr zu ignorieren? Es scheint, dass die Arithmetik in diesem Problem in unsere Hände spielen kann. Um nicht zum Anfang zu springen, hier der ursprüngliche Ausdruck:
+(_ => [,,~1])().length
Zunächst berechnen wir
~ 1 .
Das bitweise NICHT von
x gibt
- (x + 1) zurück . Das heißt,
~ 1 = -2 . Und wir haben auch einen Additionsoperator im Ausdruck, der sozusagen andeutet, dass Sie irgendwo anders 2 weitere finden müssen und alles klappen wird.
Zuletzt haben wir uns an die „Nichtwirkung“ des letzten leeren Elements in einem Array-Literal in Bezug auf seine Größe erinnert, was bedeutet, dass unsere Zwei irgendwo hier sind:
[,,].length
Und alles summiert sich sehr erfolgreich: Wir holen das Element
~ 1 aus dem Array, reduzieren seine Länge auf 2 und fügen es als ersten Operanden für unsere Hinzufügung zum Anfang des Ausdrucks hinzu:
~1+(_ => [,,])().length
Damit haben wir das Ziel bereits in
zwei Permutationen erreicht !
Aber was ist, wenn dies nicht die einzige Option ist? Ein kleines Trommelwirbel ...
+(_ => [,,~1.])(),length
Es sind auch zwei Permutationen erforderlich: der Punkt nach der Einheit (dies ist möglich, da der numerische Typ nur eine
Zahl ist ) und das Komma vor der
Länge . Es scheint Unsinn, aber "manchmal" funktioniert. Warum manchmal?
In diesem Fall wird der Ausdruck durch den
Kommaoperator in zwei Ausdrücke unterteilt und das Ergebnis ist der berechnete Wert des zweiten. Aber der zweite Ausdruck ist nur
Länge ! Tatsache ist, dass wir hier in einem globalen Kontext auf den Wert einer Variablen zugreifen. Wenn die Laufzeit ein Browser ist, dann
window.length . Das
Fensterobjekt verfügt über
eine Längeneigenschaft , die die Anzahl der Frames auf der Seite zurückgibt. Wenn unser Dokument leer ist, gibt die
Länge 0 zurück. Ja, eine Option mit einer Annahme ... lassen Sie uns daher auf die vorherige eingehen.
Und hier sind einige weitere interessante Optionen entdeckt (bereits für eine andere Anzahl von Permutationen):
(_ => [,,].length+~1)()
Es gibt keine Kommentare. Findet jemand etwas noch lustiger?
Motivation
Gute altmodische Aufgabe, „ein oder zwei Übereinstimmungen neu anzuordnen, um ein Quadrat zu erstellen“. Das bekannte Rätsel zur Entwicklung von Logik und kreativem Denken verwandelt sich in solchen JavaScript-Variationen in Obskurantismus. Ist das nützlich? Wahrscheinlicher nein als ja. Wir haben viele Merkmale der Sprache angesprochen, einige sogar konzeptionell, um den Lösungen auf den Grund zu gehen. Bei echten Projekten geht es jedoch nicht um die
Länge von Funktion und Ausdruck bis hin zu Steroiden. Diese Aufgabe wurde auf der Konferenz als eine Art süchtig machendes Training vorgeschlagen, um sich daran zu erinnern, wie JavaScript ist.
Eval Kombinatorik
Es wurden nicht alle möglichen Antworten berücksichtigt, aber um überhaupt nichts zu verpassen, wenden wir uns
echtem JavaScript zu ! Wenn es interessant ist, sie selbst zu finden, gab es oben genügend Tipps, und dann ist es besser, nicht weiter zu lesen.

Wir haben also einen Ausdruck von 23 Zeichen, in dessen String-Datensatz wir Permutationen durchführen werden. Insgesamt müssen wir im Originaldatensatz des Ausdrucks
n * (n - 1) = 506 Permutationen durchführen, um alle Varianten mit einem permutierten Symbol zu erhalten (wie es die Bedingungen von Problem 1 und 2 erfordern).
Wir definieren eine
Kombinationsfunktion , die einen Eingabeausdruck als Zeichenfolge und Prädikat verwendet, um die Eignung des durch Ausführen dieses Ausdrucks erhaltenen Werts zu testen. Die Funktion erzwingt alle möglichen Optionen und wertet den Ausdruck durch Auswertung aus. Das Ergebnis wird in einem Objekt
gespeichert :
Schlüssel - der empfangene Wert,
Wert - eine Liste von Mutationen unseres Ausdrucks für diesen Wert. So etwas ist passiert:
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; }
Wobei die
Ersetzungsfunktion aus der übergebenen Zeichenfolge des Ausdrucks eine neue Zeichenfolge zurückgibt, deren Zeichen von Position
i zu Position
j neu angeordnet wurde . Und jetzt werden wir ohne große Angst tun:
console.dir(combine('+(_ => [,,~1])().length', val => typeof val === 'number' && !isNaN(val)));
Als Ergebnis haben wir eine Reihe von Lösungen erhalten:
{ "1": [ "+(_ => [,,~1]()).length", "+((_ => [,,~1])).length", "+(_ =>( [,,~1])).length", "+(_ => ([,,~1])).length" ], "2": [ "+(_ => [,~1,])().length" ] "3": [/* ... */] "-4": [/* ... */] }
Die Lösungen für 3 und -4 interessieren uns nicht, für die beiden haben wir die einzige Lösung gefunden, und für die Einheit gibt es einen interessanten neuen Fall mit
[,, ~ 1] () . Warum nicht
TypeError: bla-bla ist keine Funktion ? Und alles ist einfach: Dieser Ausdruck hat keinen Fehler für den syntaktischen Parser und wird zur Laufzeit einfach nicht ausgeführt, da die Funktion nicht aufgerufen wird.
Wie Sie sehen können, ist es in einer Permutation unmöglich, das Problem für Null zu lösen. Können wir es in zwei versuchen? Wenn wir das Problem durch eine so erschöpfende Suche lösen, haben wir in diesem Fall die Komplexität
O (n ^ 4) der Länge der Zeichenfolge und "bewerten" so oft verfolgt und bestraft, aber es herrscht Neugier. Es ist nicht schwierig, die gegebene
Kombinationsfunktion unabhängig zu verfeinern oder eine bessere Aufzählung zu schreiben, die die Merkmale eines bestimmten Ausdrucks berücksichtigt.
console.dir(combine2('+(_ => [,,~1])().length', val => val === 0));
Am Ende wäre die Menge der Lösungen für Null:
{ "0": [ "+(_ => [,~.1])(),length", "+(_ => [,~1.])(),length", "~1+(_ => [,,])().length" ] }
Es ist merkwürdig, dass wir in der Überlegung den Punkt nach der Einheit neu angeordnet haben, aber vergessen haben, dass der Punkt vor der Einheit ebenfalls möglich ist: Nehmen Sie
0,1 auf, wobei Null weggelassen wird.
Wenn Sie alle möglichen Permutationen mit jeweils zwei Zeichen ausführen, gibt es eine Vielzahl von Antworten für Werte im Bereich von 3 bis -4:
{ "3": 198, "2": 35, "1": 150, "0": 3, "-1": 129, "-2": 118, "-3": 15, "-4": 64 }
Somit kann der Lösungspfad für den
Countdown-Ausdruck bei zwei Permutationen länger sein als der vorgeschlagene Pfad von 3 bis 0 bei einer.
Dies war der erste Teil der Analyse unserer Aufgaben bei HolyJS 2019 und der zweite sollte bald erscheinen, wo wir die Lösungen des zweiten und dritten Tests betrachten werden. Wir bleiben in Kontakt!