Tipps zum Golfen in ECMAScript 6 und höher

88

Dies ähnelt anderen "Tipps zum Golfen in <...>", zielt jedoch speziell auf die neueren Funktionen in JavaScript ab ECMAScript 6 ab.

JavaScript ist von Natur aus eine sehr ausführliche Sprache function(){}, .forEach()Umwandeln String - Array, Array-artiges Objekt Array, etc, etc Super bloats sind und nicht gesund für Golf.

ES6 + hingegen bietet einige sehr praktische Funktionen und eine geringere Stellfläche. x=>y, [...x]usw. sind nur einige Beispiele.

Bitte poste ein paar nette Tricks, die dabei helfen können, die wenigen zusätzlichen Bytes aus deinem Code zu entfernen.

HINWEIS: Tricks für ES5 sind bereits in Tipps zum Golfen in JavaScript verfügbar . Die Antworten auf diesen Thread sollten sich auf Tricks konzentrieren, die nur in ES6 und anderen zukünftigen ES-Versionen verfügbar sind.

Dieser Thread ist jedoch auch für Benutzer gedacht, die derzeit mit ES5-Funktionen Golf spielen. Die Antworten enthalten möglicherweise auch Tipps zum Verständnis und zur Zuordnung der ES6-Funktionen zu ihrem ES5-Codierungsstil.

Optimierer
quelle

Antworten:

42

Spread-Operator ...

Der Spread-Operator wandelt einen Array-Wert in eine durch Kommas getrennte Liste um.

Anwendungsfall 1:

Verwenden Sie direkt ein Array, in dem eine Funktion eine Liste erwartet

list=[1,2,3]
x=Math.min(...list)
list=[10,20], a.push(...list) // similar to concat()

Anwendungsfall 2:

Erstellen Sie ein Array-Literal aus einem iterablen Wert (normalerweise einer Zeichenfolge).

[...'buzzfizz'] // -> same as .split('')

Anwendungsfall 3:

Deklarieren Sie eine variable Anzahl von Argumenten für eine Funktion

F=(...x) => x.map(v => v+1)
// example: F(1,2,3) == [2,3,4]

Siehe Mozilla Doc

edc65
quelle
3
Jetzt habe ich hier eine Ablehnung. Offensichtlich bemerkte jemand etwas furchtbares in diesem Tipp, zu schüchtern, um einen Kommentar zu hinterlassen und zu erklären, was ...
edc65
Es sieht gut aus. Vielleicht war es das Fehlen von Semikolons? ;) (übrigens, Sie können es auch als Ruheparameter verwenden, wie Splats in Ruby)
Gcampbell
Sie könnten hinzufügen, dass es auch einen Anwendungsfall in Funktionssignaturen hat :)
Felix Dombek
Ein falscher Klick bedeutete nicht, einen Downvot durchzuführen
Stan Strum
@StanStrum passiert es. Ich mache ein kleines Update für diesen Beitrag, damit du deine Stimme eventuell ändern kannst (oder hast du es bereits getan?)
edc65
21

Tricks, die ich hier gelernt habe, seit ich dazugekommen bin

Meine primäre Programmiersprache ist JS und hauptsächlich ES6. Seit ich vor einer Woche auf dieser Seite bin, habe ich viele nützliche Tricks von Kollegen gelernt. Einige davon kombiniere ich hier. Alle Kredite an die Community.

Pfeilfunktionen und Schleifen

Wir alle wissen, dass Pfeilfunktionen viele Bytes sparen

function A(){do something} // from this
A=a=>do something // to this

Aber Sie müssen ein paar Dinge beachten

  • Versuchen Sie, mehrere Anweisungen mit dem folgenden Befehl zu ,klassifizieren: (a=b,a.map(d))- Hier ist der zurückgegebene Wert der letzte Ausdrucka.map(d)
  • Wenn Ihr do somethingTeil mehr als eine Anweisung ist, müssen Sie die umgebenden {}Klammern hinzufügen .
  • Wenn es umgebende {}Klammern gibt, müssen Sie eine explizite return-Anweisung hinzufügen.

Das oben Gesagte trifft oft zu, wenn es sich um Schleifen handelt. Also so etwas wie:

u=n=>{for(s=[,1,1],r=[i=1,l=2];c=l<n;i+=!c?s[r[l++]=i]=1:1)for(j of r)c-=j<i/2&s[i-j];return n>1?r:[1]}

Hier verschwende ich wegen der Rückgabe mindestens 9 Zeichen. Dies kann optimiert werden.

  • Vermeiden Sie Schleifen. Verwenden Sie stattdessen .mapoder .everyoder .some. Beachten Sie, dass ein Fehler auftritt, wenn Sie dasselbe Array ändern möchten, über das Sie eine Zuordnung vornehmen.
  • Wickeln Sie die Schleife in eine schließende Pfeilfunktion, und konvertieren Sie die Hauptpfeilfunktion als einzelne Anweisung.

So wird das Obige:

u=n=>(s=>{for(r=[i=1,l=2];c=l<n;i+=!c?s[r[l++]=i]=1:1)for(j of r)c-=j<i/2&s[i-j]})([,1,1])|n>1?r:[1]

entfernte Zeichen: {}return

hinzugefügte Zeichen: (){}>|

Beachten Sie, wie ich die Closure-Methode aufrufe, die die Variable korrekt auffüllt, nund da die Closure-Methode dann nichts zurückgibt (dh zurückgibt undefined), bitweise oder es und das Array nalle in einer einzigen Anweisung der äußeren Pfeilfunktion zurückgebeu

Kommas und Semikolons

Weiche ihnen aus,

Wenn Sie Variablen in einer Schleife deklarieren oder, wie im vorherigen Abschnitt erwähnt, mit ,getrennten Anweisungen einzelne Anweisungspfeile verwenden, können Sie einige ziemlich raffinierte Tricks anwenden, um diese zu vermeiden ,oder ;die letzten paar Bytes zu entfernen.

Betrachten Sie diesen Code:

