Wskazówki dotyczące gry w golfa w ECMAScript 6 i nowszych


88

Jest to podobne do innych „Porad do gry w golfa w <...>”, ale w szczególności dotyczy nowych funkcji JavaScript włączonych w ECMAScript 6 i nowszych.

JavaScript jest językiem natury bardzo gadatliwy, function(){}, .forEach(), przekształcając ciąg do tablicy, tablicy podobny obiekt do tablicy, etc, etc są super bloats i nie zdrowe dla golfa.

Z drugiej strony ES6 + ma kilka bardzo przydatnych funkcji i ma mniejszą powierzchnię. x=>y, [...x]itp. to tylko niektóre przykłady.

Proszę zamieścić kilka fajnych sztuczek, które mogą pomóc w usunięciu kilku dodatkowych bajtów z twojego kodu.

UWAGA: Triki dla ES5 są już dostępne w Poradach dotyczących gry w golfa w JavaScript ; odpowiedzi na ten wątek powinny koncentrować się na sztuczkach dostępnych tylko w ES6 i innych przyszłych wersjach ES.

Jednak ten wątek jest również dla użytkowników, którzy obecnie grają w golfa za pomocą funkcji ES5. Odpowiedzi mogą również zawierać wskazówki, które pomogą im zrozumieć i odwzorować funkcje ES6 na ich styl kodowania ES5.

Odpowiedzi:


42

Operator rozkładania ...

Operator rozkładania przekształca wartość tablicy w listę oddzieloną przecinkami.

Przypadek użycia 1:

Bezpośrednio użyj tablicy, w której funkcja oczekuje listy

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

Przypadek użycia 2:

Utwórz literał tablicowy z iterowalnego (zazwyczaj ciąg)

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

Przypadek użycia 3:

Zadeklaruj zmienną liczbę argumentów dla funkcji

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

Zobacz mozilla doc


3
Teraz mam tutaj opinię. Najwyraźniej ktoś zauważył coś strasznie złego w tym
typie

Wygląda dobrze. Może to był brak średników? ;) (btw, możesz go również użyć jako parametrów odpoczynku, takich jak ikony w Ruby)
gcampbell

Można dodać, że ma również przypadek użycia w sygnaturach funkcji :)
Felix Dombek

Misclick nie miał zamiaru głosować negatywnie
Stan Strum

@StanStrum to się zdarza. Zrobię małą aktualizację tego postu, abyś mógł ostatecznie zmienić swój głos (czy już to zrobiłeś?)
edc65

21

Sztuczki nauczyłem się tutaj, odkąd dołączyłem

Moim podstawowym językiem programowania jest JS i głównie ES6. Odkąd dołączyłem do tej witryny tydzień temu, nauczyłem się wielu przydatnych sztuczek od innych członków. Łączę niektóre z nich tutaj. Wszystkie kredyty dla społeczności.

Funkcje strzałek i pętle

Wszyscy wiemy, że funkcje strzałek pozwalają zaoszczędzić wiele bajtów

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

Ale musisz pamiętać o kilku rzeczach

  • Spróbuj połączyć wiele instrukcji za pomocą ,np. (a=b,a.map(d))- Tutaj zwracana wartość jest ostatnim wyrażeniema.map(d)
  • jeśli twoja do somethingczęść jest więcej niż jedną instrukcją, musisz dodać otaczające {}nawiasy.
  • Jeśli istnieją {}nawiasy otaczające , musisz dodać jawną instrukcję return.

Powyższe sprawdza się wiele razy, gdy masz zaangażowane pętle. Więc coś takiego:

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]}

Tutaj marnuję co najmniej 9 znaków z powodu zwrotu. Można to zoptymalizować.

  • Staraj się unikać pętli. Użyj .maplub .everylub .somezamiast. Pamiętaj, że jeśli chcesz zmienić tę samą tablicę, na której mapujesz, to się nie powiedzie.
  • Zawiń pętlę w funkcję strzałki zamknięcia, przekształcając główną funkcję strzałki jako pojedynczą instrukcję.

Tak więc powyższe staje się:

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]

usunięte postacie: {}return

dodane znaki: (){}>|

Zwróć uwagę, jak wywołuję metodę zamknięcia, która poprawnie wypełnia zmienną, na następnie, ponieważ metoda zamknięcia nic nie zwraca (tj. Zwraca undefined), bitowo lub to i zwracam tablicę nw jednym poleceniu funkcji zewnętrznej strzałkiu

Przecinki i średniki

Unikaj ich co kiedykolwiek,

Jeśli deklarujesz zmienne w pętli lub, jak wspomniano w poprzedniej sekcji, używając ,oddzielnych instrukcji, aby mieć funkcje strzałek pojedynczej instrukcji, możesz użyć całkiem sprytnych sztuczek, aby ich uniknąć ,lub ;ogolić kilka ostatnich bajtów.

Rozważ ten kod:

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

Wzywam tutaj wiele metod inicjowania wielu zmiennych. Każda inicjalizacja używa znaku ,lub ;. Można to przepisać jako:

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

Zauważ, że wykorzystuję fakt, że metoda nie przeszkadza przekazanej mu zmiennej, i używam tego faktu do golenia 3 bajtów.

różne

.search zamiast .indexOf

Oba dają ten sam wynik, ale searchsą krótsze. Chociaż wyszukiwanie oczekuje wyrażenia regularnego, używaj go mądrze.

`Ciągi szablonów`

Są one bardzo przydatne, gdy trzeba połączyć jedną lub więcej części struny na podstawie określonych warunków.

Weź poniższy przykład, aby wyprowadzić quine w JS

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

vs.

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

