【问题标题】:How do you build an infinite grid like data structure in Haskell?你如何在 Haskell 中构建一个无限网格的数据结构?
【发布时间】:2018-03-30 13:05:35
【问题描述】:

我试图通过打结来形成一个无限网格状的数据结构。

这是我的方法:

import Control.Lens

data Grid a = Grid {_val :: a,
                    _left :: Grid a,
                    _right :: Grid a,
                    _down :: Grid a,
                    _up :: Grid a}

makeLenses ''Grid

makeGrid :: Grid Bool -- a grid with all Falses
makeGrid = formGrid Nothing Nothing Nothing Nothing

formGrid :: Maybe (Grid Bool) -> Maybe (Grid Bool) -> Maybe (Grid Bool) -> Maybe (Grid Bool) -> Grid Bool
formGrid ls rs ds us = center
  where
    center = Grid False leftCell rightCell downCell upCell
    leftCell = case ls of
                Nothing -> formGrid Nothing (Just center) Nothing Nothing
                Just l ->  l
    rightCell = case rs of
                Nothing -> formGrid (Just center) Nothing Nothing Nothing
                Just r ->  r
    upCell = case us of
                Nothing -> formGrid Nothing Nothing (Just center) Nothing
                Just u ->  u
    downCell = case ds of
                Nothing -> formGrid Nothing Nothing Nothing (Just center)
                Just d ->  d

由于某种原因,这不起作用。如此处所示:

*Main> let testGrid = (set val True) . (set (right . val) True) $ makeGrid
*Main> _val $ _right $ _left testGrid
False
*Main> _val $ _left $ _right testGrid
False
*Main> _val $ testGrid
True

我哪里错了?

【问题讨论】:

  • 当您set val True 时,您不是在原地修改,而是在创建副本。 makeGrid 构造一个网格,其中所有内容都是False,包括center -> right -> left。当您在中心set val True 时,您正在创建一个副本center',其中val center' == True,但_right center' == _right center,因此_left $ _right center' == _left $ _right center == False
  • @FyodorSoikin 这应该是答案;这是我刚开始写的。
  • @cirdec 我不觉得它回答了这个问题,因为问题是“你是怎么做的”,而不是“为什么我的尝试没有奏效”。但是看到你如何删除你的答案,我会把我的评论合二为一。 :-)

标签: haskell data-structures tying-the-knot


【解决方案1】:

@Fyodor 的回答解释了为什么您当前的方法行不通。

在函数式语言中实现此目的的一种常见方法是使用 zippers (不要与zip 或相关函数混淆)。

这个想法是拉链是数据结构的表示 专注于特定部分(例如,网格中的单元格)。你可以 对拉链应用变换以“移动”这个焦点,并且 您可以应用不同的转换来查询或“改变”数据结构 相对于焦点。两种类型的转换都是纯粹的 功能性——它们作用于不可变的拉链并且只是 创建一个新副本。

在这里,您可以从带有位置的无限列表的拉链开始 资料:

data Zipper a = Zipper [a] a Int [a] deriving (Functor)
  -- Zipper ls x n rs represents the doubly-infinite list (reverse ls ++
  -- [x] ++ rs) viewed at offset n
instance (Show a) => Show (Zipper a) where
  show (Zipper ls x n rs) =
    show (reverse (take 3 ls)) ++ " " ++ show (x,n) ++ " " ++ show (take 3 rs)

这个Zipper 旨在代表双重无限 列表(即在两个方向上都是无限的列表)。一个例子 应该是:

> Zipper [-10,-20..] 0 0 [10,20..]
[-30,-20,-10] (0,0) [10,20,30]

这旨在代表所有(正面和负面)的列表 十的整数倍集中在值0,位置0,它实际上使用了两个Haskell无限列表,每个方向一个。

你可以 定义将焦点向前或向后移动的函数:

back, forth :: Zipper a -> Zipper a
back (Zipper (l:ls) x n rs)  = Zipper ls l (n-1) (x:rs)
forth (Zipper ls x n (r:rs)) = Zipper (x:ls) r (n+1) rs

这样:

> forth $ Zipper [-10,-20..] 0 0 [10,20..]
[-20,-10,0] (10,1) [20,30,40]
> back $ back $ Zipper [-10,-20..] 0 0 [10,20..]
[-50,-40,-30] (-20,-2) [-10,0,10]
>

现在,Grid 可以表示为行的拉链,每行一个 价值拉链:

newtype Grid a = Grid (Zipper (Zipper a)) deriving (Functor)
instance Show a => Show (Grid a) where
  show (Grid (Zipper ls x n rs)) =
    unlines $ zipWith (\a b -> a ++ " " ++ b)
              (map show [n-3..n+3])
              (map show (reverse (take 3 ls) ++ [x] ++ (take 3 rs)))

连同一组焦点移动功能:

up, down, right, left :: Grid a -> Grid a
up (Grid g) = Grid (back g)
down (Grid g) = Grid (forth g)
left (Grid g) = Grid (fmap back g)
right (Grid g) = Grid (fmap forth g)

您可以为焦点元素定义 getter 和 setter:

set :: a -> Grid a -> Grid a
set y (Grid (Zipper ls row n rs)) = (Grid (Zipper ls (set' row) n rs))
  where set' (Zipper ls' x m rs') = Zipper ls' y m rs'

get :: Grid a -> a
get (Grid (Zipper _ (Zipper _ x _ _) _ _)) = x

添加一个将焦点移回的功能可能会很方便 到原点用于显示目的:

recenter :: Grid a -> Grid a
recenter g@(Grid (Zipper _ (Zipper _ _ m _) n _))
  | n > 0 = recenter (up g)
  | n < 0 = recenter (down g)
  | m > 0 = recenter (left g)
  | m < 0 = recenter (right g)
  | otherwise = g

最后,使用一个创建全False 网格的函数:

falseGrid :: Grid Bool
falseGrid =
  let falseRow = Zipper falses False 0 falses
      falses = repeat False
      falseRows = repeat falseRow
  in  Grid (Zipper falseRows falseRow 0 falseRows)

你可以这样做:

> let (&) = flip ($)
> let testGrid = falseGrid & set True & right & set True & recenter
> testGrid
-3 [False,False,False] (False,0) [False,False,False]
-2 [False,False,False] (False,0) [False,False,False]
-1 [False,False,False] (False,0) [False,False,False]
0 [False,False,False] (True,0) [True,False,False]
1 [False,False,False] (False,0) [False,False,False]
2 [False,False,False] (False,0) [False,False,False]
3 [False,False,False] (False,0) [False,False,False]

> testGrid & right & left & get
True
> testGrid & left & right & get
True
> testGrid & get
True
>

完整示例:

{-# LANGUAGE DeriveFunctor #-}

module Grid where

data Zipper a = Zipper [a] a Int [a] deriving (Functor)
  -- Zipper ls x n rs represents the doubly-infinite list (reverse ls ++
  -- [x] ++ rs) viewed at offset n
instance (Show a) => Show (Zipper a) where
  show (Zipper ls x n rs) =
    show (reverse (take 3 ls)) ++ " " ++ show (x,n) ++ " " ++ show (take 3 rs)

back, forth :: Zipper a -> Zipper a
back (Zipper (l:ls) x n rs)  = Zipper ls l (n-1) (x:rs)
forth (Zipper ls x n (r:rs)) = Zipper (x:ls) r (n+1) rs

newtype Grid a = Grid (Zipper (Zipper a)) deriving (Functor)
instance Show a => Show (Grid a) where
  show (Grid (Zipper ls x n rs)) =
    unlines $ zipWith (\a b -> a ++ " " ++ b)
              (map show [n-3..n+3])
              (map show (reverse (take 3 ls) ++ [x] ++ (take 3 rs)))

up, down, right, left :: Grid a -> Grid a
up (Grid g) = Grid (back g)
down (Grid g) = Grid (forth g)
left (Grid g) = Grid (fmap back g)
right (Grid g) = Grid (fmap forth g)

set :: a -> Grid a -> Grid a
set y (Grid (Zipper ls row n rs)) = (Grid (Zipper ls (set' row) n rs))
  where set' (Zipper ls' x m rs') = Zipper ls' y m rs'

get :: Grid a -> a
get (Grid (Zipper _ (Zipper _ x _ _) _ _)) = x

recenter :: Grid a -> Grid a
recenter g@(Grid (Zipper _ (Zipper _ _ m _) n _))
  | n > 0 = recenter (up g)
  | n < 0 = recenter (down g)
  | m > 0 = recenter (left g)
  | m < 0 = recenter (right g)
  | otherwise = g

falseGrid :: Grid Bool
falseGrid =
  let falseRow = Zipper falses False 0 falses
      falses = repeat False
      falseRows = repeat falseRow
  in  Grid (Zipper falseRows falseRow 0 falseRows)

(&) = flip ($)

testGrid :: Grid Bool
testGrid = falseGrid & set True & right & set True & recenter

main = do
  print $ testGrid & get
  print $ testGrid & left & get
  print $ testGrid & left & right & get
  print $ testGrid & right & left & get

【讨论】:

    【解决方案2】:

    关键见解是:当您set val True 时,您不是在原地修改,而是在创建副本。

    makeGrid 构造一个网格,其中所有内容都是False,包括_left $ _right center。当您在centerset val True 时,您正在创建一个副本center',其中val center' == True。但是,这个副本仍然指向同一个_right,而后者又仍然指向同一个_left,换句话说:

    _right center' == _right center
    

    因此:

    _left $ _right center' == _left $ _right center == center
    

    这样:

    _val . _left $ _right center' == _val . _left $ _right center == False
    

    【讨论】:

    • 有没有办法通过适当地更新邻居或类似的方法来正确地做到这一点?
    • @Agnisom 如果您更新邻居,那么您将再次遇到与更远邻居的邻居相同的问题,依此类推。正是由于这个原因,像这样具有“反向引用”的数据结构对于不可变类型来说是一种痛苦。您也许可以在懒惰的情况下使用它,但代价是在您进行修改时会在 每个 单元格中堆积 thunk,但这只是一种痛苦。我们通常使用不同的技术。
    猜你喜欢
    • 1970-01-01
    • 2010-12-20
    • 2011-06-14
    • 1970-01-01
    • 1970-01-01
    • 2011-12-23
    • 1970-01-01
    • 2011-04-02
    • 2021-01-25
    相关资源
    最近更新 更多