Haskellでゴルフをするための一般的なヒントは何ですか?私は、少なくともHaskellに特有のゴルフ問題全般のコーディングに適用できるアイデアを探しています。回答ごとに1つのヒントのみを投稿してください。
Haskellでゴルフをするのが初めての場合は、Haskellのゴルフ規則ガイドをご覧ください。また、専用のHaskellチャットルーム:Of Monads and Menもあります。
Haskellでゴルフをするための一般的なヒントは何ですか?私は、少なくともHaskellに特有のゴルフ問題全般のコーディングに適用できるアイデアを探しています。回答ごとに1つのヒントのみを投稿してください。
Haskellでゴルフをするのが初めての場合は、Haskellのゴルフ規則ガイドをご覧ください。また、専用のHaskellチャットルーム:Of Monads and Menもあります。
回答:
これにより、通常、定義または呼び出しごとに1つまたは2つのスペースが節約されます。
0!(y:_)=y
x!(y:z)=(x-1)!z
対
f 0(y:_)=y
f x(y:z)=f(x-1)z
1バイトの事業者のための利用可能なシンボルであり!
、#
、%
、&
、と?
。他のすべてのASCII句読点は、Preludeによって演算子として既に定義されているか(など$
)、Haskellの構文で特別な意味を持っています(など@
)。
5つを超える演算子が必要な場合、などの上記の組み合わせ!#
、または次のような特定のUnicode句読文字を使用できます(UTF-8ではすべて2バイト)。
¡ ¢ £ ¤ ¥ ¦ § ¨ © ¬ ® ¯ ° ± ´ ¶ · ¸ ¿ × ÷
(x!y)z=x+y*z
そして(x#y)z u=x*z+y*u
予想通りの両方が動作します。
\f g(!)x y->f g!x y
代わりに、関数の引数にも使用できます。\f g j x y->j(f g)(x y)
g x=…;g(f x)
より長い_?x=…;0!f x
簡単なレビュー:
xs >> ys = concat $ replicate (length xs) ys
xs >>= f = concatMap f xs
mapM id[a,b,c] = cartesian product of lists: a × b × c
mapM f[a,b,c] = cartesian product of lists: f a × f b × f c
例:
Prelude> "aa">>[1..5]
[1,2,3,4,5,1,2,3,4,5]
concatMap
Prelude> reverse=<<["Abc","Defgh","Ijkl"]
"cbAhgfeDlkjI"
concat
+リストの理解Prelude> do x<-[1..5];[1..x]
[1,1,2,1,2,3,1,2,3,4,1,2,3,4,5]
Prelude> mapM id["Hh","io",".!"]
["Hi.","Hi!","Ho.","Ho!","hi.","hi!","ho.","ho!"]
Prelude> mapM(\x->[0..x])[3,2]
[[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2],[3,0],[3,1],[3,2]]
[0..b]>>[a]
代わりですreplicate a b
。
a<$[1..b]
はさらに短いですreplicate
。
=<<
すると、インポートが強制されますControl.Monad
。他の理由でそれが必要ない場合は、引数を交換して使用する>>=
方がより簡潔に思えます。
Data.Traversable
とにかく必要な場合、デカルト積の例はに短縮できますfor["Hh","io",".!"]id
。
条件ではなくガードを使用します。
f a=if a>0 then 3 else 7
g a|a>0=3|True=7
インデントではなくセミコロンを使用する
f a=do
this
that
g a=do this;that
ブール関数にブール式を使用する
f a=if zzz then True else f yyy
g a=zzz||f yyy
(SOはこれらを個別に投稿するのが面倒です)
&&
リスト内包の内部ではなく、複数のガードを使用します。
True
=>1>0
f a=if a>0 then 3 else 7
このようなものを含むGHCの最初のバージョンは、2015年3月27日にリリースされました。
これは最新バージョンです。Preludeには、ゴルフに役立ついくつかの新しい追加機能が追加されました。
(<$>)
(<*>)
これらの便利な演算子がData.Applicative
作成されました!<$>
はだけなfmap
ので、どこにでも、どこでも置き換えてmap f x
、バイトを取り戻すことができます。fmap f x
f<$>x
また、リスト<*>
のApplicative
インスタンスで便利です:
Prelude> (,)<$>[1..2]<*>"abcd"
[(1,'a'),(1,'b'),(1,'c'),(1,'d'),(2,'a'),(2,'b'),(2,'c'),(2,'d')]
(<$)
オペレータx<$a
に等しいfmap (const x) a
; つまり、コンテナ内のすべての要素をで置き換えx
ます。
これは、多くの場合にすてきな代替手段ですreplicate
:4<$[1..n]
よりも短くなっていますreplicate n 4
。
以下の機能は、リストの作業[a]
から一般的なFoldable
タイプへの作業から解除されましたt a
。
fold*, null, length, elem, maximum, minimum, sum, product
and, or, any, all, concat, concatMap
つまり、Maybe a
「最大1つの要素を持つリスト」のように動作するようになりました。たとえばnull Nothing == True
、またはsum (Just 3) == 3
。同様に、length
値に0を返しNothing
、Just
値に1を返します。書く代わりにを書くx==Just y
ことができますelem y x
。
タプルにそれらを適用することもできます\(a, b) -> [b]
。これは、最初に呼び出した場合と同じように機能します。それはほとんど完全に役に立たないが、or :: (a, Bool) -> Bool
より短い1文字でありsnd
、elem b
より短い(==b).snd
。
mempty
とmappend
命の恩人になることはあまりありませんが、型を推測できる場合mempty
はNothing
、よりも1バイト短くなります。
<*>
て、プレリュードに参加できてうれしいです!それは、ゴルフをコード化していないときでも有用であるべきです(適用者はそのような長い言葉です)。
[1..2]
あります。それはただ[1,2]
<*
からも取得しました。これは異なっているかです。これもこの時点で短くなりました。Applicative
xs <* ys == concatMap (replicate (length ys)) xs
xs >> ys
xs *> ys
concat (replicate (length ys)) xs
pure
return
<>
代わりに使用できるようにmappend
なりました(GHC 8.4.1で)の一部ですPrelude
。
の1<2
代わりにTrue
との1>2
代わりに使用しFalse
ます。
g x|x<10=10|True=x
f x|x<10=10|1<2=x
f=max 10
。
if(true)
他の言語で書いているだけです。プレリュードでは、そうでなければ実際にはブール値True
です。
otherwise
。
誰もが便利な構文であり、多くの場合map
+ラムダよりも短いことを知っています。
Prelude> [[1..x]>>show x|x<-[1..9]]
["1","22","333","4444","55555","666666","7777777","88888888","999999999"]
またはfilter
(オプションmap
で同時に):
Prelude> [show x|x<-[1..60],mod 60x<1]
["1","2","3","4","5","6","10","12","15","20","30","60"]
しかし、時々便利になる奇妙な使い方がいくつかあります。1つは、リストの理解に<-
矢印を含める必要がないことです。
Prelude> [1|False]
[]
Prelude> [1|True]
[1]
代わりにif p then[x]else[]
、あなたは書くことができることを意味します[x|p]
。また、条件を満たすリストの要素の数をカウントするには、直感的に次のように記述します。
length$filter p x
しかし、これは短くなります。
sum[1|y<-x,p y]
Prelude
GHCiを起動し、Preludeのドキュメントをスクロールします。短い名前の関数を横断するときはいつでも、それが役に立つかもしれないいくつかのケースを探すために支払うことができます。
たとえば、文字列s = "abc\ndef\nghi"
をスペースで区切られた文字列に変換したいとします"abc def ghi"
。明白な方法は次のとおりです。
unwords$lines s
しかし、あなたは虐待場合は、より良い行うことができますmax
、との事実\n < space < printable ASCII
:
max ' '<$>s
別の例はlex :: String -> [(String, String)]
、非常に不思議なことをします:
Prelude> lex " some string of Haskell tokens 123 "
[("some"," string of Haskell tokens 123 ")]
fst=<<lex s
空白をスキップして、文字列から最初のトークンを取得してください。ここで使用していますhenkmaによって巧妙なソリューションであるlex.show
にRational
値が。
リストの内包表記では、定数のパターンマッチが可能です。
[0|0<-l]
これにより、リストの0が抽出さl
れl
ます。つまり、と同じ数の0のリストが作成されます。
[1|[]<-f<$>l]
これにより、空のリスト(infixとして使用)1
に含まれる要素の数l
だけのf
リストが作成さ<$>
れmap
ます。sum
これらの要素を数えるために適用します。
比較:
[1|[]<-f<$>l]
[1|x<-l,f x==[]]
[x|(0,x)<-l]
定数は、パターンマッチの一部として使用できます。これにより、最初のエントリがであるすべてのタプルの2番目のエントリが抽出されます0
。
これらはすべて、変数の値ではなく、実際の定数リテラルを必要とすることに注意してください。たとえば、外部バインディングが上書きされるため、ではなくlet x=1 in [1|x<-[1,2,3]]
を出力します。[1,1,1]
[1]
x
words
文字列の長いリストの代わりに使用します。これは実際にはHaskellに固有のものではなく、他の言語にも同様のトリックがあります。
["foo","bar"]
words"foo bar" -- 1 byte longer
["foo","bar","baz"]
words"foo bar baz" -- 1 byte shorter
["foo","bar","baz","qux"]
words"foo bar baz qux" -- 3 bytes shorter
1)
を使用して単項関数をシミュレートしmapM
ます。
多くの場合、コードに含まれますがsequence(map f xs)
、で置き換えることができますmapM f xs
。sequence
単独で使用する場合でも、それより長くなりmapM id
ます。
2)(または)
を使用して機能を組み合わせる(>>=)
(=<<)
関数のモナドバージョン(>>=)
は次のように定義されます。
(f >>= g) x = g (f x) x
パイプラインとして表現できない関数を作成するのに役立ちます。例えば、\x->x==nub x
より長いnub>>=(==)
、そして\t->zip(tail t)t
より長いですtail>>=zip
。
Applicative
ありMonad
、実装もありませんが、pure
これはconst
以前よりも短く、実際に私を助けてくれました。
バックティックをあまり頻繁に使用しないでください。バックティックは、接頭辞関数のセクションを作成するための便利なツールですが、誤用されることもあります。
誰かがこの部分式を書いているのを見たとき:
(x`v`)
ただと同じv x
ですが。
もう1つの例は(x+1)`div`y
、div(x+1)y
。
私はそれが周りに起こる見るdiv
とelem
、これらの機能は通常、定期的なコードで中置として使用されているので、より頻繁に。
これらは、let
定義している関数の引数を分解するa またはlambda よりも短いです。これはfromJust
fromのようなものが必要なときに役立ちますData.Maybe
:
f x=let Just c=… in c
より長い
f x=(\(Just c)->c)$…
より長い
m(Just c)=c;f x=m$…
より長い
f x|Just c<-…=c
実際、分解するのではなく単純な古い値をバインドする場合でも、それらは短くなります:xnorのtipを参照してください。
e
実際には1つのトークンではなく、その$
前に必要な長い式であると仮定していますが、これは通常そうです。
last$x:[y|b]
に等しい
if b then y else x
仕組みは次のとおりです。
[y|b] x:[y|b] last$x:[y|b]
if... +--------------------------------
b == False | [] [x] x
b == True | [y] [x,y] y
if b then y else x
?
bool
リストを理解する必要がないので、使用時間は短くなりません
マイナス記号-
は、多くの構文規則にとって厄介な例外です。このヒントでは、Haskellで否定と減算を表すいくつかの簡単な方法をリストします。何か見落とした場合はお知らせください。
e
、ただやる-e
。たとえば、-length[1,2]
を与え-2
ます。e
でも適度に複雑で、あなたの周りに括弧が必要になりますe
が、あなたは通常約それらを移動することでバイトを保存することができます-length(take 3 x)
よりも短くなっています-(length$take 3 x)
。e
前に、=
または固定度が6未満の中置演算子がある場合、スペースが必要です:がより小さいかどうかをf= -2
定義f
およびk< -2
テストします。固定度が6以上の場合、括弧が必要です:gives 。通常は、これらを取り除くためにものを並べ替えることができます。たとえば、代わりにdoを実行します。k
-2
2^^(-2)
0.25
-k>2
k< -2
!
オペレータ、次いで-a!b
として解析される(-a)!b
の定着性があれば!
最大で6である(そう-1<1
与えるTrue
)、及び-(a!b)
それ以外の場合は(そう-[1,2]!!0
与えます-1
)。ユーザー定義の演算子とバックティック関数のデフォルトの固定度は9であるため、2番目の規則に従います。map
etcで使用する)には、セクションを使用します(0-)
。k
するには(-k+)
、を追加するセクションを使用します-k
。k
かなり複雑な表現でさえありえます:(-2*length x+)
期待通りに動作します。pred
には、両側にスペースが必要な場合を除き、代わりに使用します。これは稀であり、通常で発生until
するので、またはユーザ定義関数map pred x
に置き換えることができるpred<$>x
とiterate pred x
によって[x,x-1..]
。また、f pred x
どこかにある場合は、f
とにかく挿入関数として定義する必要があります。このヒントを参照してください。関数定義でパターンマッチングケースの順序を変更することで、数バイトを節約できる場合があります。これらの節約は安価ですが、見落としがちです。
例として、この回答(の一部)の次の以前のバージョンを検討してください。
(g?x)[]=x
(g?x)(a:b)=g(g?x$b)a
これは、の再帰的な定義であり?
、基本ケースは空のリストです。[]
は有用な値ではないため、定義を交換し、ワイルドカード_
またはダミー引数y
に置き換えて、バイトを保存する必要があります。
(g?x)(a:b)=g(g?x$b)a
(g?x)y=x
同じ答えから、次の定義を検討してください。
f#[]=[]
f#(a:b)=f a:f#b
空のリストが戻り値に含まれているため、ケースを交換することで2バイトを節約できます。
f#(a:b)=f a:f#b
f#x=x
また、関数の引数の順序によって、不要な空白を削除できるため、違いが生じる場合があります。この回答の以前のバージョンを検討してください:
h p q a|a>z=0:h p(q+2)(a-1%q)|1<2=1:h(p+2)q(a+1%p)
最初のブランチh
との間には迷惑な空白がありp
ます。h a p q
代わりに定義することでそれを取り除くことができますh p q a
:
h a p q|a>z=0:h(a-1%q)p(q+2)|1<2=1:h(a+1%p)(p+2)q
キャッチオールTrue
(より短い1>0
)である最終ガードを使用して、変数をバインドできます。比較:
... |1>0=1/(x+y)
... |z<-x+y=1/z
... |1>0=sum l-sum m
... |s<-sum=s l-s m
ガードは必須であり、それ以外の場合は浪費されるため、これを価値あるものにするためにはほとんど必要ありません。ペアのペアを保存するか、2回使用される長さ3の式をバインドするだけで十分です。最終的なケースがバインディングを最もよく使用する式になるように、ガードを無効にすることができます。
ローカル関数またはローカル演算子を定義する必要がある場合もありますが、追加の引数を追加して記述しwhere
たりlet…in
、トップレベルに持ち上げたりするには多くのバイトがかかります。
g~(a:b)=2!g b where k!l=k:take(a-1)l++(k+1)!drop(a-1)l
g~(a:b)=let k!l=k:take(a-1)l++(k+1)!drop(a-1)l in 2!g b
g~(a:b)=2!g b$a;(k!l)a=k:take(a-1)l++((k+1)!drop(a-1)l)a
幸いなことに、Haskellには混乱を招き、めったに使用されないが、ローカル宣言のための合理的な簡潔な構文があります。
fun1 pattern1 | let fun2 pattern2 = expr2 = expr1
この場合:
g~(a:b)|let k!l=k:take(a-1)l++(k+1)!drop(a-1)l=2!g b
この構文は、複数ステートメントの宣言または複数の宣言で使用でき、さらにネストすることもできます。
fun1 pattern1 | let fun2 pattern2 = expr2; fun2 pattern2' = expr2' = expr1
fun1 pattern1 | let fun2 pattern2 = expr2; fun3 pattern3 = expr3 = expr1
fun1 pattern1 | let fun2 pattern2 | let fun3 pattern3 = expr3 = expr2 = expr1
また、変数や他のパターンのバインドにも機能しますが、関数もバインドしない限り、パターンガードは短くなる傾向があります。
[f 1|let f x=x+1]
。
repeat n
-- 8 bytes, whitespace might be needed before and after
repeat n
-- 8 bytes, whitespace might be needed before
cycle[n]
-- 7 bytes, whitespace might be needed before and after, can be reused,
-- needs an assignment, n needs to be global
l=n:l;l
-- 7 bytes, never needs whitespace, n needs to derive from Enum,
-- n has to be short enough to be repeated twice
[n,n..]
これらの4つの式のいずれかは、の無限リストを生成しn
ます。
これは非常に具体的なヒントですが、最大3バイト節約できます!
n
がグローバルの場合、l=n:l;l
長さは同じで、(一部の)より長い式で機能します。(ただし、空白が必要な場合があります。)
条件に応じてリストA
または空のリストを返す条件が必要な場合、通常の条件構造の代わりにいくつかの短い選択肢があります。[]
C
if(C)then(A)else[] -- the normal conditional
last$[]:[A|C] -- the golfy all-round-conditional
concat[A|C] -- shorter and works when surrounded by infix operator
id=<<[A|C] -- even shorter but might conflict with other infix operators
[x|C,x<-A] -- same length and no-conflict-guarantee™
[0|C]>>A -- shortest way, but needs surrounding parenthesis more often than not
A
と[]
切り替えました。
*>
より高い固定性がある>>
(まだ少し低い快適さのために。)
ラムダ式は実際に括弧を必要としません-それはむしろ貪欲にすべてをつかむので、全体がまだ解析されます、例えばまで
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
が発生し、これにより1〜2バイト節約できる奇妙なエッジケースがいくつかあります。\
また、演算子の定義にも使用できると考えているため、これを利用する場合、演算子の直後にラムダを記述するときにスペースが必要になります(3番目の例のように)。
ラムダを使用することが私が理解できる最短のものであった場所の例を次に示します。コードは基本的に次のようになります。
a%f=...
f t=sortBy(% \c->...)['A'..'Z']
let
ラムダで置換これは通常、何らかの理由でガードにバインドできない、またはグローバルに定義できない、単独の補助定義を短縮できます。たとえば、置き換えます
let c=foo a in bar
3バイト短い
(\c->bar)$foo a
複数の補助定義の場合、定義の数に応じて、おそらくゲインは小さくなります。
let{c=foo a;n=bar a}in baz
(\c n->baz)(foo a)$bar a
let{c=foo a;n=bar a;m=baz a}in qux
(\c n m->qux)(foo a)(bar a)$baz a
let{c=foo a;n=bar a;m=baz a;l=qux a}in quux
(\c n m l->quux)(foo a)(bar a)(baz a)$qux a
一部の定義が他の定義を参照している場合、この方法でバイトを保存するのはさらに難しくなります。
let{c=foo a;n=bar c}in baz
(\c->(\n->baz)$bar c)$foo a
これに関する主な注意点は、let
多相変数を定義できることですが、@ ChristianSieversで指摘されているように、ラムダでは定義できません。例えば、
let f=length in(f["True"],f[True])
結果は(1,1)
ですが、
(\f->(f["True"],f[True]))length
型エラーが発生します。
let
があるので、できますlet f=id in (f 0,f True)
。これをラムダで書き直そうとすると、型チェックは行われません。
名前付き関数を定義する場合、ガード内の変数に式をバインドできます。例えば、
f s|w<-words s=...
と同じ
f s=let w=words s in ...
f s=(\w->...)$words s
これを使用して、繰り返し式を保存します。式が2回使用されると、長さ6でさえ壊れますが、間隔と優先順位の問題がそれを変更する可能性があります。
(例では、元の変数s
が使用されていない場合、実行する方が短くなります
g w=...
f=g.words
ただし、より複雑な式をバインドする場合はそうではありません。)
Just
例は、パターンマッチングが式に格納するのではなく、コンテナから抽出するためだと思いました。
(0<$)
代わりに使用length
リストa
がリストよりも長いかどうかをテストする場合、b
通常は次のように書きます。
length a>length b
ただし、両方のリストの各要素を同じ値に置き換えて(例:)0
、次にこれらの2つのリストを比較する方が短い場合があります。
(0<$a)>(0<$b)
括弧があるために必要とされる<$
と、比較演算子(==
、>
、<=
他のいくつかの例では、彼らはさらに多くのバイトを保存し、必要ではないかもしれませんが、...)は、同じ優先レベル4を持っています。
scanr(:)[]
リストのサフィックスを取得するために使用します。
λ scanr(:)[] "abc"
["abc","bc","c",""]
これはtails
後よりもずっと短いですimport Data.List
。プレフィックスをscanr(\_->init)=<<id
付けることができます(ØrjanJohansenにより発見)。
λ scanr(\_->init)=<<id $ "abc"
["","a","ab","abc"]
これはバイトを節約します
scanl(\s c->s++[c])[]
scanl(flip(:))[] "abc"
= ["","a","ba","cba"]
も言及する価値があります。場合によっては、プレフィックスが後方にあることは重要ではありません。
scanr(\_->init)=<<id