W ciągu szablonu, który jest ciągiem wewnątrz dwóch cudzysłowów (`), wszystko wewnątrz a ${ }jest traktowane jako kod i analizowane pod kątem wstawienia wynikowej odpowiedzi do ciągu.

Później dodam kilka trików. Miłej gry w golfa!


1
.search jest krótszy, użyj go, jeśli to możliwe! ale to nie to samo co .indexOf. .search chce, a regexpnie łańcucha. Spróbuj'abc'.search('.')
edc65,

@ edc65 Zaktualizowano!
Optymalizator

Możesz zmodyfikować oryginalną tablicę za pomocą metod instancji. Drugi to bieżący indeks, a trzeci to iterowana tablica.
Isiah Meadows

8
„Dołączył do strony tydzień temu” - 21,4 tys. Rep ...
GamrCorps

2
Oprócz tego .maprekurencja jest inną techniką, która może czasem pomóc ci zmienić forpętlę w wyrażenie.
Neil

20

Używanie skrótów własności

Skrócone właściwości pozwalają ustawić zmienne na wartości tablic:

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

Można to również wykorzystać jako:

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

Możesz nawet użyć tego do odwrócenia zmiennych:

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

Możesz go również użyć do skrócenia slice()funkcji.

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

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

Podstawowe konwersje

ES6 zapewnia znacznie krótszy sposób konwersji formatu Base-2 (binarny) i Base-8 (ósemkowy) na dziesiętny:

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

+może być użyty do konwersji ciągu dwójkowego, ósemkowego lub szesnastkowego na liczbę dziesiętną. Można użyć 0b, 0oi 0xna binarny, ósemkowy i hex odpowiednio .:

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

Jeśli używasz tego> 7 razy, użycie parseInti zmiana nazwy będzie krótsza :

(p=parseInt)(v,2)

Teraz pmożna go wykorzystać do parseIntoszczędzania wielu bajtów na dłuższą metę.


Podstawowa sztuczka konwersji jest dobra, ale bardziej prawdopodobne jest, że liczba konwersji będzie miała postać zmiennej zamiast literału, w którym to przypadku będzie znacznie dłuższa.
Optymalizator

1
'0x'+v-0jest jeszcze krótszy, ale może nie działać tak dobrze w niektórych scenariuszach.
ETHproductions

1
Nawiasem mówiąc, 0767(ES5) jest krótszy niż 0o767notacja (ES6).
Camilo Martin,

@CamiloMartin 0767jest niestandardowym rozszerzeniem i jest zabronione w trybie ścisłym.
Oriol,

1
Tryb ścisły @Oriol był złym memem. Nie poprawiło to wydajności, nie zmusiło cię do napisania dobrego kodu i nigdy nie stałoby się domyślnym. 0-fabrykowane literały ósemkowe nigdzie nie idą i są tak samo poprawne jak ekmascript 0o.
Camilo Martin

19

Używanie szablonów ciągów z funkcjami

Gdy masz funkcję z jednym ciągiem jako argumentami. Możesz pominąć, ()jeśli nie masz żadnych wyrażeń:

join`` // Works
join`foobar` // Works
join`${5}` // Doesn't work 

9
Ostrzegam, to faktycznie przekazuje tablicę. fun`string` jest taki sam jak fun(["string"])nie fun("string"). Jest to odpowiednie dla funkcji rzutujących na ciąg znaków, takich jak alert, ale dla innych może to powodować problemy. Aby uzyskać więcej informacji, zobacz artykuł MDN
Cyoce

5
Krótka informacja: fun`foo${1}bar${2}bazjest odpowiednikiem dzwonieniafun(["foo","bar","baz"],1,2)
Cyoce

14

Zrozumienie tablic (Firefox 30-57)

Uwaga: interpretacje tablic nigdy nie były znormalizowane i stały się przestarzałe w Firefoksie 58. Używaj na własne ryzyko.


Początkowo specyfikacja ECMAScript 7 zawierała wiele nowych funkcji opartych na macierzy. Chociaż większość z nich nie dotarła do wersji finalnej, obsługa Firefoksa (ed) prawdopodobnie największa z tych funkcji: wymyślna nowa składnia, która może zastąpić .filteri .mapze for(a of b)składnią. Oto przykład:

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

Jak widać, dwie linie nie różnią się od siebie tak bardzo, że druga nie zawiera nieporęcznych słów kluczowych i funkcji strzałek. Ale to tylko uwzględnia zamówienie .filter().map(); co się stanie, jeśli .map().filter()zamiast tego? To zależy od sytuacji:

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]

Albo co jeśli chcesz albo .map albo .filter ? Cóż, zwykle okazuje się mniej OK:

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]

Więc moja rada jest użycie składni dla tablic gdziekolwiek byś zwykle używają .map i .filter , ale nie tylko jedną lub drugą stronę.

Rozumienie ciągów

Zaletą w zrozumieniu ES7 jest to, że w przeciwieństwie do funkcji specyficznych dla tablicy, takich jak .mapi .filter, można ich używać na dowolnym obiekcie iterowalnym, nie tylko tablicach. Jest to szczególnie przydatne, gdy mamy do czynienia z łańcuchami. Na przykład, jeśli chcesz uruchomić każdy znak cw ciągu c.charCodeAt():

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

To dwa bajty zapisane na dość małą skalę. A co jeśli chcesz filtrować określone znaki w ciągu? Na przykład ten zawiera tylko wielkie litery:

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

Hmm, to nie jest krótsze. Ale jeśli połączymy dwa:

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

Wow, zapisano całe 10 bajtów!

Kolejną zaletą ciągów znaków jest to, że ciągi zakodowane na stałe oszczędzają dodatkowy bajt, ponieważ można pominąć spację po 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]

Indeksowanie

Zrozumienie tablic utrudnia uzyskanie bieżącego indeksu w ciągu / tablicy, ale można to zrobić:

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

Najważniejsze, aby zachować ostrożność, aby upewnić się, że indeks jest zwiększany za każdym razem, a nie tylko wtedy, gdy spełniony jest warunek.

