您在Haskell打高尔夫球有哪些一般秘诀?我正在寻找可以应用于编码高尔夫问题的想法,这些想法至少在某种程度上特定于Haskell。每个答案请只发表一个提示。
如果您是Haskell的高尔夫新手,请参阅Haskell的高尔夫规则指南。还有一个专门的Haskell聊天室:Monads and Men。
您在Haskell打高尔夫球有哪些一般秘诀?我正在寻找可以应用于编码高尔夫问题的想法,这些想法至少在某种程度上特定于Haskell。每个答案请只发表一个提示。
如果您是Haskell的高尔夫新手,请参阅Haskell的高尔夫规则指南。还有一个专门的Haskell聊天室:Monads and Men。
Answers:
每个定义或调用通常节省一两个空间。
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语法中具有特殊含义(例如@
)。
如果需要五个以上的运算符,则可以使用上述的组合,例如!#
或某些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
(因此让我分开张贴这些消息让我很痛苦)
&&
在列表理解内使用。
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
,就像“最多包含一个元素的列表”一样。例如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
比短一个字符snd
,并且elem b
比短(==b).snd
。
mempty
和mappend
并非通常可以挽救生命,但是如果可以推断出类型,mempty
它比短一字节Nothing
。
<*>
其编入序曲!即使它不是编码高尔夫球(适用性很长的话),这也应该很有用。
[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
+ lambda 短:
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"]
但是,有些怪异的用法有时会派上用场。首先,列表理解根本不需要包含任何<-
箭头:
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
,即制作一个与in中一样多的0的列表l
。
[1|[]<-f<$>l]
这使得许多名单1
的,因为有元素l
是f
需要的空列表(使用<$>
作为缀map
)。适用sum
于计数这些元素。
相比:
[1|[]<-f<$>l]
[1|x<-l,f x==[]]
[x|(0,x)<-l]
常量可以用作模式匹配的一部分。这将提取所有第一项为的元组的第二项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)
使用(>>=)
(或(=<<)
)组合功能
函数monad的版本(>>=)
定义如下:
(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
。
另一个例子是(x+1)`div`y
与相对div(x+1)y
。
我发现这种情况经常发生div
,elem
因为这些功能通常在常规代码中用作中缀。
它们比let
解构您所定义函数的参数的a或lambda 短。这有助于当你需要像fromJust
来自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的技巧。
e
实际上不是一个令牌,而是$
在它之前需要一个更长的表达式,通常是这种情况。
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 的infix运算符,则需要一个空格:f= -2
定义f
并k< -2
测试if k
小于-2
。如果固定度为6或更高,则需要parens:2^^(-2)
给0.25
。通常,您可以重新整理内容以摆脱这些问题:例如,使用do -k>2
代替k< -2
。!
是操作符,则将-a!b
其解析为(-a)!b
固定性!
最多为6(-1<1
给定True
),-(a!b)
否则为(-[1,2]!!0
给定-1
)。用户定义的运算符和反引号函数的默认固定性为9,因此它们遵循第二条规则。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
无论如何都应该将其定义为infix函数。看到这个技巧。通过更改函数定义中模式匹配用例的顺序,有时可以节省几个字节。这些节省很便宜,但很容易忽略。
例如,请考虑以下早期版本的(部分)此答案:
(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
空列表出现在返回值中,因此我们可以通过交换大小写来节省两个字节:
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
有时候,你需要定义一个本地函数或操作,但它的成本大量的字节写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..]
这四个表达式中的任何一个都会产生的无限列表n
。
这是一个非常具体的提示,但最多可以节省3个字节!
n
为global,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
和[]
切换。
*>
具有高于固定性>>
(还是有点低了舒适性。)
Lambda表达式实际上并不需要括号-它只是贪婪地抓取了所有内容,因此整个过程仍然可以解析,例如直到
(foo$ \x -> succ x)
let a = \x -> succ x in a 4
main = getContents>>= \x -> head $ words x
遇到这种情况,在某些奇怪的情况下可以节省一两个字节。我相信\
也可以用来定义运算符,因此在利用此运算符时,直接在运算符之后编写一个lambda时将需要一个空间(如第三个示例)。
这是一个示例,其中我可以弄清楚使用lambda是最短的事情。该代码基本上看起来像:
a%f=...
f t=sortBy(% \c->...)['A'..'Z']
let
为lambda通常,这可以缩短一个单独的辅助定义,该辅助定义由于某种原因而无法与防护绑定或全局定义。例如,替换
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指出,lambda不能。例如,
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)
。如果我们尝试使用lambda重写它,则不会键入check。
定义命名函数时,可以将表达式绑定到防护中的变量。例如,
f s|w<-words s=...
与...相同
f s=let w=words s in ...
f s=(\w->...)$words s
使用它来保存重复的表达式。两次使用该表达式时,即使长度和优先级问题都可以更改该长度,它在长度6处的收支平衡。
(在示例中,如果s
未使用原始变量,则这样做的时间要短一些
g w=...
f=g.words
但这不适用于绑定更复杂的表达式。)
Just
示例使我认为是从容器中提取而不是存储在表达式中的模式匹配。
使用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