What are some better ways to write [(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] in Haskell?

╄→尐↘猪︶ㄣ 提交于 2019-12-08 14:45:55

问题


I've run in to a few situations where I need the list:

[(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)] -- no (0,0)

Note that there is no (0,0) in the list. I use the (dx,dy) tuples to search up, down, left, right and diagonally from a coordinate.

Every time I write it I feel like there should be a more concise, and/or easier to read way to define it. I'm relatively new to Haskell and I figure somewhere in the bag of Applicative/Functor/Monad tricks there should be a neat way to do this.

I've tried:

[(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]

Sometimes it's better just to write it out, but I don't think this is one of those times. It's not obvious with a glance that (0,0) isn't included, and you have to read it a bit to notice the pattern.

map (\[a,b] -> (a,b)) $ delete [0,0] $ sequence $ replicate 2 [-1,0,1]

I like the one above because I get to put the "2" in there which is a nice explicit way to say "We're doing the same sort of thing twice", but I can't accept the map in front with the big unsafe lambda and 2 names.

[(dx,dy) | let a = [-1,0,1], dx <- a, dy <- a, (dx,dy) /= (0, 0)]

This one has too many names in it, but uses the list comprehension exactly as it is designed. It might be easier to read for somebody who really likes list comprehensions, but I don't like all the names.

let x = [-1,0,1] in delete (0,0) $ (,) <$> x <*> x

That one looks prettier imo, but I don't have that "2" in there, and I have a name. This is my favorite so far, but it doesn't feel perfect.

I think if I understood how to write this better I might have a deeper understanding of Functors/Monads or the like. I've read about them quite a bit, and I've heard a bunch of words like fmap/mconcat/etc, but I don't know which one to grab for in this situation.


回答1:


Actually, I think it's indeed best to write it out explicitly in this case. Just align it reasonably, and no questions stay open whatsoever:

neighbours = [ (-1,-1), (-1,0), (-1,1)
             , ( 0,-1),         ( 0,1)
             , ( 1,-1), ( 1,0), ( 1,1) ]

There's no way any alternative could be more self-explanatory than this.

Of course, there are more concise alternatives. Being a physics guy, I'd tend to

   [ (round $ - sin φ, round $ - cos φ) | φ <- [pi/4, pi/2 .. 2*pi] ]

which is of course more expensive to compute, but that doesn't matter if you only define this list at one place and re-use it from all over you program. The order is different here, not sure if that matters.




回答2:


Why not just use a list comprehension? They can have boolean guards in them, so excluding (0,0) is pretty simple:

[(i,j) | i <- [-1..1], j <- [-1..1], (i,j) /= (0,0)]

Note that as a Haskell newb myself, there is likely a more compact/efficient way to write that guard expression. Nonetheless, this gets the job done.




回答3:


Prelude Data.Ix Data.List> delete (0,0) (range ((-1,-1),(1,1)))
[(-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)]



回答4:


Here's a concise version using stuff from Control.Applicative:

delete (0, 0) $ (,) <$> [-1..1] <*> [-1..1]

Personally, I think this looks much better using &, which is just $ flipped.

(,) <$> [-1..1] <*> [-1..1] & delete (0, 0)

You could also use liftA2 instead of <$> and <*>:

liftA2 (,) [-1..1] [-1..1] & delete (0, 0)

It's defined in the lens library, among other place, but you can define it yourself:

infixl 1 &
x & f = f x

All that said, I still prefer the version with the list comprehension or even just the literal list with good indentation.




回答5:


liftA2 zip (!!0) (!!1) . transpose . tail . replicateM 2 $ [0,1,-1]

(Not that I recommend this.)




回答6:


Prelude> let sqrt' n | n == 0 = [0] | otherwise = [sqrt n, negate (sqrt n)]

Prelude> [(x,y) | x <- [-1..1]
                , y <- sqrt' (1 - x^2) ++ (if x==0 then [] else sqrt' (2 - x^2))]



回答7:


For easier to read you could try:

left = (-1,0)
right = (1,0)
up  = (0,1)
down = (0,-1)
--etc
directions = [left,right,up,down]

These are really vectors, so you might want to consider using a vector library if it makes sense in your app, or create your own custom vector operations:

import Data.Vect
vecUp = Vec2 0,1
vecDown = Vec2 0,(-1)
--etc.
goUp10 = 10 *& vecUp -- '*&' is the scalar multiply operator


来源:https://stackoverflow.com/questions/21321372/what-are-some-better-ways-to-write-1-1-1-0-1-1-0-1-0-1-1-1-1

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!