Каковы некоторые лучшие способы записи [(-1, -1), (- 1,0), (- 1,1), (0, -1), (0,1), (1, -1), (1,0), (1,1)] в Хаскелле?

Я столкнулся с несколькими ситуациями, когда мне нужен список:

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

Обратите внимание, что в списке нет (0,0). Я использую кортежи (dx, dy) для поиска вверх, вниз, влево, вправо и по диагонали от координаты.

Каждый раз, когда я пишу его, я чувствую, что должен быть более сжатый и/или более простой способ прочитать его. Я относительно новичок в Haskell, и я фигурирую где-то в сумке с аппликативными /Functor/Monad трюками, должен быть аккуратный способ сделать это.

Я пробовал:

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

Иногда лучше просто написать это, но я не думаю, что это один из тех времен. Это не очевидно, если взглянуть, что (0,0) не входит в комплект, и вы должны немного прочитать его, чтобы заметить шаблон.

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

Мне нравится выше, потому что я получаю там "2", что является хорошим явным способом сказать "мы делаем то же самое в два раза", но я не могу принять карту впереди с большой небезопасной лямбдой и двумя именами.

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

У этого слишком много имен в нем, но использует понимание списка точно так, как оно было разработано. Это может быть проще читать для тех, кто действительно любит списки, но мне не нравятся все имена.

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

Это выглядит красивее imo, но у меня нет этого "2", и у меня есть имя. Это мой любимый пока, но он не чувствует себя прекрасно.

Я думаю, что если бы я понял, как лучше писать, я мог бы глубже понять Функторы/Монады или тому подобное. Я читал о них совсем немного, и я слышал кучу слов, таких как fmap/mconcat/etc, но я не знаю, в какую из них можно было бы справиться в этой ситуации.

Ответы

Ответ 1

На самом деле, я думаю, что лучше всего написать это явно в этом случае. Просто выровняйте его разумно, и никаких вопросов не останется открытым:

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

Никакой альтернативы не может быть более понятным, чем это.

Конечно, есть более сжатые альтернативы. Будучи физическим парнем, я бы склонен к

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

что, конечно, дороже для вычисления, но это не имеет значения, если вы только определяете этот список в одном месте и повторно используете его со всей вашей программы. Порядок здесь другой, не уверен, что это важно.

Ответ 2

Почему бы просто не использовать понимание списка? Они могут иметь в себе булевую охрану, поэтому исключение (0,0) довольно просто:

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

Обратите внимание, что как сам Haskell newb, вероятно, более компактный/эффективный способ написать это защитное выражение. Тем не менее, это выполняет свою работу.

Ответ 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

Здесь краткая версия, использующая материал из Control.Applicative:

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

Лично я думаю, что это выглядит намного лучше, используя &, который просто $ перевернулся.

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

Вы также можете использовать liftA2 вместо <$> и <*>:

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

Он определен в библиотеке объективов, среди прочих мест, но вы можете определить его сами:

infixl 1 &
x & f = f x

Все, что сказал, я по-прежнему предпочитаю версию со списком или даже буквальным списком с хорошим отступом.

Ответ 5

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

(Не то, чтобы я рекомендую это.)

Ответ 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

Для упрощения чтения вы можете попробовать:

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

Это действительно векторы, поэтому вы можете захотеть использовать библиотеку векторов, если это имеет смысл в вашем приложении, или создать свои собственные пользовательские векторные операции:

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