r=v=>Math.random()*100|0;n=r();m=r();D=v=>A(n-x)+A(m-y);d=0;do{g();l=d;d=D();....

Hier rufe ich viele Methoden auf, um viele Variablen zu initialisieren. Jede Initialisierung verwendet ein ,oder ;. Dies kann wie folgt umgeschrieben werden:

r=v=>Math.random()*100|0;n=r(m=r(d=0));D=v=>A(n-x)+A(m-y);do{d=D(l=d,g());....

Beachten Sie, wie ich die Tatsache verwende, dass die Methode die an sie übergebene Variable nicht stört, und diese Tatsache verwende, um 3 Bytes zu rasieren.

Sonstiges

.search Anstatt von .indexOf

Beide geben das gleiche Ergebnis, sind aber searchkürzer. Die Suche erwartet zwar einen regulären Ausdruck, verwenden Sie ihn jedoch mit Bedacht.

`Template Strings`

Diese sind sehr praktisch, wenn Sie einen oder mehrere Saitenteile unter bestimmten Bedingungen zusammenfügen müssen.

Nehmen Sie das folgende Beispiel, um ein Quine in JS auszugeben

(f=x=>alert("(f="+f+")()"))()

gegen

(f=x=>alert(`(f=${f})()`))()

In einer Vorlagenzeichenfolge, bei der es sich um eine Zeichenfolge in zwei Anführungszeichen (`) handelt, wird alles in a ${ }als Code behandelt und ausgewertet, um die resultierende Antwort in die Zeichenfolge einzufügen.

Ich werde später noch ein paar Tricks posten. Viel Spaß beim Golfen!

Optimierer
quelle
1
.search ist kürzer, benutze es wenn möglich! aber es ist nicht dasselbe von .indexOf. .search will einen regexp, keinen String. Try'abc'.search('.')
edc65
@ edc65 Aktualisiert!
Optimierer
Sie können das ursprüngliche Array mit den Instanzmethoden ändern. Der zweite ist der aktuelle Index und der dritte ist das Array, das iteriert wird.
Isiah Meadows
8
"Ist vor einer Woche der Site beigetreten" - 21.4k rep ...
GamrCorps
2
Neben .mapist Rekursion eine andere Technik , die manchmal können Ihnen helfen , eine Wende - forSchleife in einen Ausdruck.
Neil
20

Verwenden von Eigenschaftskürzeln

Mit Eigenschaftskurzbefehlen können Sie Variablen auf die Werte eines Arrays setzen:

a=r[0];b=r[1] // ES5
[a,b]=r       // ES6 - 6 bytes saved

Dies kann auch verwendet werden wie:

a=r[0],b=r[2] // ES5
[a,,b]=r      // ES6 - 5 bytes saved

Sie können dies sogar verwenden, um Variablen umzukehren:

c=a,a=b,b=c // ES5 - uses extra variable
[b,a]=[a,b] // ES6 - not shorter, but more flexible

Hiermit können Sie auch slice()Funktionen verkürzen .

z = [1, 2, 3, 4, 5];

a=z.slice(1) // a = [2,3,4,5]; ES5
[,...a]=z    // a = [2,3,4,5]; ES6

Basis-Conversions

ES6 bietet eine viel kürzere Möglichkeit, die Formulare Base-2 (binär) und Base-8 (oktal) in Dezimalzahlen umzuwandeln:

0b111110111 // == 503
0o767       // == 503

+kann verwendet werden, um eine binäre, oktale oder hexadezimale Zeichenfolge in eine Dezimalzahl umzuwandeln. Sie können verwendet werden 0b, 0ound 0xfür Binär-, und hex jeweils .:

parseInt(v,2) // ES5
+('0b'+v)     // ES6 - 4 bytes saved; use '0o' for octal and '0x' for hex
'0b'+v-0      // Shorter, but may not work in all cases
              // You can adapt this your case for better results

Wenn Sie dies> 7 Mal verwenden, ist es kürzer, es zu verwenden parseIntund umzubenennen:

(p=parseInt)(v,2)

Jetzt pkönnen parseIntSie viele Bytes auf lange Sicht sparen.

Downgoat
quelle
Der grundlegende Konvertierungstrick ist nett, aber es ist wahrscheinlicher, dass die Konvertierungsnummer in Form einer Variablen statt eines Literal vorliegt. In diesem Fall wird sie viel länger.
Optimierer
1
'0x'+v-0ist noch kürzer, funktioniert aber in einigen Szenarien möglicherweise nicht so gut.
ETHproductions
1
Übrigens ist 0767(ES5) kürzer als die 0o767(ES6) -Notation.
Camilo Martin
@CamiloMartin 0767ist keine Standarderweiterung und im strikten Modus ausdrücklich verboten.
Oriol
1
@Oriol Strict Mode war ein schlechtes Meme. Es hat die Leistung nicht verbessert, hat Sie nicht wirklich dazu gezwungen, guten Code zu schreiben, und würde sowieso nie zum Standard werden. 0- Vorgefertigte oktale Literale gehen nirgendwo hin und sind so gültig wie ecmascript 0o.
Camilo Martin
19

Verwenden von Zeichenfolgenvorlagen mit Funktionen

Wenn Sie eine Funktion mit einer Zeichenfolge als Argument haben. Sie können das weglassen, ()wenn Sie keine Ausdrücke haben:

join`` // Works
join`foobar` // Works
join`${5}` // Doesn't work 
Downgoat
quelle
9
Seien Sie gewarnt, dies passiert tatsächlich ein Array. fun`string` ist das gleiche wie fun(["string"]), nicht fun("string"). Dies ist in Ordnung für Funktionen, die in Zeichenfolgen umgewandelt werden, wie alertz. B. , aber für andere kann dies zu Problemen führen. Weitere Informationen finden Sie im MDN-Artikel
Cyoce
5
Kurzreferenz Sache: fun`foo${1}bar${2}bazentspricht dem Aufruffun(["foo","bar","baz"],1,2)
Cyoce
14

Array-Verständnis (Firefox 30-57)

Hinweis: Das Array-Verständnis wurde nie standardisiert und wurde mit Firefox 58 überholt. Verwenden Sie es auf eigene Gefahr.


Ursprünglich enthielt die ECMAScript 7-Spezifikation eine Reihe neuer Array-basierter Funktionen. Obwohl die meisten davon nicht in die endgültige Version kamen, unterstützt Firefox (ed) möglicherweise die größte dieser Funktionen: Fantasievolle neue Syntax, die .filterund .mapdurch for(a of b)Syntax ersetzen kann . Hier ist ein Beispiel:

b.filter(a=>/\s/.test(a)).map(a=>a.length)
[for(a of b)if(/\s/.test(a))a.length]

Wie Sie sehen, unterscheiden sich die beiden Zeilen nur geringfügig. Die zweite Zeile enthält keine umfangreichen Schlüsselwörter und Pfeilfunktionen. Dies erklärt aber nur die Reihenfolge .filter().map(); Was passiert, wenn Sie .map().filter()stattdessen haben? Es kommt wirklich auf die Situation an:

b.map(a=>a[0]).filter(a=>a<'['&&a>'@')
[for(a of b)if(a<'['&&a>'@')a[0]]

b.map(a=>c.indexOf(a)).filter(a=>a>-1)
[for(a of b)if((d=c.indexOf(a))>-1)d]

b.map(a=>a.toString(2)).filter(a=>/01/.test(a))
[for(a of b)if(/01/.test(c=a.toString(2)))c]

Oder was ist, wenn Sie entweder .map oder wollen .filter? Nun, es stellt sich normalerweise als weniger OK heraus:

b.map(a=>a.toString(2))
[for(a of b)a.toString(2)]

b.filter(a=>a%3&&a%5)
[for(a of b)if(a%3&&a%5)a]

Mein Rat ist also, Array-Verständnis dort zu verwenden, wo Sie es normalerweise verwenden würden, .map und .filter nicht nur das eine oder das andere.

String-Verständnis

Das Schöne am ES7-Verständnis ist, dass sie im Gegensatz zu Array-spezifischen Funktionen wie .mapund .filterfür jedes iterierbare Objekt und nicht nur für Arrays verwendet werden können. Dies ist besonders nützlich, wenn Sie mit Zeichenfolgen arbeiten. Wenn Sie beispielsweise jedes Zeichen cin einer Zeichenfolge ausführen möchten, gehen Sie wie folgt vor c.charCodeAt():

x=>[...x].map(c=>c.charCodeAt())
x=>[for(c of x)c.charCodeAt()]

Das sind zwei Bytes, die in relativ geringem Umfang eingespart werden. Und was ist, wenn Sie bestimmte Zeichen in einer Zeichenfolge filtern möchten? Zum Beispiel enthält dieser nur Großbuchstaben:

x=>[...x].filter(c=>c<'['&&c>'@')
x=>[for(c of x)if(c<'['&&c>'@')c]

Hmm, das ist nicht kürzer. Aber wenn wir beides kombinieren:

x=>[...x].filter(c=>c<'['&&c>'@').map(c=>c.charCodeAt())
x=>[for(c of x)if(c<'['&&c>'@')c.charCodeAt()]

Wow, ganze 10 Bytes gespart!

Ein weiterer Vorteil des Zeichenfolgenverständnisses besteht darin, dass fest codierte Zeichenfolgen ein zusätzliches Byte einsparen, da Sie das Leerzeichen weglassen können, nachdem of:

x=>[...'[](){}<>'].map(c=>x.split(c).length-1)
x=>[for(c of'[](){}<>')x.split(c).length-1]

x=>[...'[](){}<>'].filter(c=>x.split(c).length>3)
x=>[for(c of'[](){}<>')if(x.split(c).length>3)c]

Indizierung

Das Array-Verständnis macht es etwas schwieriger, den aktuellen Index in der Zeichenfolge / dem Array abzurufen. Dies kann jedoch folgendermaßen geschehen:

a.map((x,i)=>x+i).filter ((x,i)=>~i%2)
[for(x of(i=0,a))if(++i%2)x+i-1]

Das Wichtigste, worauf Sie achten müssen, ist sicherzustellen, dass der Index jedes Mal erhöht wird , nicht nur, wenn eine Bedingung erfüllt ist.

Verständnis des Generators

Generatorverständnisse haben grundsätzlich die gleiche Syntax wie Arrayverständnisse. Ersetzen Sie einfach die Klammern durch Klammern:

x=>(for(c of x)if(c<'['&&c>'@')c.charCodeAt())

Dies erzeugt einen Generator , der ähnlich wie ein Array funktioniert, aber das ist eine Geschichte für eine andere Antwort.

Zusammenfassung

Grundsätzlich .map().filter()kommt es auf die Besonderheiten der Situation an , obwohl das Verständnis in der Regel kürzer ist als . Probieren Sie es am besten in beide Richtungen aus und finden Sie heraus, was besser funktioniert.

PS Sie können gerne einen anderen verständnisbezogenen Tipp vorschlagen oder eine Möglichkeit finden, diese Antwort zu verbessern!

ETHproductions
quelle
Hier ist ein Trick für Bereiche, die ein paar weitere Zeichen speichern:(x,y)=>[...Array(y-x)].map(a=>x++)
Mwr247
2
Sie können weitere 11 Bytes abschneiden, um einen Bereich von 0 bis x x=>[...Array(x).keys()]
festzulegen
Letzter für das Verständnis dort: n=>[for(x of Array(n).keys())if(/1/.test(x))x](spart 7 Bytes)
Mwr247
@ Mwr247 Eigentlich kann ich jetzt sehen, dass die Reichweiten beim Verständnis in der Regel nicht so kurz sind wie bei anderen netten ES6-Features. Ich füge stattdessen einen Abschnitt über Zeichenfolgen hinzu und lasse Sie mit Bereichen umgehen.
ETHproductions
Es ist erwähnenswert, dass die Array-Funktionen in allen neueren Versionen von Javascript nicht mehr unterstützt und entfernt wurden. Weitere Informationen finden Sie in den MDN-Dokumenten zu diesem Thema.
Keefer Rourke
13

Funktionsausdrücke in ES6 verwenden die Pfeilnotation und helfen viel, im Vergleich zur ES5-Version Bytes einzusparen:

f=function(x,y){return x+y}
f=(x,y)=>x+y

Wenn Ihre Funktion nur einen Parameter hat, können Sie die Klammern weglassen, um zwei Bytes zu sparen:

f=x=>x+1

Wenn Ihre Funktion überhaupt keine Parameter hat, deklarieren Sie sie so, als hätte sie einen, um ein Byte zu speichern:

f=()=>"something"
f=x=>"something"

Achtung: Die Pfeilfunktionen sind nicht identisch mit function () {}. Die Regeln für thissind unterschiedlich (und besser IMO). Siehe Dokumente

William Barbosa
quelle
2
Aber wenn Sie Golf spielen, interessieren Sie sich im Allgemeinen nicht für thisusw.
Optimizer
1
Im Allgemeinen nicht, aber es ist eine Einschränkung, die Sie möglicherweise nie erfahren, wenn sie auftritt. Es ist auch üblicher, dass Lambdas in der Produktion keine funktionslokale Bindung benötigen.
Isiah Meadows
Auch, wenn Sie alle Ihre Argumente nehmen möchten, können Sie den „Rest“ Argument Funktion verwenden, zum Beispiel f=(...x)=>x würde , dass f(1,2,3) => [1,2,3].
Conor O'Brien
1
Hier ist ein Tipp speziell für diese Site: Wenn Sie mit einer Funktion antworten, die die Form annimmt (x,y)=>..., können Sie ein Byte durch Curry speichern, indem Sie es durchx=>y=>...
Cyoce
12

Verwenden evalfür Pfeilfunktionen mit mehreren Anweisungen und areturn

Einer der lächerlicheren Tricks, über die ich gestolpert bin ...

Stellen Sie sich eine einfache Pfeilfunktion vor, die mehrere Anweisungen benötigt, und a return.

a=>{for(o="",i=0;i<a;i++)o+=i;return o}

Eine einfache Funktion, die einen einzelnen Parameter akzeptiert a, der alle Ganzzahlen [0, a)durchläuft und sie am Ende der zurückgegebenen Ausgabezeichenfolge anheftet o. Wenn Sie dies beispielsweise mit 4als Parameter aufrufen, erhalten Sie 0123.

Beachten Sie, dass diese Pfeilfunktion in geschweifte Klammern eingeschlossen werden {}musste und return oam Ende ein hat.

Dieser erste Versuch wiegt 39 Bytes .

Nicht schlecht, aber mit evalkönnen wir das verbessern.

a=>eval('for(o="",i=0;i<a;i++)o+=i;o')

Diese Funktion entfernte die geschweiften Klammern und die return-Anweisung, indem sie den Code in ein evalumhüllte und einfach die letzte Anweisung in der evalevalu to- Anweisung machte o. Dies bewirkt die evalRückgabe von o, was wiederum die Rückgabe der Funktion bewirkt o, da es sich nun um eine einzelne Anweisung handelt.

Dieser verbesserte Versuch hat ein Gewicht von 38 Byte und spart ein Byte gegenüber dem Original.

Aber warte, es gibt noch mehr! Eval-Anweisungen geben die zuletzt ausgewertete Anweisung zurück. In diesem Fall o+=iwertet das aus o, damit wir das nicht brauchen ;o! (Danke, edc65!)

a=>eval('for(o="",i=0;i<a;i++)o+=i')

Dieser letzte Versuch wiegt nur 36 Bytes - eine Einsparung von 3 Bytes gegenüber dem Original!


Diese Technik kann auf alle allgemeinen Fälle ausgedehnt werden, in denen eine Pfeilfunktion einen Wert zurückgeben und mehrere Anweisungen haben muss (die auf andere Weise nicht kombiniert werden können).

b=>{statement1;statement2;return v}

wird

b=>eval('statement1;statement2;v')

ein Byte speichern.

Wenn statement2bewertet v, kann dies sein

b=>eval('statement1;statement2')

Einsparung von insgesamt 3 Bytes.

jrich
quelle
1
Ich denke, nur das Schreiben einer anonymen Funktion könnte noch kürzer sein
Downgoat
@vihan yeah, beide Funktionen könnten anonymisiert werden, um jeweils 2 Bytes zu sparen. Die Ein-Byte-Ersparnis bleibt jedoch bestehen.
Jrich
1
Aber noch besser: eval gibt den zuletzt ausgewerteten Ausdruck zurück, Sie brauchen ihn also nicht ;o- probieren Sie es aus:a=>eval('for(o="",i=0;i<a;i++)o+=i')
edc65
4
Aber Template-Strings!
Conor O'Brien,
1
@ CᴏɴᴏʀO'Bʀɪᴇɴ Möchten Sie anhand der Beispielfunktion als Kontext erläutern, wie Vorlagenzeichenfolgen hier funktionieren würden?
WallyWest
10

Neue Zeilen der Vorlagenzeichenfolge "\ n" vorziehen

Dies macht sich bereits bei einem einzelnen neuen Zeilenzeichen in Ihrem Code bezahlt. Ein Anwendungsfall könnte sein:

(16 Bytes)

array.join("\n")

(15 Bytes)

array.join(`
`)

Update: Sie können sogar die geschweiften Klammern wegen markierter Template-Strings weglassen (danke, edc65!):

(13 Bytes)

array.join`
`
Chiru
quelle
5
Aber noch besser, Sie können die Klammer vermeiden. Lesen Sie die Dokumentation ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), um zu
erfahren,
Ah richtig. Danke, ich habe es hinzugefügt.
Chiru
9

Arrays füllen - Statische Werte und dynamische Bereiche

Ursprünglich habe ich diese als Kommentare unter Verstehen hinterlassen, aber da sich dieser Beitrag in erster Linie auf Verstehen konzentrierte, dachte ich, dass es gut wäre, diesem seinen eigenen Platz zu geben.

ES6 gab uns die Möglichkeit, Arrays ohne Schleifen mit statischen Werten zu füllen:

// ES5
function(x){for(i=0,a=[];i<x;i++)a[i]=0;return a}

// ES6
x=>Array(x).fill(0)

Beide geben ein Array der Länge x zurück, das mit dem Wert 0 gefüllt ist.

Wenn Sie Arrays jedoch mit dynamischen Werten füllen möchten (z. B. mit einem Bereich von 0 ... x), ist das Ergebnis etwas länger (obwohl es immer noch kürzer als der alte Weg ist):

// ES5
function(x){for(i=0,a=[];i<x;i++)a[i]=i;return a}

// ES6
x=>Array(x).fill().map((a,i)=>i)

Beide geben ein Array der Länge x zurück, beginnend mit dem Wert 0 und endend mit x-1.

Der Grund, warum Sie das .fill()dort benötigen, liegt darin, dass Sie es durch einfaches Initialisieren eines Arrays nicht zuordnen können. Dies bedeutet, dass beim Ausführen x=>Array(x).map((a,i)=>i)ein leeres Array zurückgegeben wird. Sie können den Füllbedarf auch umgehen (und ihn damit noch kürzer machen), indem Sie den Spread-Operator wie folgt verwenden:

x=>[...Array(x)]

Mit dem Operator und der .keys()Funktion spread können Sie jetzt einen kurzen Bereich von 0 ... x festlegen:

x=>[...Array(x).keys()]

Wenn Sie einen benutzerdefinierten Bereich von x ... y oder einen speziellen Bereich (z. B. gerade Zahlen) möchten, können Sie den Spread-Operator entfernen .keys()und einfach verwenden .map()oder verwenden .filter():

// Custom range from x...y
(x,y)=>[...Array(y-x)].map(a=>x++)

// Even numbers (using map)
x=>[...Array(x/2)].map((a,i)=>i*2)

// Even numbers (using filter)
x=>[...Array(x).keys()].filter(a=>~a%2)
Mwr247
quelle
Hier ist ein Vorschlag für das zweite Beispiel: x=>Array(x).fill(i=0).map(a=>i++)Außerdem bin ich mir nicht sicher, ob die Eingabe von 0 .fill(0)erforderlich ist. Hast du es ohne probiert?
ETHproductions
@ETHproductions Du hast Recht, ich habe vergessen, dass die 0 in der Füllung vor der Karte nicht benötigt wird. Das macht es 1 Zeichen kürzer als das von Ihnen vorgeschlagene, also werde ich es so belassen. Vielen Dank!
Mwr247,
Auch für das letzte Beispiel a=>a%2-1funktioniert dies einwandfrei a=>a%2<1.
ETHproductions
1
Ein neuer Trick, den ich gelernt habe: [...Array(x)]funktioniert genauso gut wie Array(x).fill()und ist 2 Bytes kürzer. x=>[...Array(x)].map((a,i)=>i)
ETHproductions
1
@yonatanmn Sehr schön! Nur Kommentare wären 1) das 1/4Beispiel wäre kürzer geschrieben [0,0,0,0]und 2) stringifizierte Funktionen sind implementierungsspezifisch, geben also keine verlässliche Länge zurück ( Map32 Bytes in Chrome, aber 36 Bytes in Firefox).
Mwr247
9

