【问题标题】:Two-dimensional zipper二维拉链
【发布时间】:2012-02-12 20:54:21
【问题描述】:

受最近关于 Haskell 中二维网格的问题的启发,我想知道是否可以创建一个二维拉链来跟踪列表中的位置。列表上的一维拉链使我们能够真正有效地在大列表中本地移动(常见的例子是文本编辑器)。但是假设我们有这样的第二个维度:

grid = 
    [[ 1, 2, 3, 4, 5]
    ,[ 6, 7, 8, 9,10]
    ,[11,12,13,14,15]
    ,[16,17,18,19,20]
    ,[21,22,23,24,25]]

我们是否可以创建某种拉链数据结构来有效地在此处的网格中左右移动以及上下移动?如果是这样,如果我们将列表列表替换为无限列表的无限列表,我们仍然可以获得有效的移动吗?

【问题讨论】:

    标签: haskell data-structures functional-programming multidimensional-array zipper


    【解决方案1】:

    我一直在寻找类似的东西:一种廉价且轻松地导航(包括“向后”)双重无限列表的方法。这是我的看法。

    如果我仔细阅读其他答案,我在这里展示的并不是 真的 拉链:虽然导航是摊销 O(1),但拉链 结构使用的内存 网络永远不会被释放。另一方面,无论我们采取何种路径到达它们,它都应该足够紧密地共享“单元”,这就是我们想要的二维列表的拓扑结构。

    作为补偿,用于生成它的列表列表最终应该不被引用并被垃圾回收。

    data FakeZip2D a = Z2 { z2Val   :: a
                          , goUp    :: !( Maybe (FakeZip2D a) )
                          , goDown  ::    Maybe (FakeZip2D a)
                          , goLeft  :: !( Maybe (FakeZip2D a) )
                          , goRight ::    Maybe (FakeZip2D a)
                          }
    
    fromList2 :: [[a]] -> Maybe (FakeZip2D a)
    fromList2 xss = head (head zss) where
      extended =       [ repeat Nothing ] ++
        map (\z -> [Nothing] ++ z ++ repeat Nothing) zss ++
                       [ repeat Nothing ]
      zss = zipWith4' row xss extended (drop 1 extended) (drop 2 extended)
      row xs prev cur next = Just <$> zipWith5' Z2 xs (tail prev) (tail next)
                                                      cur         (drop 2 cur)
    
      -- totally inspired by https://stackoverflow.com/a/54096748/12274
      zipWith4' f (a:as) (b:bs) ~(c:cs) ~(d:ds) =
        f a b c d : zipWith4' f as bs cs ds
      zipWith5' f (a:as) (b:bs) ~(c:cs) (d:ds) ~(e:es) =
        f a b c d e : zipWith5' f as bs cs ds es
    

    数据结构应该是不言自明的。因为我们是从单链表构建的,所以 Up and left 可以做到严格。 AFAIK,在 Haskell 中让他们懒惰是没有意义的,因为他们不会让任何事情超出范围。

    格是递归构建的,使用Nothing 扩展所提供输入的边界。我需要的 zipWith 的足够懒惰的变体灵感来自 answers to another series of questions of mine on the topic.

    它在行动:

    demo :: IO ()
    demo = do
      let multList2 = [[ i*j | j <- [0..] ] | i <- [0..] ]
          multZ2 = fromList2 multList2
    
      let rows = iterate (>>= goDown) multZ2
          cols = map (iterate (>>= goRight)) rows
    
      putStrLn "Multiplication table"
      mapM_ (print . map z2Val) $ take 5 $ map (catMaybes . take 5) cols
    
      putStrLn "List of squares"
      let goDiag = goRight >=> goDown
      print $ map z2Val $ take 25 $ catMaybes $ iterate (>>= goDiag) multZ2
    
      putStrLn "Convoluted list of squares"
      let goDiag' = goDown >=> goRight >=> goUp >=> goLeft >=> goDiag
      print $ map z2Val $ take 25 $ catMaybes $ iterate (>>= goDiag') multZ2
    

    删除Maybes 可能会使界面更易于使用。自然而然,风险自负。

    这可能有点离题,因为它也不是真正的拉链,但它解决了我的问题;由于这是我第一次寻找解决方案时出现的问题,因此我将其发布在这里是为了帮助其他人。

    【讨论】:

    • 等等。这不是真正的 O(1),是吗?它在 M+N 操作中分配 O(MN),所以我们可以说 O(N), ✓N 用于平均使用,对吧?我需要睡过去。 (我的用例是 MN 操作的 O(MN) 空间,足够恒定。)
    【解决方案2】:

    你可以使用一些简单的东西,比如下面的代码。我们用所选元素的顶行、所选元素的底行、所选元素左侧的元素和所选元素右侧的元素来表示一个表格。

    顶部行和左侧元素以相反的顺序存储,以实现高效移动。

    我不确定这是否符合拉链的条件,因为即使我们在数据结构中保存了“位置”,它也不是“路径”。

    -- Table sel left right top bottom
    data Table a = Table a [a] [a] [[a]] [[a]] deriving Show
    
    left :: Table a -> Table a
    left tab@(Table _ [] _ _ _) = tab
    left (Table sel (l:ls) rs ts bs) = Table l ls (sel:rs) ts bs
    
    right :: Table a -> Table a
    right tab@(Table _ _ [] _ _) = tab
    right (Table sel ls (r:rs) ts bs) = Table r (sel:ls) rs ts bs
    
    up :: Table a -> Table a
    up tab@(Table _ _ _ [] _) = tab
    up (Table sel ls rs (t:ts) bs) = Table sel' ls' rs' ts (b:bs)
      where
        (ls',(sel':rs')) = splitAt (length ls) t
        b = ls ++ (sel:rs)
    
    down :: Table a -> Table a
    down tab@(Table _ _ _ _ []) = tab
    down (Table sel ls rs ts (b:bs)) = Table sel' ls' rs' (t:ts) bs
      where
        (ls',(sel':rs')) = splitAt (length ls) b
        t = ls ++ (sel:rs)
    
    tableToList :: Table a -> [[a]]
    tableToList (Table sel ls rs ts bs) = (reverse ts) ++ [ls ++ (sel:rs)] ++ bs
    
    listToTable :: [[a]] -> Table a
    listToTable [] = error "cannot make empty table"
    listToTable ([]:_) = error "cannot make empty table"
    listToTable ((t:tr):ts) = Table t [] tr [] ts
    

    这甚至适用于无限列表 -

    selected :: Table a -> a
    selected (Table sel _ _ _ _) = sel
    
    a :: Table Int
    a = listToTable $ replicate 10 [1..]
    
    selected a                   #=> 1
    selected $ down a            #=> 1
    selected $ right $ down a    #=> 2
    

    【讨论】:

    • 这提供了与拉链相同的操作,但它不是一个。 Huet 介绍的拉链在每个导航步骤中具有恒定的分配量。您的实现具有取决于总数据结构大小的分配成本。所以,我不知道,这对于您的用例来说可能是一个有用的数据结构。但我不会称它为拉链。
    • 啊,有道理..我不知道我在想什么
    • @jmg:公平地说,这一个拉链——具体来说,两个标准的列表拉链嵌套,在嵌套列表上操作。 The actual navigation steps are moving along an inner list, or moving along the outer list when the selection is the first element of an inner list.问题是“上”和“下”不是此拉链导航的一部分。
    • @C.A.McCann:你可以这么说,但最终你没有拉链的通常好处。上面,我已经说过它可能是有用的数据结构,具体取决于具体情况。我只是不会称它为拉链。但是,是的,这就像使用一组减少的导航原语来制作拉链。
    【解决方案3】:

    相当,不。拉链如何工作的一个关键方面是,它们通过用于到达它的路径表示结构中的位置,以及沿途创建的额外片段,最终结果是您可以沿着该路径回溯并重建结构你走。因此,通过数据结构可用的路径的性质限制了拉链。

    因为位置是由路径标识的,每条不同的路径代表一个不同的位置,因此任何具有相同值的多个路径的数据结构都不能与拉链一起使用——例如,考虑循环列表或任何其他带有循环路径的结构。

    2D 空间中的任意移动并不真正符合上述要求,因此我们可以推断出 2D 拉链必然会受到一定的限制。例如,也许您会从原点开始,穿过结构,然后沿着该路径回溯一段距离以到达其他点。这也意味着对于结构中的任何点,都有其他点只能通过原点到达。

    可以做的是在数据结构中构建一些 2D 距离的概念,这样当您沿着路径向下穿过结构时,“下方”的点会彼此靠近;这个想法是最小化在 2D 空间中移动短距离所需的平均回溯量。这最终与按距离搜索 2D 空间所需的方法大致相同——最近邻搜索、有效的几何交集等等——并且可以使用相同类型的数据结构来完成,即space partitioning 创建一个更高维的搜索树。为quadtreekd-tree 或类似结构实现拉链很简单,就像任何其他树一样。

    【讨论】:

    • “拉链工作方式的一个关键方面是它们通过用于到达它的路径表示结构中的位置”。为什么拥有独特的路径是拉链的关键要求?我原以为在数据结构中表示“位置”的任何方式都足够了
    • @AnupamJain:因为用于重建的片段是原始不可变结构的片段,如果其中一个包含另一个到“相同”位置的路径,当你重新组装它时,该路径仍然具有原值。处理它的唯一方法是沿着两条路径走并进行两种替换 - 即,将两条路径一起视为“唯一”路径。
    • @AnupamJain:可能的冗余路径越多,您创建的效率就越低。最坏的情况是类似于循环列表,其中有无数条路径,每条路径都包含整个结构,这会迫使您重新构建所有内容。
    • 嗯,你觉得我下面的回答怎么样?我不确定它是否符合拉链的条件
    • @habitue:如果您仍然对探索这个想法感兴趣,我注意到spacepart 包似乎已被放弃。具有距离感知拉链的更广泛的空间分区库将是 hackage 的一个很好的补充......
    猜你喜欢
    • 2021-07-05
    • 2023-04-08
    • 2015-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多