Zrozumienie generatora

Zrozumienia generatora mają zasadniczo tę samą składnię, co zeznania tablicowe; wystarczy zastąpić nawiasy klamrowe:

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

Tworzy to generator, który działa w taki sam sposób jak tablica, ale to historia na inną odpowiedź.

Podsumowanie

Zasadniczo, chociaż rozumienie jest zwykle krótsze niż .map().filter()wszystko, wszystko sprowadza się do specyfiki sytuacji. Najlepiej jest wypróbować to na dwa sposoby i sprawdzić, które z nich działa lepiej.

PS Nie wahaj się zasugerować inną wskazówkę dotyczącą zrozumienia lub sposób na poprawienie tej odpowiedzi!


Oto sztuczka dla zakresów, która pozwoli zaoszczędzić kilka dodatkowych znaków:(x,y)=>[...Array(y-x)].map(a=>x++)
Mwr247,

2
Możesz odciąć kolejne 11 bajtów, aby uzyskać zakres od 0 do x:x=>[...Array(x).keys()]
Mwr247,

Ostatni do zrozumienia: n=>[for(x of Array(n).keys())if(/1/.test(x))x](zapisuje 7 bajtów)
Mwr247,

@ Mwr247 Właściwie widzę teraz, że zakresy zwykle nie są tak krótkie ze zrozumieniem, jak w przypadku innych fajnych funkcji ES6. Zamiast tego dodam sekcję o ciągach i pozwolę ci obsługiwać zakresy.
ETHproductions

Warto zauważyć, że Zrozumienia macierzy zostały wycofane i usunięte ze wszystkich najnowszych wersji javascript. Zobacz dokumenty MDN na ten temat.
Keefer Rourke

13

Wyrażenia funkcyjne w ES6 używają notacji ze strzałką i pomaga dużo zaoszczędzić bajty w porównaniu z wersją ES5:

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

Jeśli twoja funkcja ma tylko jeden parametr, możesz pominąć nawiasy, aby zapisać dwa bajty:

f=x=>x+1

Jeśli twoja funkcja w ogóle nie ma parametrów, zadeklaruj ją tak, jakby miała jeden, aby zapisać jeden bajt:

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

Uwaga: funkcje strzałek nie są dokładnie takie same jak function () {}. Zasady thissą różne (i lepiej IMO). Zobacz dokumenty


2
Ale kiedy grasz w golfa, na ogół nie przejmujesz się thisitp.
Optymalizator

1
Zasadniczo nie, ale jest to zastrzeżenie, którego możesz nigdy nie wiedzieć, kiedy się pojawi. Bardziej powszechne jest również, że lambdas nie potrzebują lokalnego wiązania funkcji podczas produkcji.
Isiah Meadows

Ponadto, jeśli chcesz wziąć wszystkie swoje argumenty, możesz użyć funkcji argumentu „reszta”, np. f=(...x)=>x Miałbyś to f(1,2,3) => [1,2,3].
Conor O'Brien

1
Oto wskazówka dotycząca tej strony: jeśli odpowiadasz za pomocą funkcji, która przyjmuje postać (x,y)=>..., możesz zapisać bajt z curry , zastępując gox=>y=>...
Cyoce

12

Używanie evalfunkcji strzałek z wieloma instrukcjami ireturn

Jedna z bardziej absurdalnych sztuczek, na które natknąłem się ...

Wyobraź sobie prostą funkcję strzałki, która wymaga wielu instrukcji i a return.

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

Prosta funkcja akceptująca pojedynczy parametr a, który iteruje po wszystkich liczbach całkowitych [0, a), i umieszcza je na końcu ozwracanego ciągu wyjściowego . Na przykład wywołanie tego z 4parametrem dałoby wynik 0123.

Zauważ, że ta funkcja strzałki musiała być owinięta w nawiasy klamrowe {}i mieć return ona końcu znak .

Ta pierwsza próba waży 39 bajtów .

Nieźle, ale za pomocą evalmożemy to poprawić.

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

Ta funkcja usunęła nawiasy klamrowe i instrukcję return, zawijając kod w evali po prostu zmieniając ostatnią instrukcję w evalocenę o. Powoduje evalto zwrócenie o, co z kolei powoduje zwrócenie funkcji o, ponieważ jest to teraz pojedyncza instrukcja.

Ta ulepszona próba waży 38 bajtów , oszczędzając jeden bajt od oryginału.

Ale czekaj, jest więcej! Instrukcje ewaluacyjne zwracają to, do czego ostatnio oceniane. W tym przypadku o+=iocenia się o, więc nie potrzebujemy ;o! (Dzięki, edc65!)

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

Ta ostatnia próba waży tylko 36 bajtów - 3 bajty oszczędności w stosunku do oryginału!


Technikę tę można rozszerzyć na każdy ogólny przypadek, w którym funkcja strzałki musi zwrócić wartość i mieć wiele instrukcji (których nie można połączyć innymi metodami)

b=>{statement1;statement2;return v}

staje się

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

zapisywanie bajtu.

Jeśli statement2oceni to v, może to być

b=>eval('statement1;statement2')

oszczędzając w sumie 3 bajty.


1
Myślę, że samo napisanie anonimowej funkcji może być jeszcze krótsze
Downgoat

@vihan tak, obie te funkcje można uczynić anonimowymi, aby zapisać 2 bajty każda. Jednobajtowe oszczędności nadal jednak pozostają.
jrich

1
Ale jeszcze lepiej: eval zwraca ostatnie ocenione wyrażenie, więc nie potrzebujesz ;o- spróbuj:a=>eval('for(o="",i=0;i<a;i++)o+=i')
edc65

4
Ale ciągi szablonów!
Conor O'Brien