Rückgabe von Werten in Pfeilfunktionen

Es ist allgemein bekannt, dass eine einzelne Anweisung, die auf die Deklaration der Pfeilfunktion folgt, das Ergebnis dieser Anweisung zurückgibt:

a=>{return a+3}
a=>a+3

-7 Bytes

Kombinieren Sie also nach Möglichkeit mehrere Anweisungen zu einer. Dies geschieht am einfachsten, indem die Anweisungen in Klammern eingeschlossen und durch Kommas getrennt werden:

a=>{r=0;a.map(n=>r+=n);return r}
a=>(r=0,a.map(n=>r+=n),r)

-8 Bytes

Wenn es jedoch nur zwei Aussagen gibt, ist es normalerweise möglich (und kürzer), sie mit &&oder zu kombinieren ||:

a=>{r=0;a.map(n=>r+=n);return r}

// - Use && because map always returns an array (true)
// - declaration of r moved into unused map argument to make it only 2 statements
a=>a.map(n=>r+=n,r=0)&&r

-9 Bytes

Wenn Sie eine Karte (oder ähnliches) verwenden und eine Nummer zurückgeben müssen und Sie garantieren können, dass die Karte niemals ein Array mit einer Länge von 1 und einer Nummer zurückgibt, können Sie die Nummer zurückgeben mit |:

a=>{a=b=0;a.map(n=>(a+=n,b-=n));return a/b}

// - {} in map ensures it returns an array of undefined, so the | will make the returned
//   array cast from [ undefined, undefined, undefined ] to ",," to NaN to 0 and 0|n = n,
//   if the map returned [ 4 ] it would cast from [ 4 ] to "4" to 4 and make it 4|n
a=>a.map(n=>{a+=n,b-=n},a=b=0)|a/b
user81655
quelle
Im letzten Beispiel müssen Sie außerdem sicherstellen, dass die Zahl immer eine Ganzzahl ist.
ETHproductions
8

Zufällige Template-String-Hacks

Diese Funktion zerlegt zwei Saiten (dh wird "abc","de"zu "adbec"):

f=(x,y)=>String.raw({raw:x},...y)

Beachten Sie, dass dies nur funktioniert, wenn xlänger als y. Wie funktioniert es, fragst du? String.rawist so konzipiert, dass es ein Template-Tag ist:

String.raw`x: ${x}\ny: ${y}\nx + y: ${x + y}`

Dies nennt man im Grunde String.raw(["x: ", "\ny: ", "\nx + y: ", ""], x, y, x + y), obwohl es nicht so einfach ist. Das Template-Array hat auch eine spezielle rawEigenschaft, die im Grunde genommen eine Kopie des Arrays ist, jedoch mit den Roh-Strings. String.raw(x, ...args)Grundsätzlich kehrt x.raw[0] + args[0] + x.raw[1] + args[1] + x.raw[2] + ...und so weiter, bis xkeine Artikel mehr verfügbar sind.

Nachdem wir nun wissen, wie es String.rawfunktioniert, können wir es zu unserem Vorteil nutzen:

f=(x,y)=>String.raw({raw:x},...y)                   // f("abc", "de") => "adbec"
f=x=>String.raw({raw:x},...[...x].keys())           // f("abc") => "a0b1c"
f=(x,y)=>String.raw({raw:x},...[...x].fill(y))      // f("abc", " ") => "a b c"

Natürlich ist der letzte Teil f=(x,y)=>x.split``.join(y)viel kürzer, aber Sie haben die Idee.

Hier sind ein paar Funktionen von riffling , die auch funktionieren , wenn xund ysind gleich lang:

f=(x,y)=>String.raw({raw:x.match(/.?/g)},...y)
f=(x,y)=>String.raw({raw:x},...y)+y.slice(-1)  // Only works if x.length == y.length

Sie können mehr über String.raw MDN erfahren .

ETHproductions
quelle
7

Golfen mit Rekursion

Die Rekursion ist zwar nicht die schnellste, aber häufig die kürzeste. Im Allgemeinen ist die Rekursion am kürzesten, wenn die Lösung auf einen kleineren Teil der Herausforderung vereinfacht werden kann, insbesondere wenn die Eingabe eine Zahl oder eine Zeichenfolge ist. Wenn zum Beispiel f("abcd")aus "a"und berechnet werden kann f("bcd"), ist es normalerweise am besten, die Rekursion zu verwenden.

Nehmen Sie zum Beispiel Fakultät:

n=>[...Array(n).keys()].reduce((x,y)=>x*++y,1)
n=>[...Array(n)].reduce((x,_,i)=>x*++i,1)
n=>[...Array(n)].reduce(x=>x*n--,1)
n=>{for(t=1;n;)t*=n--;return t}
n=>eval("for(t=1;n;)t*=n--")
f=n=>n?n*f(n-1):1

In diesem Beispiel ist die Rekursion offensichtlich viel kürzer als bei jeder anderen Option.

Wie wäre es mit der Summe der Zeichencodes:

s=>[...s].map(x=>t+=x.charCodeAt(),t=0)|t
s=>[...s].reduce((t,x)=>t+x.charCodeAt())
s=>[for(x of(t=0,s))t+=x.charCodeAt()]|t  // Firefox 30+ only
f=s=>s?s.charCodeAt()+f(s.slice(1)):0

Diese ist kniffliger, aber wir können sehen, dass bei korrekter Implementierung durch Rekursion 4 Bytes eingespart werden .map.

Betrachten wir nun die verschiedenen Arten der Rekursion:

Vorkursion

Dies ist normalerweise die kürzeste Art der Rekursion. Die Eingabe ist in zwei Teile aund aufgeteilt b, und die Funktion berechnet etwas mit aund f(b). Zurück zu unserem Fakultätsbeispiel:

f=n=>n?n*f(n-1):1

In diesem Fall aist n , bist n-1 , und der Wert zurückgegeben wird a*f(b).

Wichtiger Hinweis: Alle rekursiven Funktionen müssen die Möglichkeit haben, die Rekursion zu beenden, wenn die Eingabe klein genug ist. In der Fakultätsfunktion wird dies mit gesteuert n? :1, dh wenn der Eingang 0 ist , wird 1 zurückgegeben, ohne ferneut aufzurufen .

Nachrekursion

Die Nachrekursion ähnelt der Vorrekursion, unterscheidet sich jedoch geringfügig. Der Eingang ist in zwei Teile geteilt aund b, und die Funktion berechnet etwas mit a, dann ruft f(b,a). Das zweite Argument hat normalerweise einen Standardwert (dh f(a,b=1)).

