Jakie masz ogólne wskazówki na temat gry w golfa w Haskell? Szukam pomysłów, które można by zastosować do problemów z golfem w kodzie, które są przynajmniej nieco specyficzne dla Haskell. Proszę zamieścić tylko jedną wskazówkę na odpowiedź.
Jeśli dopiero zaczynasz grę w golfa w Haskell, zapoznaj się z Przewodnikiem po zasadach gry w golfa w Haskell . Istnieje również dedykowany czat Haskell: Of Monads and Men .
Odpowiedzi:
Zdefiniuj operatory infix zamiast funkcji binarnych
To oszczędza zwykle jedną lub dwie spacje na definicję lub wywołanie.
vs.
Symbole dostępne dla operatorów 1 bajt jest
!
,#
,%
,&
i?
. Wszystkie inne znaki interpunkcyjne ASCII są już zdefiniowane jako Preludium (takie jak$
) lub mają specjalne znaczenie w składni Haskella (takie jak@
).Jeśli potrzebujesz więcej niż pięciu operatorów, możesz użyć kombinacji powyższych, takich jak
!#
niektóre znaki interpunkcyjne Unicode, takie jak te (wszystkie 2 bajty w UTF-8):źródło
(x!y)z=x+y*z
i(x#y)z u=x*z+y*u
oba działają zgodnie z oczekiwaniami.\f g(!)x y->f g!x y
Zamiast\f g j x y->j(f g)(x y)
g x=…;g(f x)
jest dłuższe niż_?x=…;0!f x
W razie potrzeby używaj bezcelowej (lub -free) notacji
Często funkcję z jednym lub dwoma parametrami można zapisać bez punktów.
Zatem lista krotek, których elementy są zamieniane, jest naiwnie zapisana jako:
(ten typ ma na celu pomóc ci zrozumieć, co robi).
dla naszych celów jest to o wiele lepsze:
źródło
Użyj listy monad
Szybki przegląd:
Przykłady:
Powtarzanie listy dwa razy
Krótszy
concatMap
Krótsze
concat
+ zrozumienie listyProdukt kartezjański
Lista współrzędnych na siatce
źródło
[0..b]>>[a]
zamiastreplicate a b
.a<$[1..b]
jest jeszcze krótszyreplicate
.=<<
zmusza do importuControl.Monad
. Jeśli nie potrzebujesz tego z innego powodu, zamiana argumentów i używanie>>=
wydaje się bardziej zwięzłe.Data.Traversable
tak potrzebujesz , przykład produktu kartezjańskiego można skrócićfor["Hh","io",".!"]id
.(=<<)
tak naprawdę jest w Preludium ! Dużo z tego korzystałem.Używaj osłon nie warunkowych:
Używaj średników, a nie wcięć
Użyj funkcji boolowskich dla funkcji boolowskich
(SO przeszkadza mi, żebym mógł opublikować je osobno)
źródło
&&
trakcie przeglądania listy.True
=>1>0
f a=if a>0 then 3 else 7
interact :: (String → String) → IO ()
Ludzie często zapominają, że ta funkcja istnieje - pobiera wszystkie standardowe wejścia i stosuje ją do funkcji (czystej). Często widzę
main
-code wzdłuż liniipodczas
jest nieco krótszy. Jest w Preludium, więc nie ma potrzeby importowania!
źródło
Użyj GHC 7.10
Pierwsza wersja GHC zawierająca te rzeczy została wydana 27 marca 2015 r .
Jest to najnowsza wersja, a Prelude ma kilka nowych dodatków, które są przydatne do gry w golfa:
(<$>)
I(<*>)
operatorzyPrzydali się ci przydatni operatorzy
Data.Applicative
!<$>
jest po prostufmap
, więc można zastąpićmap f x
ifmap f x
zef<$>x
wszędzie i odzyskać bajtów. Jest także<*>
przydatny wApplicative
przypadku list:(<$)
operatorax<$a
jest równoważne zfmap (const x) a
; tzn. zamień każdy element w pojemniku nax
.Jest to często miła alternatywa dla
replicate
:4<$[1..n]
jest krótsza niżreplicate n 4
.Propozycja składana / przejezdna
Następujące funkcje zostały przeniesione z pracy na listach
[a]
do ogólnychFoldable
typówt a
:Oznacza to, że teraz pracują również
Maybe a
tam, gdzie zachowują się jak „listy zawierające co najwyżej jeden element”. Na przykładnull Nothing == True
, lubsum (Just 3) == 3
. Podobnielength
zwraca 0 dlaNothing
i 1 dlaJust
wartości. Zamiast pisaćx==Just y
możesz pisaćelem y x
.Możesz także zastosować je na krotkach, co działa tak, jakbyś zadzwonił jako
\(a, b) -> [b]
pierwszy. Jest prawie całkowicie bezużyteczny, aleor :: (a, Bool) -> Bool
jest o jedną postać krótszy niżsnd
ielem b
jest krótszy niż(==b).snd
.Funkcje Monoid
mempty
imappend
Nie często ratuje życie, ale jeśli możesz wywnioskować, jaki
mempty
jest typ, jest on o jeden bajt krótszy niżNothing
, więc jest taki.źródło
<*>
uczynieniu go Preludium! To powinno się przydać, nawet jeśli nie jest to golf golfowy (tak długie słowo ma zastosowanie).[1..2]
. to po prostu[1,2]
<*
odApplicative
, który jest na liściexs <* ys == concatMap (replicate (length ys)) xs
. To różni się odxs >> ys
lubxs *> ys
które jestconcat (replicate (length ys)) xs
.pure
który jest krótszy, równieżreturn
w tym momencie.<>
zamiastmappend
, jest to teraz (z GHC 8.4.1) częśćPrelude
.Użyj
1<2
zamiastTrue
i1>2
zamiastFalse
.źródło
f=max 10
.if(true)
w innych językach. w preludium, w przeciwnym razie jest to wartość boolowskaTrue
.otherwise
.Używaj wyrażeń listowych (w sprytny sposób)
Wszyscy wiedzą, że są użyteczną składnią, często krótszą niż
map
+ lambda:Lub
filter
(i opcjonalniemap
w tym samym czasie):Ale od czasu do czasu przydają się dziwniejsze zastosowania. Po pierwsze, lista nie musi zawierać żadnych
<-
strzałek:Co oznacza
if p then[x]else[]
, że możesz pisać[x|p]
. Ponadto, aby policzyć liczbę elementów listy spełniających warunek, intuicyjnie napisałbyś:Ale to jest krótsze:
źródło
Znać twój
Prelude
Odpal GHCi i przewiń dokumentację Preludium . Za każdym razem, gdy przechodzisz przez funkcję o krótkiej nazwie, warto zwrócić uwagę na niektóre przypadki, w których może być przydatna.
Na przykład, załóżmy, że chcesz, aby przekształcić ciąg
s = "abc\ndef\nghi"
w jedno miejsce, które jest oddzielone,"abc def ghi"
. Oczywistym sposobem jest:Ale możesz zrobić lepiej, jeśli nadużyjesz
max
, a fakt, że\n < space < printable ASCII
:Innym przykładem jest
lex :: String -> [(String, String)]
coś tajemniczego:Spróbuj
fst=<<lex s
uzyskać pierwszy token z ciągu, pomijając białe znaki. Tutaj jest rozwiązanie przez henkma mądry, który używalex.show
naRational
wartości.źródło
Dopasuj stałą wartość
Zrozumienie listy może dopasowywać wzór na stałe.
To wyodrębnia zera z listy
l
, tzn. Tworzy listę tylu zer, ile jestl
.To tworzy listę tylu
1
, ile jest elementów,l
któref
przechodzą na pustą listę (używając<$>
jako infixmap
). Zastosuj,sum
aby policzyć te elementy.Porównać:
Stała może być używana jako część dopasowania wzorca. To wyodrębnia drugie wpisy ze wszystkich krotek, których pierwszym wpisem jest
0
.Zauważ, że wszystkie z nich wymagają rzeczywistego stałego literału, a nie wartości zmiennej. Na przykład
let x=1 in [1|x<-[1,2,3]]
wyświetli[1,1,1]
, a nie[1]
, ponieważ zewnętrznex
wiązanie zostanie zastąpione.źródło
Użyj
words
zamiast długiej listy ciągów. To nie jest tak naprawdę specyficzne dla Haskell, inne języki też mają podobne sztuczki.źródło
Poznaj swoje funkcje monadyczne
1)
symulować funkcje monadyczne za pomocą
mapM
.kod będzie miał wiele razy
sequence(map f xs)
, ale można go zastąpićmapM f xs
. nawet gdy używa sięsequence
go sam, jest to dłuższemapM id
.2)
łącz funkcje za pomocą
(>>=)
(lub(=<<)
)wersja monad funkcji
(>>=)
jest zdefiniowana w następujący sposób:może być przydatny do tworzenia funkcji, których nie można wyrazić jako potok. na przykład
\x->x==nub x
jest dłuższy niżnub>>=(==)
i\t->zip(tail t)t
jest dłuższy niżtail>>=zip
.źródło
Applicative
ale nieMonad
ma też implementacji,pure
która jest krótsza niżconst
i faktycznie pomogła mi wcześniej.Argumenty mogą być krótsze niż definicje
Właśnie zostałem bardzo ciekawie wyrzucony przez henkma .
Jeśli funkcja pomocnicza
f
w Twojej odpowiedzi używa operatora, który nie jest używany w innym miejscu w Twojej odpowiedzi if
jest wywoływany jeden raz, uczyń go argumentemf
.To:
Czy dwa bajty są dłuższe niż to:
źródło
Użyj operatora minus (:)
podczas łączenia list, jeśli pierwszy ma długość 1, użyj
:
zamiast niego.źródło
1:2:3:x
Zamiast[1,2,3]++x
.Nie używaj backsick zbyt często. Backticks to fajne narzędzie do tworzenia sekcji funkcji prefiksów, ale czasami może być niewłaściwie używane.
Kiedyś widziałem, jak ktoś pisze to podwyrażenie:
Chociaż jest tak samo jak tylko
v x
.Innym przykładem jest pisanie
(x+1)`div`y
w przeciwieństwie dodiv(x+1)y
.Widzę to się stało dookoła
div
ielem
częściej, ponieważ funkcje te są zwykle używane jako wrostkiem w regularnych kodu.źródło
Użyj strażników wzorów
Są krótsze niż a
let
lub lambda, które dekonstruują argumenty funkcji, którą definiujesz. To pomaga, gdy trzeba coś podobnegofromJust
zData.Maybe
:jest dłuższy niż
jest dłuższy niż
jest dłuższy niż
W rzeczywistości są krótsze, nawet jeśli wiążą zwykłą starą wartość zamiast dekonstruować: patrz wskazówka xnor .
źródło
e
tak naprawdę nie jest to jeden token, ale dłuższe wyrażenie, które potrzebuje$
przed nim, co zwykle ma miejsce.Krótszy warunkowy
jest równa
Oto jak to działa:
źródło
if b then y else x
?bool
krótszego, ponieważ nie potrzebujesz zrozumienia listyPraca ze znakiem minus
Znak minus
-
jest irytującym wyjątkiem od wielu reguł składniowych. Ta wskazówka zawiera kilka krótkich sposobów wyrażania negacji i odejmowania w Haskell. Daj mi znać, jeśli coś przeoczyłem.Negacja
e
, po prostu zrób-e
. Na przykład-length[1,2]
daje-2
.e
jest nawet umiarkowanie skomplikowany, będziesz potrzebować nawiasówe
, ale zwykle możesz zaoszczędzić bajt, przesuwając je:-length(take 3 x)
jest krótszy niż-(length$take 3 x)
.e
jest poprzedzony przez=
lub operator poprawek o trwałości mniejszej niż 6, potrzebujesz spacji:f= -2
określaf
ik< -2
sprawdza, czyk
jest mniejsza niż-2
. Jeśli trwałość wynosi 6 lub więcej, potrzebujesz parens:2^^(-2)
daje0.25
. Zwykle możesz zmienić układ rzeczy, aby się ich pozbyć: na przykład rób-k>2
zamiastk< -2
.!
jest operatorem, to-a!b
jest analizowane tak,(-a)!b
jakby poprawność!
wynosi co najwyżej 6 (-1<1
daje toTrue
), a-(a!b)
poza tym (-[1,2]!!0
daje-1
). Domyślna poprawność zdefiniowanych przez użytkownika operatorów i funkcji zaznaczania wstecznego wynosi 9, więc są zgodne z drugą zasadą.map
itp.), Skorzystaj z sekcji(0-)
.Odejmowanie
k
, użyj sekcji(-k+)
, która dodaje-k
.k
może być nawet dość złożonym wyrażeniem:(-2*length x+)
działa zgodnie z oczekiwaniami.pred
zamiast tego, chyba że wymagałoby to spacji po obu stronach. Jest to rzadkie i zwykle dzieje się zuntil
funkcją zdefiniowaną przez użytkownika lub, ponieważmap pred x
może być zastąpione przezpred<$>x
iiterate pred x
przez[x,x-1..]
. A jeśli maszf pred x
gdzieś, prawdopodobnie if
tak powinieneś zdefiniować funkcję infix. Zobacz tę wskazówkę .źródło
Spróbuj zmienić definicje funkcji i / lub argumentów
Czasami można zapisać kilka bajtów, zmieniając kolejność dopasowywania wzorców w definicji funkcji. Oszczędności te są tanie, ale łatwe do przeoczenia.
Jako przykład rozważ następującą wcześniejszą wersję (części) tej odpowiedzi :
Jest to rekurencyjna definicja
?
, przy czym podstawowym przypadkiem jest pusta lista. Ponieważ[]
nie jest to przydatna wartość, powinniśmy zamienić definicje i zastąpić je_
znakiem zastępczym lub fikcyjnym argumentemy
, zapisując bajt:Na podstawie tej samej odpowiedzi rozważ tę definicję:
Pusta lista występuje w wartości zwracanej, dzięki czemu możemy zapisać dwa bajty, zamieniając przypadki:
Również kolejność argumentów funkcji może czasem mieć znaczenie, umożliwiając usunięcie niepotrzebnych białych znaków. Rozważ wcześniejszą wersję tej odpowiedzi :
Jest przykry kawał spacji pomiędzy
h
ip
w pierwszej gałęzi. Możemy się go pozbyć, definiująch a p q
zamiasth p q a
:źródło
Nie marnuj strażnika „inaczej”
Ostateczna osłona, która jest funkcją catch-all
True
(krótsza jako1>0
), może zostać użyta do powiązania zmiennej. Porównać:Ponieważ strażnik jest obowiązkowy i w inny sposób zmarnowany, niewiele potrzeba, aby było warto. Wystarczy zapisać parę parenów lub powiązać wyrażenie długości 3, które jest używane dwukrotnie. Czasami możesz zanegować strażników, aby ostatecznym przypadkiem było wyrażenie, które najlepiej używa wiązania.
źródło
Użyj
,
zamiast&&
w osłonachWiele warunków w osłonie, które wszyscy muszą trzymać, można łączyć
,
zamiast&&
.źródło
f xs m | [x] <- xs, Just y <- m, x > 3 = y
Krótsza składnia lokalnych deklaracji
Czasami musisz zdefiniować lokalną funkcję lub operator, ale kosztuje to wiele bajtów do napisania
where
lublet…in
podniesienia do najwyższego poziomu poprzez dodanie dodatkowych argumentów.Na szczęście Haskell ma mylącą i rzadko używaną, ale dość zwięzłą składnię dla lokalnych deklaracji :
W tym przypadku:
Możesz użyć tej składni z deklaracjami złożonymi z wielu instrukcji lub z wieloma deklaracjami, a nawet zagnieżdża:
Działa również w przypadku wiązania zmiennych lub innych wzorców, chociaż osłony wzorców są do tego krótsze, chyba że masz również funkcje wiązania.
źródło
[f 1|let f x=x+1]
.Uniknąć
repeat n
Każde z tych czterech wyrażeń spowoduje utworzenie nieskończonej listy
n
.To bardzo konkretna wskazówka, ale może zaoszczędzić do 3 bajtów!
źródło
n
jest globalny,l=n:l;l
ma tę samą długość i działa dla (niektórych) dłuższych wyrażeń. (Może jednak wymagać spacji).Krótsze warunki warunkowe, gdy jednym wynikiem jest pusta lista
Gdy potrzebujesz warunku, który zwraca listę
A
lub pustą listę w[]
zależności od niektórych warunkówC
, wówczas istnieje kilka krótszych alternatyw dla zwykłych konstrukcji warunkowych:Przykłady: 1 , 2
źródło
A
i[]
włączony.*>
ma wyższą trwałość niż>>
(wciąż nieco niski komfort).Zasady analizy lambda
Wyrażenie lambda tak naprawdę nie potrzebuje nawiasów wokół niego - po prostu łapczywie chwyta wszystko, więc całość wciąż się analizuje, np.
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
napotkano i istnieją dziwne przypadki, w których można zaoszczędzić bajt lub dwa. Sądzę, że
\
można go również użyć do zdefiniowania operatorów, więc podczas korzystania z tego będziesz potrzebował spacji, pisząc lambda bezpośrednio za operatorem (jak w trzecim przykładzie).Oto przykład, w którym użycie lambda było najkrótszą rzeczą, jaką mogłem wymyślić. Kod zasadniczo wygląda następująco:
źródło
Zamień
let
na lambdaZwykle może to skrócić pojedynczą definicję pomocniczą, która z jakiegoś powodu nie może być związana ze strażnikiem ani zdefiniowana globalnie. Na przykład zamień
o 3 bajty krótsze
W przypadku wielu definicji pomocniczych wzmocnienie jest prawdopodobnie mniejsze, w zależności od liczby definicji.
Jeśli niektóre definicje odnoszą się do innych, jeszcze trudniej jest zapisać bajty w ten sposób:
Głównym zastrzeżeniem jest to, że
let
pozwala definiować zmienne polimorficzne, ale lambdy nie, jak zauważają @ChristianSievers. Na przykład,powoduje
(1,1)
, aledaje błąd typu.
źródło
let
, więc możemy to zrobićlet f=id in (f 0,f True)
. Jeśli spróbujemy przepisać to za pomocą lambda, nie będzie to sprawdzać.Powiąż za pomocą strażników
Podczas definiowania nazwanej funkcji można powiązać wyrażenie ze zmienną w osłonie. Na przykład,
robi to samo co
Użyj tego, aby zaoszczędzić na powtarzanych wyrażeniach. Gdy wyrażenie jest używane dwukrotnie, łamie się ono nawet przy długości 6, chociaż problemy z odstępami i pierwszeństwem mogą to zmienić.
(W tym przykładzie, jeśli pierwotna zmienna
s
nie jest używana, jest to krótszeale nie dotyczy to wiązania bardziej złożonych wyrażeń).
źródło
Just
przykład przypomniał mi, że dopasowanie wzorca polega na wydobyciu z kontenera, a nie zapisaniu wyrażenia.Użyj
(0<$)
zamiastlength
do porównańPodczas testowania, czy lista
a
jest dłuższa niż listab
, zwykle pisze sięJednak zastąpienie każdego elementu obu list tą samą wartością, np.
0
A następnie porównanie tych dwóch list może być krótsze:Wypróbuj online!
Nawias są potrzebne, bo
<$
i operatorów porównania (==
,>
,<=
, ...) mają ten sam poziom pierwszeństwo 4, chociaż w niektórych innych przypadkach może nie być potrzebne, oszczędzając nawet więcej bajtów.źródło
Krótszy
transpose
Aby skorzystać z
transpose
funkcjiData.List
, należy ją zaimportować. Jeśli jest to jedyna funkcja wymagająca importu, można zapisać bajt, stosując następującąfoldr
definicjętranspose
:Zauważ, że zachowanie jest identyczne tylko dla listy list o tej samej długości.
Z powodzeniem wykorzystałem to tutaj .
źródło
Uzyskaj sufiksy
Użyj,
scanr(:)[]
aby uzyskać sufiksy listy:To jest znacznie krótsze niż
tails
późniejimport Data.List
. Możesz zrobić prefiksy zscanr(\_->init)=<<id
(znalezione przez Ørjan Johansen).To oszczędza bajt
źródło
scanl(flip(:))[] "abc"
=["","a","ba","cba"]
- czasami prefiksy cofane nie mają znaczenia.scanr(\_->init)=<<id