1
@ CᴏɴᴏʀO'Bʀɪᴇɴ Chcesz wyjaśnić, jak działałyby tutaj ciągi szablonów, używając przykładowej funkcji jako kontekstu?
WallyWest,

10

Preferuj ciąg nowych linii szablonu zamiast „\ n”

Zacznie się to opłacać nawet w jednym nowym znaku wiersza w kodzie. Jednym przypadkiem użycia może być:

(16 bajtów)

array.join("\n")

(15 bajtów)

array.join(`
`)

Aktualizacja: Możesz nawet zostawić nawiasy klamrowe z powodu oznakowanych ciągów szablonów (dzięki, edc65!):

(13 bajtów)

array.join`
`

5
Ale jeszcze lepiej, możesz uniknąć nawiasu. Przeczytaj dokumentację ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ), aby zobaczyć, dlaczego
edc65

Ah, tak. Dzięki, dodałem to.
Chiru,

9

Tablice wypełniania - wartości statyczne i zakresy dynamiczne

Pierwotnie pozostawiłem je jako komentarze pod zrozumieniem, ale ponieważ ten post był przede wszystkim poświęcony zrozumieniu, pomyślałem, że dobrze byłoby nadać temu swoje miejsce.

ES6 dał nam możliwość wypełniania tablic wartościami statycznymi bez użycia pętli:

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

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

Oba zwracają tablicę o długości x, wypełnioną wartością 0.

Jeśli jednak chcesz wypełnić tablice wartościami dynamicznymi (np. Zakres od 0 ... x), wynik jest nieco dłuższy (choć nadal krótszy niż w starym przypadku):

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

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

Oba zwracają tablicę o długości x, zaczynając od wartości 0 i kończąc na x-1.

Powodem tego .fill()jest to, że po prostu inicjowanie tablicy nie pozwoli jej zmapować. To znaczy, że wykonanie x=>Array(x).map((a,i)=>i)zwróci pustą tablicę. Możesz także ominąć potrzebę wypełniania (a tym samym jeszcze go skrócić), używając operatora rozrzucania w następujący sposób:

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

Za pomocą operatora i .keys()funkcji rozrzucania możesz teraz utworzyć krótki zakres 0 ... x:

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

Jeśli chcesz mieć zakres niestandardowy od x ... y lub całkowicie zakres specjalistyczny (taki jak liczby parzyste), możesz się go pozbyć .keys()i po prostu użyć .map(), lub użyć .filter(), za pomocą operatora rozkładania:

// 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)

Oto sugestia dla drugiego przykładu: x=>Array(x).fill(i=0).map(a=>i++)Nie jestem też pewien, czy 0 w .fill(0)jest konieczne. Próbowałeś tego bez?
ETHproductions

@ETHproductions Masz rację, zapomniałem, że 0 nie jest potrzebne do wypełnienia przed mapą. To sprawia, że ​​jest ona o 1 postać krótsza niż sugerowana, więc zachowam ją w ten sposób. Dzięki!
Mwr247,

Również w ostatnim przykładzie a=>a%2-1działa dobrze, podobnie jak a=>a%2<1.
ETHprodukcje

1
Nowa sztuczka, której się nauczyłem: [...Array(x)]działa równie dobrze Array(x).fill()i jest o 2 bajty krótsza. x=>[...Array(x)].map((a,i)=>i)
ETHproductions

1
@yonatanmn Very nice! Tylko komentarze byłyby 1) 1/4przykład byłby krótszy na piśmie [0,0,0,0], a 2) funkcje łańcuchowe są specyficzne dla implementacji, więc nie zwrócą wiarygodnej długości ( Map32 bajty w Chrome, ale 36 bajtów w Firefox).
Mwr247

9

Zwracanie wartości w funkcjach strzałek

Powszechnie wiadomo, że jeśli pojedyncza instrukcja występuje po deklaracji funkcji strzałki, zwraca wynik tej instrukcji:

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

-7 bajtów

Jeśli to możliwe, połącz wiele instrukcji w jedną. Najłatwiej to zrobić, otaczając instrukcje nawiasami i oddzielając je przecinkami:

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

-8 bajtów

Ale jeśli są tylko dwie instrukcje, zwykle jest możliwe (i krótsze) połączenie ich z &&lub ||:

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 bajtów

Wreszcie, jeśli korzystasz z mapy (lub podobnej) i chcesz zwrócić liczbę, a masz gwarancję, że mapa nigdy nie zwróci tablicy o długości 1 z liczbą, możesz zwrócić liczbę za pomocą |:

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

W tym ostatnim przykładzie musisz również upewnić się, że liczba będzie zawsze liczbą całkowitą.
ETHprodukcje

8

Losowe włamania do szablonów

Ta funkcja riffuje dwa ciągi (tzn. Zamienia się "abc","de"w "adbec"):

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

Pamiętaj, że działa to tylko wtedy, gdy xjest dłuższy niż y. Jak to działa, pytasz? String.rawjest zaprojektowany jako tag szablonu, taki jak:

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

To w zasadzie wymaga String.raw(["x: ", "\ny: ", "\nx + y: ", ""], x, y, x + y), choć nie jest to takie proste. Tablica szablonów ma również specjalną rawwłaściwość, która jest w zasadzie kopią tablicy, ale z nieprzetworzonymi łańcuchami. String.raw(x, ...args)w zasadzie wraca x.raw[0] + args[0] + x.raw[1] + args[1] + x.raw[2] + ...i tak dalej, aż xzabraknie przedmiotów.

Teraz, gdy wiemy, jak String.rawdziała, możemy go wykorzystać na naszą korzyść:

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"

Oczywiście ten ostatni f=(x,y)=>x.split``.join(y)jest o wiele krótszy, ale masz pomysł.

Oto kilka riffling funkcje, które również działają jeśli xi ysą równej długości:

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