Eine Vorrekursion ist gut, wenn Sie mit dem Endergebnis etwas Besonderes anfangen müssen. Zum Beispiel, wenn Sie die Fakultät einer Zahl plus 1 wollen:

f=(n,p=1)=>n?f(n-1,n*p):p+1

Selbst dann ist post- nicht immer kürzer als die Verwendung der Prärekursion in einer anderen Funktion:

n=>(f=n=>n?n*f(n-1):1)(n)+1

Wann ist es also kürzer? Möglicherweise stellen Sie fest, dass nach der Rekursion in diesem Beispiel Klammern um die Funktionsargumente erforderlich sind, während dies vor der Rekursion nicht der Fall war. Wenn beide Lösungen Klammern um die Argumente benötigen, ist die Nachrekursion im Allgemeinen um 2 Byte kürzer:

n=>!(g=([x,...a])=>a[0]?x-a.pop()+g(a):0)(n)
f=([x,...a],n=0)=>a[0]?f(a,x-a.pop()+n):!n

(Programme hier aus dieser Antwort entnommen )

So finden Sie die kürzeste Lösung

Normalerweise können Sie die kürzeste Methode nur finden, indem Sie alle ausprobieren. Das beinhaltet:

  • Schleifen
  • .map(entweder für Zeichenfolgen [...s].mapoder s.replacefür Zahlen können Sie einen Bereich erstellen. )
  • Array-Verständnis
  • Vorkursion (manchmal innerhalb einer anderen dieser Optionen)
  • Nachrekursion

Und dies sind nur die gebräuchlichsten Lösungen. Die beste Lösung könnte eine Kombination davon sein oder sogar etwas ganz anderes . Der beste Weg, um die kürzeste Lösung zu finden, ist, alles auszuprobieren .

ETHproductions
quelle
1
+1 für seinen Wert, und ich möchte ein weiteres +1 für zootopia hinzufügen
edc65
7

Kürzere Wege .replace


Wenn Sie alle Instanzen einer exakten Teilzeichenfolge durch eine andere in einer Zeichenfolge ersetzen möchten, ist der naheliegende Weg:

f=s=>s.replace(/l/g,"y") // 24 bytes
f("Hello, World!")       // -> "Heyyo, Woryd!"

Sie können jedoch 1 Byte kürzer machen:

f=s=>s.split`l`.join`y`  // 23 bytes
f("Hello, World!")       // -> "Heyyo, Woryd!"

Beachten Sie, dass dies nicht mehr kürzer ist, wenn Sie neben dem gFlag Regex-Funktionen verwenden möchten . Wenn Sie jedoch alle Instanzen einer Variablen ersetzen, ist diese normalerweise viel kürzer:

f=(s,c)=>s.replace(RegExp(c,"g"),"") // 36 bytes
f=(s,c)=>s.split(c).join``           // 26 bytes
f("Hello, World!","l") // -> "Heo, Word!"

Manchmal möchten Sie jedes Zeichen in einer Zeichenfolge abbilden und jedes durch etwas anderes ersetzen. Ich finde mich oft dabei:

f=s=>s.split``.map(x=>x+x).join`` // 33 bytes
f=s=>[...s].map(x=>x+x).join``    // 30 bytes
f("abc") // -> "aabbcc"

Ist .replaceaber fast immer kürzer:

f=s=>s.replace(/./g,x=>x+x)  // 27 bytes
f=s=>s.replace(/./g,"$&$&")  // Also works in this particular case

Wenn Sie nun jedes Zeichen in einer Zeichenfolge abbilden möchten, sich aber nicht für die resultierende Zeichenfolge interessieren, .mapist dies normalerweise besser, da Sie Folgendes entfernen können .join``:

f=s=>s.replace(/./g,x=>t+=+x,t=0)&&t // 36 bytes
f=s=>[...s].map(x=>t+=+x,t=0)&&t     // 32 bytes
f("12345")  // -> 15
ETHproductions
quelle
Für den letzten Fall, wenn nur bestimmte Zeichen, die mit einem regulären Ausdruck (wie /\w/g) übereinstimmen, interessiert sind, ist die Verwendung von "Ersetzen" viel besser als in dieser Demo .
Shieru Asakoto
6

RegEx-Literale schreiben mit eval

Der Regex-Konstruktor kann aufgrund seines langen Namens sehr sperrig sein. Schreiben Sie stattdessen ein Literal mit eval und backticks:

eval(`/<${i} [^>]+/g`)

Wenn die Variable igleich ist foo, generiert dies:

/<foo [^>]+/g

Dies ist gleich:

new RegExp("<"+i+" [^>]+","g")

Sie können auch verwenden String.raw, um zu vermeiden, dass Backslashes wiederholt ausgeblendet werden müssen\

eval(String.raw`/\(?:\d{4})?\d{3}\d{3}\d{3}\d{3}\d{3}\d{3}\d{4}/g`)

Dies wird Folgendes ausgeben:

/(?:\d{4})?\d{3}\d{3}\d{3}/g

Welches ist gleich:

RegExp("\\(?:\\d{4})?\\d{3}\\d{3}\\d{3}\\d{3}\\d{3}\\d{3}\\d{4}","g")

Merken Sie sich!

String.rawnimmt eine Menge Bytes in Anspruch und wird länger sein , es sei denn, Sie haben mindestens neun Backslashes String.raw.

Downgoat
quelle
Sie brauchen das newin dort nicht, so dass die Verwendung des Konstruktors für das zweite Beispiel tatsächlich kürzer ist
Optimizer
5

.forEachvs forSchleifen

Immer .mapeiner for-Schleife vorziehen . Einfache und sofortige Einsparungen.


a.map(f)
for(x of a)f(x);
for(i=0;i<a.length;)f(a[i++]);
  • Insgesamt 8 Byte für das Original
  • 8 Bytes gespart gegen for-of ( 50% Reduktion)
  • 22 Bytes im Vergleich zum C-Stil für die Schleife gespeichert ( 73% Reduzierung)

a.map(x=>f(x,0))
for(x of a)f(x,0);
for(i=0;i<a.length;)f(a[i++],0);
  • Insgesamt 16 Bytes für das Original
  • 2 Bytes gespart gegen for-of ( 11% Reduktion)
  • 16 Bytes im Vergleich zum C-Stil für die Schleife gespeichert ( 50% Reduzierung)

a.map((x,i)=>f(x,i,0))
for(i in a)f(a[i],i,0);
for(i=0;i<a.length;)f(a[i],i++,0);
  • Insgesamt 22 Byte für das Original
  • 1 Byte im Vergleich zum For-In gespeichert ( Reduzierung um 4% )
  • 11 Bytes im Vergleich zum C-Stil für die Schleife gespeichert ( 33% Reduzierung)

a.map(x=>f(x)&g(x))
for(x of a)f(x),g(x);
for(i=0;i<a.length;)f(x=a[i++]),g(x);
  • Insgesamt 19 Byte für das Original
  • 2 Bytes gespart gegen for-of ( 10% Reduktion)
  • 18 Bytes im Vergleich zum C-Stil für die Schleife gespeichert ( 49% Reduzierung)
Isiah Meadows
quelle
5

Verwenden von nicht initialisierten Zählern in der Rekursion

Hinweis : Genau genommen ist dies nicht ES6-spezifisch. Es ist jedoch sinnvoller, die Rekursion in ES6 zu verwenden und zu missbrauchen, da die Pfeilfunktionen prägnant sind.


Es ist eher üblich, auf eine rekursive Funktion zu stoßen, die einen Zähler verwendet, der kanfänglich auf Null gesetzt und bei jeder Iteration inkrementiert wurde:

f = (…, k=0) => [do a recursive call with f(…, k+1)]

Unter bestimmten Umständen ist es möglich , die Initialisierung eines solchen Zählers wegzulassen und ersetzen k+1mit -~k:

f = (…, k) => [do a recursive call with f(…, -~k)]

Dieser Trick spart normalerweise 2 Bytes .

Warum und wann funktioniert es?

Die Formel, die es möglich macht, ist ~undefined === -1. So wird bei der ersten Iteration -~kausgewertet 1. Auf den nächsten Iterationen -~kentspricht im Wesentlichen -(-k-1)die gleich k+1, zumindest für ganze Zahlen im Bereich [0 ... 2 31 -1].

Sie müssen jedoch sicherstellen, dass k = undefinedbei der ersten Iteration das Verhalten der Funktion nicht gestört wird. Beachten Sie insbesondere, dass die meisten arithmetischen Operationen undefineddazu führen werden NaN.