Możesz dowiedzieć się więcej String.raw na temat MDN .


7

Jak grać w golfa z rekurencją

Rekurencja, choć nie najszybsza, jest często najkrótsza. Zasadniczo rekurencja jest najkrótsza, jeśli rozwiązanie można uprościć do rozwiązania mniejszej części wyzwania, szczególnie jeśli dane wejściowe to liczba lub ciąg znaków. Na przykład, jeśli f("abcd")można to obliczyć na podstawie "a"i f("bcd"), zwykle najlepiej jest użyć rekurencji.

Weźmy na przykład silnię:

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

W tym przykładzie rekurencja jest oczywiście znacznie krótsza niż jakakolwiek inna opcja.

Co powiesz na sumę znaków:

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

Ten jest trudniejszy, ale widzimy, że przy prawidłowej implementacji rekursja oszczędza 4 bajty .map.

Teraz spójrzmy na różne typy rekurencji:

Rekurencja wstępna

Zazwyczaj jest to najkrótszy typ rekurencji. Wejście jest podzielona na dwie części ai b, a funkcja oblicza coś z ai f(b). Wracając do naszego silnego przykładu:

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

W tym przypadku, ato n , bjest n-1 , a wartość wynosi a*f(b).

Ważna uwaga: wszystkie funkcje rekurencyjne muszą mieć sposób na zatrzymanie rekurencji, gdy dane wejściowe są wystarczająco małe. W funkcji silniakowej jest to kontrolowane za pomocą n? :1, tzn. Jeśli wejście ma wartość 0 , zwraca 1 bez fponownego wywoływania .

Po rekursji

Rekurencja jest podobna do rekurencji, ale nieco inna. Wejście jest podzielona na dwie części ai b, a funkcja oblicza coś z a, a następnie zwraca f(b,a). Drugi argument zwykle ma wartość domyślną (tj f(a,b=1).).

Rekurencja wstępna jest dobra, gdy trzeba zrobić coś specjalnego z końcowym rezultatem. Na przykład, jeśli chcesz silnię liczby plus 1:

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

Jednak nawet wtedy post- nie zawsze jest krótszy niż użycie rekursji wstępnej w ramach innej funkcji:

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

Więc kiedy jest krótszy? W tym przykładzie można zauważyć, że rekursja po rekurencji wymaga nawiasów wokół argumentów funkcji, a rekursja wstępna nie. Zasadniczo, jeśli oba rozwiązania wymagają nawiasów wokół argumentów, rekursja po rekurencji jest o około 2 bajty krótsza:

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

(programy pochodzą z tej odpowiedzi )

Jak znaleźć najkrótsze rozwiązanie

Zwykle jedynym sposobem na znalezienie najkrótszej metody jest wypróbowanie wszystkich. To zawiera:

  • Pętle
  • .map(dla ciągów albo [...s].mapalbo s.replace; dla liczb możesz utworzyć zakres )
  • Zrozumienia tablic
  • Rekurencja wstępna (czasami w ramach innej z tych opcji)
  • Po rekursji

A to tylko najczęstsze rozwiązania; najlepszym rozwiązaniem może być ich połączenie, a nawet coś zupełnie innego . Najlepszym sposobem na znalezienie najkrótszego rozwiązania jest wypróbowanie wszystkiego .


1
+1 za swoją wartość, i chciałbym dodać kolejne +1 za zootopię
edc65,

7

Krótsze sposoby na zrobienie tego .replace


Jeśli chcesz zastąpić wszystkie wystąpienia jednego dokładnego podłańcucha innym ciągiem, oczywistym sposobem byłoby:

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

Możesz jednak zrobić 1 bajt krótszy:

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

Pamiętaj, że nie jest to już krótsze, jeśli chcesz korzystać z funkcji wyrażenia regularnego oprócz gflagi. Jeśli jednak zastępujesz wszystkie wystąpienia zmiennej, zwykle jest ona znacznie krótsza:

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!"

Czasami będziesz chciał zamapować każdy znak w ciągu, zastępując każdy inny czymś innym. Często robię to:

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

Jednak .replaceprawie zawsze jest krótszy:

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

Teraz, jeśli chcesz zmapować każdy znak w ciągu, ale nie przejmujesz się wynikowym ciągiem, .mapzwykle lepiej, ponieważ możesz pozbyć się .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

W ostatnim przypadku, jeśli zainteresowane są tylko niektóre znaki pasujące do wyrażenia regularnego (jak /\w/g), to użycie zamiany będzie znacznie lepsze niż w tej wersji demonstracyjnej .
Shieru Asakoto

6

Pisanie literałów RegEx za pomocą eval

Konstruktor wyrażeń regularnych może być bardzo nieporęczny z powodu swojej długiej nazwy. Zamiast tego napisz literał z eval i backticks:

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

Jeśli zmienna ijest równa foo, wygeneruje:

/<foo [^>]+/g

Jest to równe:

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

Możesz także użyć, String.rawaby uniknąć konieczności wielokrotnego unikania ukośników odwrotnych\

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

Spowoduje to wygenerowanie:

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

Co jest równe:

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

Pamiętać!

String.rawzajmuje dużo bajtów i jeśli nie masz przynajmniej dziewięciu odwrotnych ukośników, String.rawbędzie dłuższy.


Nie potrzebujesz newtam, więc użycie konstruktora jest w rzeczywistości krótsze w drugim przykładzie
Optymalizator

5

.forEachvs forpętle

Zawsze wolę .mapdowolną z pętli for. Łatwe, natychmiastowe oszczędności.


a.map(f)
for(x of a)f(x);
for(i=0;i<a.length;)f(a[i++]);
  • Łącznie 8 bajtów dla oryginału
  • 8 bajtów zapisanych w porównaniu do przed ( 50% redukcji)
  • 22 bajty zapisane w stosunku do pętli w stylu C ( redukcja 73% )

a.map(x=>f(x,0))
for(x of a)f(x,0);
for(i=0;i<a.length;)f(a[i++],0);
  • 16 bajtów ogółem dla oryginału
  • 2 bajty zapisane w porównaniu do for-of ( redukcja 11% )
  • 16 bajtów zapisanych w stosunku do pętli w stylu C ( redukcja 50% )

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);
  • 22 bajty ogółem dla oryginału
  • 1 bajt zapisany w porównaniu z wprowadzeniem ( redukcja 4% )
  • 11 bajtów zapisanych w stosunku do pętli w stylu C ( redukcja 33% )

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);
  • Łącznie 19 bajtów dla oryginału
  • 2 bajty zapisane w porównaniu do for-of ( 10% redukcji)
  • 18 bajtów zapisanych w stosunku do pętli w stylu C ( redukcja 49% )

5

Używanie niezainicjowanych liczników w rekurencji

Uwaga : Ściśle mówiąc, nie dotyczy to ES6. Jednak bardziej sensowne jest używanie i nadużywanie rekurencji w ES6, ze względu na zwięzły charakter funkcji strzałek.


Często spotyka się funkcję rekurencyjną, która używa licznika kpoczątkowo ustawionego na zero i zwiększanego przy każdej iteracji:

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

W pewnych okolicznościach można pominąć inicjalizację takiego licznika i zastąpić k+1go -~k:

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

Ta sztuczka zwykle oszczędza 2 bajty .

Dlaczego i kiedy to działa?

Formuła, która to umożliwia, to ~undefined === -1. Tak więc podczas pierwszej iteracji -~kzostanie oceniony jako 1. W następnych iteracjach -~kjest zasadniczo równoważny temu, -(-k-1)co jest równe k+1, co najmniej dla liczb całkowitych z zakresu [0… 2 31 -1].

Musisz jednak upewnić się, że k = undefinedwykonanie pierwszej iteracji nie zakłóci działania funkcji. Należy szczególnie pamiętać, że większość operacji arytmetycznych undefinedprzyniesie to skutek NaN.

Przykład 1

Biorąc pod uwagę dodatnią liczbę całkowitą n, ta funkcja szuka najmniejszej liczby całkowitej k, która się nie dzieli n:

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

Można go skrócić do:

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

Działa n % undefinedto NaN, ponieważ jest , co jest fałszem. To oczekiwany wynik przy pierwszej iteracji.

[Link do oryginalnej odpowiedzi]

Przykład nr 2

Biorąc pod uwagę dodatnią liczbę całkowitą n, ta funkcja szuka liczby całkowitej ptakiej, że (3**p) - 1 == n:

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

Można go skrócić do:

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

Działa pto, ponieważ w ogóle nie jest używane przy pierwszej iteracji ( n<kfałsz).

[Link do oryginalnej odpowiedzi]


5

Funkcje ES6

Matematyka

Math.cbrt(x)zapisuje znaki niż Math.pow(x,1/3).

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

3 znaki zapisane

Math.hypot(...args)przydaje się, gdy potrzebujesz pierwiastka kwadratowego z sumy kwadratów argumentów. Wykonanie tego kodu ES5 jest o wiele trudniejsze niż użycie wbudowanego.

Funkcja Math.trunc(x)nie byłaby pomocna, ponieważ x|0jest krótsza. (Dzięki Mwr247!)

Istnieje wiele właściwości, które wymagają dużo kodu w ES5, ale łatwiej w ES6:

  • Math.acosh, asinh, atanh, cosh, sinh, tanh. Oblicza hiperboliczny odpowiednik funkcji trygonometrycznych.
  • Math.clz32. Może być to możliwe w ES5, ale teraz jest łatwiejsze. Zlicza zera wiodące w 32-bitowej reprezentacji liczby.

Istnieje wiele więcej, więc mam zamiar wymienić tylko kilka:
Math.sign, Math.fround, Math.imul, Math.log10, Math.log2, Math.log1p.


Math.trunc(x)jest czterokrotnie dłuższy niż x|0.
Mwr247,

@ mwr247: Ok, zaktualizuje się.
ev3commander

Oto najkrótsze odpowiedniki ES5, jakie znam dla kilku z tych funkcji: Math.hypot(a,b) => Math.sqrt(a*a+b*b)(3 bajty dłużej; staje się jeszcze dłuższy z większą liczbą argumentów), Math.sign(a) => (a>0)-(a<0)(1 bajt krótszy, ale w niektórych przypadkach potrzebuje otaczających nawiasów; może nie współpracować NaN)
ETHproductions

@ETHproductions Potrzebujesz tablicy argumentów dla (hipoteza ES5). I czy jesteś pewien, że obejście dla Math.sign działa dla -0? (Powinien zwrócić -0)
dowódca ev3,

1
@ ev3commander Są to po prostu zamienniki dla odpowiednich odpowiedników ES6, więc są zmniejszane do 99% zastosowań. Prawdziwe odtworzenie tych funkcji wymagałoby znacznie więcej kodu. Poza tym nie widzę powodu, dla którego trzeba mieć specjalny przypadek dla -0, ponieważ (AFAIK) nie ma sposobu na uzyskanie -0 poza ręcznym określeniem go i praktycznie bezużyteczne w przypadku gry w golfa kodowego. Ale dzięki za wskazanie tych rzeczy.
ETHprodukcje

5

Optymalizacja małych stałych zakresów dla map()

Kontekst

map()for[0..N1]

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

można zastąpić:

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

lub częściej:

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

Array(N)N

[0..N1]

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

Uwaga : Długość kodu wywołania zwrotnego F(i)nie jest liczona.

[1..9]

[1..9]

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

Optymalizacje bez licznika

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

Uwaga : Długość kodu wywołania zwrotnego F()nie jest liczona.