Beispiel 1

Bei einer positiven Ganzzahl nsucht diese Funktion nach der kleinsten Ganzzahl k, die sich nicht teilt n:

f=(n,k=0)=>n%k?k:f(n,k+1)   // 25 bytes

Es kann gekürzt werden auf:

f=(n,k)=>n%k?k:f(n,-~k)     // 23 bytes

Das funktioniert , weil n % undefinedist NaN, was falsy ist. Das ist das erwartete Ergebnis bei der ersten Iteration.

[Link zur ursprünglichen Antwort]

Beispiel # 2

Bei einer positiven Ganzzahl nsucht diese Funktion nach einer Ganzzahl pwie folgt (3**p) - 1 == n:

f=(n,p=0,k=1)=>n<k?n>k-2&&p:f(n,p+1,k*3)  // 40 bytes

Es kann gekürzt werden auf:

f=(n,p,k=1)=>n<k?n>k-2&&p:f(n,-~p,k*3)    // 38 bytes

Dies funktioniert, weil pes bei der ersten Iteration überhaupt nicht verwendet wird ( n<kweil es falsch ist).

[Link zur ursprünglichen Antwort]

Arnauld
quelle
5

ES6 funktioniert

Mathematik

Math.cbrt(x)speichert Zeichen als Math.pow(x,1/3).

Math.cbrt(x)
Math.pow(x,1/3)

3 Zeichen gespeichert

Math.hypot(...args)ist nützlich, wenn Sie die Quadratwurzel der Summe der Quadrate der Args benötigen. Die Erstellung von ES5-Code ist viel schwieriger als die Verwendung eines integrierten Codes.

Die Funktion Math.trunc(x)wäre nicht hilfreich, da sie x|0kürzer ist. (Danke Mwr247!)

Es gibt viele Eigenschaften, für die in ES5 viel Code erforderlich ist, in ES6 ist dies jedoch einfacher:

  • Math.acosh, asinh, atanh, cosh, sinh, tanh. Berechnet das hyperbolische Äquivalent von trigonometrischen Funktionen.
  • Math.clz32. Könnte in ES5 möglich sein, ist aber jetzt einfacher. Zählt führende Nullen in der 32-Bit-Darstellung einer Zahl.

Es gibt viel mehr, so dass ich nur einige gehe zur Liste:
Math.sign, Math.fround, Math.imul, Math.log10, Math.log2, Math.log1p.

ev3commander
quelle
Math.trunc(x)ist viermal länger als x|0.
Mwr247,
@ mwr247: Ok, wird aktualisiert.
ev3commander
Hier sind die kürzesten mir bekannten ES5-Entsprechungen für einige dieser Funktionen: Math.hypot(a,b) => Math.sqrt(a*a+b*b)(3 Byte länger; wird mit mehr Argumenten noch länger) Math.sign(a) => (a>0)-(a<0)(1 Byte kürzer, benötigt aber in einigen Fällen umgebende Klammern; funktioniert möglicherweise nicht NaN)
ETHproductions,
@ETHproductions Sie benötigen das Argumentarray für (die es5-Problemumgehung von) hypot. Und sind Sie sicher, dass die Problemumgehung für Math.sign für -0 funktioniert? (Es sollte -0 zurückgeben)
ev3commander
1
@ ev3commander Diese sind nur als Inline-Ersatz für die entsprechenden ES6-Äquivalente gedacht, sodass sie für 99% der Verwendungszwecke verkleinert werden. Die Neuerstellung dieser Funktionen würde wesentlich mehr Code erfordern. Ich sehe auch keinen Grund, einen speziellen Fall für -0 zu benötigen, da (AFAIK) es keine Möglichkeit gibt, -0 zu erhalten, außer durch manuelles Spezifizieren und praktisch keine Verwendung innerhalb von Code-Golf. Aber danke, dass Sie darauf hingewiesen haben.
ETHproductions
5

Optimierung kleiner konstanter Bereiche für map()

Kontext

map()for[0..N1] , sodass die gesamte Antwort in funktionalem Stil geschrieben werden kann:

for(i = 0; i < 10; i++) {
  do_something_with(i);
}

kann ersetzt werden durch:

[...Array(10).keys()].map(i => do_something_with(i))

oder häufiger:

[...Array(10)].map((_, i) => do_something_with(i))

Array(N)N eine kleine Konstante ist.

[0..N1] mit Zähler

i

N           | Method                               | Example                         | Length
------------+--------------------------------------+---------------------------------+-------
N ≤ 6       | use a raw array of integers          | [0,1,2,3].map(i=>F(i))          | 2N+10
N = 7       | use either a raw array of integers   | [0,1,2,3,4,5,6].map(i=>F(i))    | 24
            | or a string if your code can operate | [...'0123456'].map(i=>F(i))     | 23
            | with characters rather than integers |                                 |
8 ≤ N ≤ 9   | use scientific notation 1e[N-1]      | [...1e7+''].map((_,i)=>F(i))    | 24
N = 10      | use scientific notation 1e9          | [...1e9+''].map((_,i)=>F(i))    | 24
            | or the ES7 expression 2**29+'4' if   | [...2**29+'4'].map(i=>F(i))     | 23
            | the order doesn't matter and your    |                                 |
            | code can operate with characters     |  (order: 5,3,6,8,7,0,9,1,2,4)   |
            | rather than integers                 |                                 |
11 ≤ N ≤ 17 | use scientific notation 1e[N-1]      | [...1e12+''].map((_,i)=>F(i))   | 25
N = 18      | use the fraction 1/3                 | [...1/3+''].map((_,i)=>F(i))    | 24
N = 19      | use the fraction 1/6                 | [...1/6+''].map((_,i)=>F(i))    | 24
20 ≤ N ≤ 21 | use scientific notation 1e[N-1]      | [...1e20+''].map((_,i)=>F(i))   | 25
N = 22      | use scientific notation -1e20        | [...-1e20+''].map((_,i)=>F(i))  | 26
23 ≤ N ≤ 99 | use Array(N)                         | [...Array(23)].map((_,i)=>F(i)) | 27

NB : Die Länge des Rückrufcodes F(i)wird nicht gezählt.

[1..9] mit Zähler

[1..9]

[...17**6+'8'].map(i=>F(i))  // order: 2,4,1,3,7,5,6,9,8; length: 23

Optimierungen ohne Zähler

N

N           | Method                               | Example                         | Length
------------+--------------------------------------+---------------------------------+-------
N ≤ 5       | use a raw array of integers          | [0,0,0,0].map(_=>F())           | 2N+10
6 ≤ N ≤ 10  | use scientific notation 1e[N-1]      | [...1e7+''].map(_=>F())         | 20
11 ≤ N ≤ 17 | use scientific notation 1e[N-1]      | [...1e12+''].map(_=>F())        | 21
N = 18      | use the fraction 1/3                 | [...1/3+''].map(_=>F())         | 20
N = 19      | use the fraction 1/6                 | [...1/6+''].map(_=>F())         | 20
20 ≤ N ≤ 21 | use scientific notation 1e[N-1]      | [...1e20+''].map(_=>F())        | 21
N = 22      | use scientific notation -1e20        | [...-1e20+''].map(_=>F())       | 22
23 ≤ N ≤ 99 | use Array(N)                         | [...Array(23)].map(_=>F())      | 23

NB : Die Länge des Rückrufcodes F()wird nicht gezählt.

Arnauld
quelle
Sollte nicht 2**26sein 2**29?
Shaggy
@ Shaggy Heck. Guter Fang!
Arnauld
Wollte mich nicht bearbeiten, weil ich Code-Blindheit habe! : D
Shaggy
Mit .keys(), brauchen Sie kein Lambda:[...Array(10).keys()].map(do_something_with)
Long-Lazuli
@ Long-Lazuli Wenn Sie kein Lambda brauchen und nur eine Reichweite wollen, dann brauchen Sie wahrscheinlich auch keine Karte ...
Arnauld
4

Aufgaben zerstören

ES6 führt eine neue Syntax ein, um Zuweisungen zu destrukturieren, dh einen Wert in Stücke zu schneiden und jedes Stück einer anderen Variablen zuzuweisen. Hier einige Beispiele:

Streicher und Arrays

a=s[0];b=s[1];       // 14 bytes
[a,b]=s;             //  8 bytes

a=s[0];s=s.slice(1); // 20 bytes
a=s.shift();         // 12 bytes, only works if s is an array
[a,...s]=s;          // 11 bytes, converts s to an array

Objekte

a=o.asdf;b=o.bye;c=o.length; // 28 bytes
{asdf:a,bye:b,length:c}=o;   // 26 bytes

a=o.a;b=o.b;c=o.c; // 18 bytes
{a,b,c}=o;         // 10 bytes

Diese Zuweisungen können auch in Funktionsparametern verwendet werden:

f=a=>a[0]+a[1]+a[2]
f=([a,b,c])=>a+b+c

f=b=>b[1]?b[0]+f(b.slice(1)):b[0]*2
f=b=>b[1]?b.shift()+f(b):b[0]*2
f=([a,...b])=>b[0]?a+f(b):a*2
ETHproductions
quelle
4

Noch ein anderer Weg, um zu vermeiden return

Sie wissen, dass Sie eval für Pfeilfunktionen mit mehreren Anweisungen und einem Return verwenden sollten . In einigen ungewöhnlichen Fällen können Sie mit einer inneren Unterfunktion mehr sparen.

Ich sage ungewöhnlich, weil

  1. Das zurückgegebene Ergebnis darf nicht der letzte in der Schleife ausgewertete Ausdruck sein

  2. Vor der Schleife müssen (mindestens) 2 verschiedene Initialisierungen vorhanden sein

In diesem Fall können Sie eine innere Unterfunktion ohne Rückgabe verwenden, wobei einer der Anfangswerte als Parameter übergeben wird.

Beispiel Ermitteln Sie den Kehrwert der Summe der Exp-Funktionen für Werte in einem Bereich von a bis b.

Der lange Weg - 55 Bytes

(a,b)=>{for(r=0,i=a;i<=b;i++)r+=Math.exp(i);return 1/r}

Mit eval - 54 Bytes

(a,b)=>eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i);1/r")

Mit einer inneren Funktion - 53 Bytes

(a,b)=>(i=>{for(r=0;i<=b;i++)r+=Math.exp(i)})(a)||1/r

Beachten Sie, dass aich ohne die Anforderung einer unteren Bereichsgrenze die Initialisierungen von i und r zusammenführen kann und die Evaluierungsversion kürzer ist.

edc65
quelle
In Ihrer Stichprobe brauchen Sie nicht zu behaltena
l4m2
@ l4m2 Ich kann Ihren Standpunkt nicht verstehen, bitte helfen ...
edc65
(i,b)=>{for(r=0;i<=b;i++)r+=Math.exp(i);return 1/r}
14 m²,
@ l4m2 äh ja, return a/rwäre ein besseres Beispiel
edc65
1
Eval ist immer noch besser (a,b)=>1/eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i)")und in diesem Fall(i,b)=>1/eval("for(r=0;i<=b;)r+=Math.exp(i++)")
JayXon
4

Verwenden der Currying-Syntax für dyadische und rekursive Funktionen

Dyadische Funktionen

Immer wenn eine Funktion genau zwei Argumente ohne Standardwerte verwendet, spart die aktuelle Syntax ein Byte.

Vor

f =
(a,b)=>a+b  // 10 bytes

Angerufen mit f(a,b)

Nach

f =
a=>b=>a+b   // 9 bytes

Angerufen mit f(a)(b)

Hinweis : Dieser Beitrag in Meta bestätigt die Gültigkeit dieser Syntax.

Rekursive Funktionen

Durch die Verwendung der currying-Syntax werden möglicherweise auch einige Bytes gespart, wenn eine rekursive Funktion mehrere Argumente verwendet, diese jedoch nur zwischen den einzelnen Iterationen aktualisiert werden müssen.

Beispiel

Die folgende Funktion berechnet die Summe aller Ganzzahlen im Bereich [a,b]:

f=(a,b)=>a>b?0:b+f(a,b-1)   // 25 bytes

Da dies awährend des gesamten Vorgangs unverändert bleibt, können wir 3 Bytes einsparen, indem wir Folgendes verwenden:

f =                         // no need to include this assignment in the answer anymore
a=>F=b=>a>b?0:b+F(b-1)      // 22 bytes

Anmerkung : Wie Neil in den Kommentaren bemerkt hat, bedeutet die Tatsache, dass ein Argument nicht explizit an die rekursive Funktion übergeben wird, nicht, dass es als unveränderlich angesehen werden sollte. Bei Bedarf könnten wir ändern ainnerhalb des Funktionscode mit a++, a--oder was auch immer ähnlicher Syntax.

Arnauld
quelle
Das letzte Beispiel kann so geschrieben werden a=>F=b=>a>b?0:a+++F(b), dass es afür jeden rekursiven Aufruf geändert wird. In diesem Fall hilft dies nicht, in Fällen mit mehr Argumenten werden jedoch möglicherweise Bytes gespart.
Neil
Heh, ich habe gerade darüber nachgedacht, einen Tipp dafür zu schreiben :-)
ETHproductions
4

Primalitätstestfunktion

Die folgende 28-Byte-Funktion gibt truefür Primzahlen und falsefür Nicht-Primzahlen zurück:

f=(n,x=n)=>n%--x?f(n,x):x==1

Dies kann leicht geändert werden, um andere Dinge zu berechnen. Diese 39-Byte-Funktion zählt beispielsweise die Anzahl der Primzahlen, die kleiner oder gleich einer Zahl sind:

f=(n,x=n)=>n?n%--x?f(n,x):!--x+f(n-1):0

Wenn Sie bereits eine Variable haben n, die Sie auf Primalität prüfen möchten, kann die Primalitätsfunktion erheblich vereinfacht werden:

(f=x=>n%--x?f(x):x==1)(n)

Wie es funktioniert

f = (         // Define a function f with these arguments:
  n,          //   n, the number to test;
  x = n       //   x, with a default value of n, the number to check for divisibility by.
) =>
  n % --x ?   //   If n is not divisible by x - 1,
  f(n, x)     //     return the result of f(n, x - 1).
              //   This loops down through all numbers between n and 0,
              //     stopping when it finds a number that divides n.
  : x == 1    //   Return x == 1; for primes only, 1 is the smallest number
              //     less than n that divides n.
              //   For 1, x == 0; for 0, x == -1.

Hinweis: Bei Aufrufen mit einer ausreichend großen Eingabe, z. B. 12345, schlägt dies mit dem Fehler "zu viel Rekursion" fehl. Sie können dies mit einer Schleife umgehen:

f=n=>eval('for(x=n;n%--x;);x==1')
ETHproductions
quelle
1
Aber scheitern Sie mit zu viel Rekursion für eine Eingabe von nur 12345
edc65
x==1kann wahrscheinlich x<2für Einsparungen sein.
CalculatorFeline
@CalculatorFeline Danke, aber dann scheitert es für 1oder 0(weil xwäre 0oder -1)
ETHproductions
Kann in bestimmten Fällen nützlich sein. Auch !~-xfür -0 Bytes.
CalculatorFeline
3

Array#concat() und der Spread-Operator

Dies hängt weitgehend von der Situation ab.


Mehrere Arrays kombinieren.

Bevorzugen Sie die concat-Funktion, es sei denn, Sie klonen.

0 Bytes gespeichert

a.concat(b)
[...a,...b]

3 Bytes verschwendet

a.concat(b,c)
[...a,...b,...c]

3 Bytes gespeichert

a.concat()
[...a]

6 Bytes gespeichert

// Concatenate array of arrays
[].concat.apply([],l)
[].concat(...l)

Verwenden Sie lieber ein bereits vorhandenes Array als Array#concat().

Einfache 4 Bytes gespeichert

[].concat(a,b)
a.concat(b)
Isiah Meadows
quelle
3

Zwischenergebnis zurückgeben

Sie wissen, dass Sie mit dem Komma-Operator eine Folge von Ausdrücken ausführen können, die den letzten Wert zurückgeben. Wenn Sie jedoch die Literal-Array-Syntax missbrauchen, können Sie einen beliebigen Zwischenwert zurückgeben. Es ist zum Beispiel in .map () nützlich.

// capitalize words
// f is a flag indicating if prev char is space
[...x].map(c=>(f?c=c.toUpperCase():0,f=c<'!',c),f=1).join('')

// shortened to ...
[...x].map(c=>[f?c.toUpperCase():c,f=c<'!'][0],f=1).join('')
edc65
quelle
3
Denken Sie daran, das .join('')kann natürlich sein.join``
Cyoce
3

Standardeinstellungen für Funktionsparameter festlegen