Nie powinno 2**26być 2**29?
Shaggy

@Shaggy Heck. Dobry chwyt!
Arnauld,

Nie chciałem edytować w sobie, bo mam ślepotę na kody! : D
Shaggy,

Używając .keys(), nie potrzebujesz lambda:[...Array(10).keys()].map(do_something_with)
long-lazuli

@ long-lazuli Jeśli nie potrzebujesz lambda i po prostu chcesz zasięg, prawdopodobnie nie potrzebujesz mapy ...
Arnauld

4

Zadania związane z restrukturyzacją

ES6 wprowadza nową składnię przypisań destrukcyjnych, tj. Wycinanie wartości na części i przypisywanie każdej części do innej zmiennej. Oto kilka przykładów:

Ciągi i tablice

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

Obiekty

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

Te przypisania można również wykorzystać w parametrach funkcji:

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

4

Jeszcze inny sposób na uniknięcie return

Wiesz, że powinieneś używać eval do funkcji strzałek z wieloma instrukcjami i zwrotem . W niektórych nietypowych przypadkach możesz zaoszczędzić więcej za pomocą wewnętrznej podfunkcji.

Mówię niezwykłe, ponieważ

  1. Zwrócony wynik nie może być ostatnim wyrażeniem ocenianym w pętli

  2. Przed pętlą muszą być (co najmniej) 2 różne inicjalizacje

W takim przypadku możesz użyć wewnętrznej podfunkcji bez powrotu, mając jedną z wartości początkowych przekazaną jako parametr.

Przykład Znajdź odwrotność sumy funkcji exp dla wartości w zakresie od a do b.

Długa droga - 55 bajtów

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

Z eval - 54 bajty

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

Z funkcją wewnętrzną - 53 bajty

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

Zauważ, że bez wymogu dolnej granicy zakresu a, mogę scalić inicjalizacje i i r, a wersja eval jest krótsza.


W twojej próbce nie musisz zachowywaća
l4m2 13.04.18

@ l4m2 Nie mogę dostać twojego punktu, pomóż proszę ...
edc65

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

@ l4m2 uh racja, return a/rbyłby lepszy przykład
edc65

1
eval jest jeszcze lepszy, (a,b)=>1/eval("for(r=0,i=a;i<=b;i++)r+=Math.exp(i)")w tym przypadku(i,b)=>1/eval("for(r=0;i<=b;)r+=Math.exp(i++)")
JayXon

4

Używanie składni curry do funkcji diadycznych i rekurencyjnych

Funkcje dyadyczne

Ilekroć funkcja pobiera dokładnie dwa argumenty bez wartości domyślnych, użycie składni curry pozwala zaoszczędzić jeden bajt.

Przed

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

Zadzwoniłem z f(a,b)

Po

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

Zadzwoniłem z f(a)(b)

Uwaga : ten post w Meta potwierdza poprawność tej składni.

Funkcje rekurencyjne

Użycie składni curry może również zaoszczędzić niektóre bajty, gdy funkcja rekurencyjna pobiera kilka argumentów, ale wystarczy zaktualizować niektóre z nich między każdą iteracją.

Przykład

Poniższa funkcja oblicza sumę wszystkich liczb całkowitych w zakresie [a,b]:

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

Ponieważ apozostaje niezmieniony podczas całego procesu, możemy zapisać 3 bajty, używając:

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

Uwaga : Jak zauważył Neil w komentarzach, fakt, że argument nie jest jawnie przekazywany funkcji rekurencyjnej, nie oznacza, że ​​należy ją uznać za niezmienną. W razie potrzeby, możemy modyfikować aw kodzie funkcji z a++, a--czy cokolwiek podobnego składnia.


Ostatni przykład można zapisać jako a=>F=b=>a>b?0:a+++F(b), modyfikując adla każdego wywołania rekurencyjnego. To nie pomaga w takim przypadku, ale może zaoszczędzić bajty w przypadkach z większą liczbą argumentów.
Neil,

Heh, właśnie myślałem o napisaniu napiwku na ten temat :-)
ETHproductions

4

Funkcja testowania pierwotności

Zwraca następującą funkcję 28-bajtową truedla liczb pierwszych i falsedla liczb niepierwszych:

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

Można to łatwo zmodyfikować, aby obliczyć inne rzeczy. Na przykład ta 39-bajtowa funkcja liczy liczbę liczb pierwszych mniejszą lub równą liczbie:

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

Jeśli masz już zmienną n, którą chcesz sprawdzić pod kątem pierwotności, funkcję pierwotności można nieco uprościć:

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

Jak to działa

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.

Uwaga: nie powiedzie się to z powodu błędu „zbyt dużej rekurencji”, gdy zostanie wywołany z odpowiednio dużym wejściem, takim jak 12345. Można to obejść za pomocą pętli:

f=n=>eval('for(x=n;n%--x;);x==1')

1
Ale zawodzi przy zbyt dużej rekurencji dla danych wejściowych tak małych, jak 12345
edc65,

x==1prawdopodobnie może być x<2dla oszczędności.
CalculatorFeline

@CalculatorFeline Dzięki, ale potem kończy się niepowodzeniem dla 1lub 0(ponieważ xbyłoby odpowiednio 0lub -1)
ETHproductions

Może być przydatny w niektórych przypadkach. Również !~-xdla -0 bajtów.
CalculatorFeline

3

Array#concat() i operator spreadu

Zależy to w dużej mierze od sytuacji.


Łączenie wielu tablic.

Preferuj funkcję concat, chyba że klonujesz.

Zapisano 0 bajtów

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

3 bajty zmarnowane

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

Zapisano 3 bajty

a.concat()
[...a]

Zapisano 6 bajtów

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

Preferuj użycie już istniejącej tablicy do Array#concat().

Łatwe 4 bajty zapisane

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