($,a,b,_)=>_!=undefined?'asdf':_ // before
($,a,b,_)=>_!=[]._?'asdf':_ // before, but a bit golfed
($,a,b,_='asdf')=>_ // after

Das ist wirklich nützlich ...

Stellen Sie jedoch sicher, dass so etwas _=>_||'asdf'kürzer ist, wenn Sie nur ein (nützliches) Argument an die Funktion übergeben.

Mama Fun Roll
quelle
1
Ich stelle fest, dass die Verwendung eines OP _=>_||'asdf'in den meisten Fällen kürzer ist
Downgoat
@ Downgoat Ich würde feststellen, dass dies "asdf"für eine Eingabe von ""(leere Zeichenfolge) zurückgibt .
ETHproductions
2
Beachten Sie, dass der Standardwert immer dann ausgewertet wird, wenn das Argument angegeben wurde undefined, auch wenn Sie diesen Wert explizit übergeben. Beispielsweise wird [...Array(n)].map((a,b,c)=>b)immer undefinedals übergeben a, und Sie können daher einen Standardwert dafür angeben (obwohl nicht in Bezug auf b).
Neil
3

Verwenden Sie evalanstelle von geschweiften Klammern Pfeilfunktionen

Pfeilfunktionen sind fantastisch. Sie haben die Form x=>y, in der xein Argument und yder Rückgabewert stehen. Wenn Sie jedoch eine Kontrollstruktur verwenden müssen, wie z. B. while, müssen Sie geschweifte Klammern setzen, z =>{while(){};return}. Wir können dies jedoch umgehen; Zum Glück nimmt die evalFunktion einen String, wertet diesen String als JS-Code aus und gibt den zuletzt ausgewerteten Ausdruck zurück . Vergleichen Sie zum Beispiel diese beiden:

x=>{while(foo){bar};return baz} // before
x=>eval('while(foo){bar};baz')  // after
//                            ^

Wir können eine Erweiterung dieses Konzepts verwenden, um unseren Code weiter zu verkürzen: In den Augen von geben evalKontrollstrukturen auch ihren zuletzt ausgewerteten Ausdruck zurück. Zum Beispiel:

x=>{while(foo)bar++;return bar} // before
x=>eval('while(foo)++bar')      // after
//                        ^^^^^
Cyoce
quelle
3

Golf logische Operationen in ES6

GLOE (S6)

Allgemeine Logik

Angenommen, Sie haben Anweisungen erstellt sund t. Prüfen Sie, ob Sie einen der folgenden Ersatzartikel verwenden können:

Traditional conjuction: s&&t
Equivalent conjuction: s*t OR s&t

Traditional disjunction: s||t
Equivalent disjunction: s+t OR s|t

(Diese können nicht funktionieren , wenn die Reihenfolge falsch ist, dh +und *eine niedrigere Ordnung Vorrang als ||und &&tun.)

Hier sind auch einige nützliche logische Ausdrücke:

  • Entweder soder tist wahr / XOR:s^t
  • sund tsind die gleichen Wahrheitswert: !s^toders==t

Array-Logik

Alle Mitglieder von aerfüllen Bedingung p:

a.every(p)                             // 10 bytes (11 bytes saved)
a.map(x=>c&=p(x),c=1)                  // 21 bytes (16 bytes saved)
for(i=0,c=1;i<a.length;c&=p(a[i++]));  // 37 bytes (hideously long)

Mindestens ein Mitglied aerfüllt die Bedingung p:

a.some(p)                            // 9  bytes (13 bytes saved)
a.map(x=>c|=p(x),c=0)                // 21 bytes (14 bytes saved)
for(i=c=0;i<a.length;c|=p(a[i++]));  // 35 bytes (just please no)

Keine Mitglieder asatisfy Zustand p: !a.some(p).

Element eexistiert im Array a:

a.includes(e)                        // 13 bytes, standard built-in
~a.indexOf(e)                        // 13 bytes, "traditional" method
a.find(x=>e==x)                      // 15 bytes, find (ES6)
a.some(x=>x==e)                      // 15 bytes, some (ES5)
(a+"").search(e)                     // 16 bytes, buggy
a.filter(t=>t==e).length             // 24 bytes, no reason to use this
for(i=c=0;i<a.length;c+=e==a[i++]);  // 35 bytes, super-traditional

Element eist nicht vorhanden in Array a:

!a.includes(e)
!~a.indexOf(e)
a.every(t=>t!=e)
!a.filter(t=>t==e).length
for(i=0,c=1;i<a.length;c*=e!=a[i++]);
Conor O'Brien
quelle
Ich in der Regel verwenden &&und ||wie x?y:xund x?x:ydargestellt. Aber ich kann sehen, wie nützlich dies in logikbasierten Programmen wäre. Das einzige Problem mit +wäre, dass zB 3und -3beide wahr sind, aber 3+-3nicht.
ETHproductions
@ETHproductions Ah, du hast recht; das ist ein Randfall. -könnte auch funktionieren, wenn s != t.
Conor O'Brien
a.filter(t=>t==e).length==a.lengthist falsch. Es sollte sein!a.filter(t=>t==e).length
ETHproductions
@ETHproductions genau das Richtige für Sie!
Conor O'Brien
3

Verkürzen Sie wiederholte Funktionsaufrufe

Wenn Sie eine Funktion mit einem langen Namen wiederholt aufgerufen haben, z. B. Canvas-Manipulation:

c.lineTo(0,100);c.lineTo(100,100);c.lineTo(100,0);c.lineto(0,0);c.stroke()

Die traditionelle Art, es zu verkürzen, wäre der Alias ​​des Funktionsnamens:

c[l='lineTo'](0,100);c[l](100,100);c[l](100,0);c[l](0,0);c.stroke()

Wenn Sie genügend Anrufe haben, ist es besser, eine Funktion zu erstellen, die die Arbeit für Sie erledigt:

l=(x,y)=>c.lineTo(x,y);l(0,100);l(100,100);l(100,0);l(0,0);c.stroke()

Wenn die meisten Funktionsaufrufe verkettet sind, können Sie die Funktion selbst zurückgeben lassen, sodass Sie zwei Bytes von jedem nachfolgenden Aufruf abschneiden können:

l=(x,y)=>c.lineTo(x,y)||l;l(0,100)(100,100)(100,0)(0,0);c.stroke()

Anwendungsbeispiel: 1 , 2

ETHproductions
quelle
1
Sie können mit verkürzen binden Operator :(l=::c.lineTo)(0,100)(100,100)(100,0)(0,0);c.stroke()
Downgoat
@Downgoat Danke, welche Browser unterstützen das? (Auch nach dem, was ich gesehen habe, wird der Fehler beim zweiten Aufruf auftreten, da er c.lineTosich natürlich nicht von selbst
zurückgibt
Sie müssen es durch babel reiben, da es ein ES7-Feature ist
Downgoat
3

Operator binden ::

Der Bindeoperator kann verwendet werden, um Bytes bei wiederholten Funktionen zu verkürzen:

(x='abc'.search(a))+x.search(b) // Before
(x=::'abc'.search)(a)+x(b)      // 5 bytes saved

Zusätzlich, wenn Sie die Funktion mit einem anderen verwenden möchten, thiszB:

s[r='replace'](/a/g,'b')+s[r](/c/g,'d') // Before
(r=s.replace)(/a/g,'b')+s::r(/c/g,'d')  // 1 byte saved
Downgoat
quelle
3

Vermeiden Sie Kommas, wenn Sie viele Daten speichern

Wenn Sie viele Daten (z. B. Indizes, Zeichen usw.) in einem Array speichern müssen, sollten Sie möglicherweise alle Kommas weglassen. Dies funktioniert am besten, wenn jedes Datenelement dieselbe Zeichenfolgenlänge hat, wobei 1 offensichtlich optimal ist.

43 Bytes (Grundlinie)

a=[[3,7,6,1,8,9,4,5,2],[5,4,3,2,7,6,5,4,3]]

34 Bytes (keine Kommas)

a=[[..."376189452"],[..."543276543"]]

Wenn Sie Ihren Array-Zugriff ändern möchten , können Sie diesen möglicherweise noch weiter reduzieren, indem Sie dieselben Werte wie folgt speichern:

27 Bytes (gleiche Daten, ändert nur den Array-Zugriff)

a=[..."376189452543276543"]
Chiru
quelle
Warum wird nur der letzte Block markiert?
CalculatorFeline
@CalculatorFeline Danke, behoben.
Chiru