3

Zwraca wynik pośredni

Wiesz, że za pomocą operatora przecinka możesz wykonać sekwencję wyrażeń zwracających ostatnią wartość. Ale nadużywając literalnej składni tablicowej, możesz zwrócić dowolną wartość pośrednią. Jest to przydatne na przykład w .map ().

// 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('')

3
Pamiętaj oczywiście, że .join('')może to być.join``
Cyoce

3

Ustaw wartości domyślne parametrów funkcji

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

Ten jest naprawdę przydatny ...

Pamiętaj jednak, aby zrozumieć, że coś takiego _=>_||'asdf'jest krótsze, gdy przekazujesz tylko jeden (użyteczny) argument do funkcji.


1
Chciałbym zauważyć, że użycie OR _=>_||'asdf'jest zwykle krótsze w większości przypadków
Downgoat

@Downgoat Zauważyłem, że zwraca "asdf"wartość wejściową ""(pusty ciąg).
ETHproductions

2
Zauważ, że wartość domyślna jest oceniana za każdym razem, gdy argument byłby undefined, nawet jeśli jawnie przekażesz tę wartość. Na przykład [...Array(n)].map((a,b,c)=>b)zawsze przechodzi undefinedna a, dlatego możesz podać dla niego wartość domyślną (choć nie w kategoriach b).
Neil

3

Użyj evalzamiast nawiasów klamrowych dla funkcji strzałek

Funkcje strzałek są niesamowite. Przyjmują formę x=>y, gdzie xjest argumentem i ywartością zwracaną. Jeśli jednak chcesz użyć struktury kontrolnej, takiej jak np. while, Musisz wstawić nawiasy klamrowe, np =>{while(){};return}. Możemy jednak obejść ten problem; na szczęście evalfunkcja pobiera ciąg, ocenia ten ciąg jako kod JS i zwraca ostatnio ocenione wyrażenie . Na przykład porównaj te dwa:

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

Możemy użyć rozszerzenia tej koncepcji, aby jeszcze bardziej skrócić nasz kod: w oczach evalstruktur kontrolnych zwraca również ostatnio ocenione wyrażenie. Na przykład:

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

3

Gra logiczna w golfa w ES6

„GLOE (S6)”

Ogólna logika

Załóżmy, że zbudowałeś oświadczenia si t. Sprawdź, czy możesz użyć jednego z następujących zamienników:

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

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

(Mogą nie działać, jeśli zamówienie jest źle, to znaczy +i *mają pierwszeństwo zamówienia mniejszą niż ||i&& robić).

Oto kilka przydatnych wyrażeń logicznych:

  • Albo salbo tjest prawdą / XOR:s^t
  • si tmają tę samą wartość prawdy: !s^tlubs==t

Logika tablicowa

Wszyscy członkowie aspełniają warunek 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)

Co najmniej jeden członek aspełnia warunek 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)

Brak członków aspełniających warunek p:!a.some(p) .

Element eistnieje w tablicy 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 ema nie istnieć w tablicy 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++]);

I zazwyczaj używają &&i ||jak x?y:xi x?x:y, odpowiednio. Ale widzę, jak byłoby to użyteczne w programach opartych na logice. Jedyny problem +to, że na przykład 3i -3to zarówno truthy, ale 3+-3nie jest.
ETHproductions

@ETHproductions Ach, masz rację; to jest przypadek na krawędzi. -może również działać, jeśli s != t.
Conor O'Brien

a.filter(t=>t==e).length==a.lengthjest nieprawidłowe. Powinno być!a.filter(t=>t==e).length
ETHproductions

@ETHproductions masz rację!
Conor O'Brien

3

Skróć powtarzane wywołania funkcji

Jeśli powtórzono wywołania funkcji o długiej nazwie, takiej jak manipulacja na kanwie:

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

Tradycyjnym sposobem skrócenia tego byłoby alias nazwy funkcji:

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

Jeśli masz wystarczającą liczbę połączeń, lepszym sposobem jest utworzenie funkcji, która wykona zadanie za Ciebie:

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

Jeśli większość wywołań funkcji jest połączonych w łańcuch, możesz sprawić, że funkcja sama się zwróci, co pozwoli ci odciąć dwa bajty z każdego kolejnego wywołania:

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

Przykładowe użycie: 1 , 2


1
można skrócić za pomocą operatora(l=::c.lineTo)(0,100)(100,100)(100,0)(0,0);c.stroke()
bindowania

@Downgoat Dzięki, które przeglądarki obsługują to? (Również z tego, co widziałem, wystąpi błąd przy drugim wywołaniu, ponieważ c.lineTonaturalnie się nie zwraca)
ETHproductions

musisz przecierać go przez babel, ponieważ jest to funkcja
ES7

3

Operator powiązania ::

Za pomocą operatora powiązania można skrócić bajty w stosunku do powtarzających się funkcji:

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

Dodatkowo, jeśli chcesz użyć funkcji z innymi thisnp .:

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

3

Unikanie przecinków podczas przechowywania dużej ilości danych

Jeśli masz dużo danych (tj. Indeksów, znaków itp.), Które musisz przechowywać w tablicy, lepiej zostawić wszystkie przecinki. Działa to najlepiej, jeśli każdy kawałek danych ma tę samą długość łańcucha, przy czym 1 jest oczywiście optymalny.

43 bajtów (linia bazowa)

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

34 bajty (bez przecinków)

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

Jeśli chcesz zmienić dostęp do tablicy , możesz to jeszcze bardziej zmniejszyć, przechowując takie same wartości:

27 bajtów (te same dane, zmienia tylko dostęp do tablicy)

a=[..."376189452543276543"]

Dlaczego podświetlony jest tylko ostatni blok?
CalculatorFeline

@CalculatorFeline Dzięki, naprawiono.
Chiru,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.