我对在函数式语言中实现常量网格的不同方式感兴趣。一个完美的解决方案应该在每步的pesimistic恒定时间内提供遍历,而不是使用命令式构造(懒惰是可以的)。不太满足这些要求的解决方案仍然受到欢迎。
我的建议基于四向链接节点,如此
基本操作是构建给定大小的网格。似乎这个操作将决定类型,即哪个方向将是懒惰的(显然这种数据结构不能没有懒惰)。所以我建议(在OCaml中)
type 'a grid =
| GNil
| GNode of 'a * 'a grid Lazy.t * 'a grid Lazy.t * 'a grid * 'a grid
订购参考:左,上,右,下。左,上是暂停。然后我按对角线方式构建网格
这是一个make_grid
函数,它构造一个给定大小的网格,并将坐标元组作为节点值。请注意,gl
,gu
,gr
,gd
功能允许在所有方向上在网格上行走,如果给出GNil
,则会返回GNil
}。
let make_grid w h =
let lgnil = Lazy.from_val GNil in
let rec build_ur x y ls dls = match ls with
| l :: ((u :: _) as ls') ->
if x = w && y = h then
GNode ((x, y), l, u, GNil, GNil)
else if x < w && 1 < y then
let rec n = lazy (
let ur = build_ur (x + 1) (y - 1) ls' (n :: dls) in
let r = gd ur in
let d = gl (gd r)
in GNode ((x, y), l, u, r, d)
)
in force n
else if x = w then
let rec n = lazy (
let d = build_dl x (y + 1) (n :: dls) [lgnil]
in GNode ((x, y), l, u, GNil, d)
)
in force n
else
let rec n = lazy (
let r = build_dl (x + 1) y (lgnil :: n :: dls) [lgnil] in
let d = gl (gd r)
in GNode ((x, y), l, u, r, d)
)
in force n
| _ -> failwith "make_grid: Internal error"
and build_dl x y us urs = match us with
| u :: ((l :: _) as us') ->
if x = w && y = h then
GNode ((x, y), l, u, GNil, GNil)
else if 1 < x && y < h then
let rec n = lazy (
let dl = build_dl (x - 1) (y + 1) us' (n :: urs) in
let d = gr dl in
let r = gu (gr d)
in GNode ((x, y), l, u, r, d)
)
in force n
else if y = h then
let rec n = lazy (
let r = build_ur (x + 1) y (n :: urs) [lgnil]
in GNode ((x, y), l, u, r, GNil)
)
in force n
else (* x = 1 *)
let rec n = lazy (
let d = build_ur x (y + 1) (lgnil :: n :: urs) [lgnil] in
let r = gu (gr d)
in GNode ((x, y), l, u, r, d)
)
in force n
| _ -> failwith "make_grid: Internal error"
in build_ur 1 1 [lgnil; lgnil] [lgnil]
它看起来相当复杂,因为它必须分别处理我们上升时以及当我们分别下降时的情况 - build_ur
和build_dl
辅助功能。 build_ur
函数的类型为
build_ur :
int -> int ->
(int * int) grid Lazy.t list ->
(int * int) grid Lazy.t list -> (int * int) grid
在给定当前位置x
和y
的情况下构建一个节点,上一个对角线ls
的暂停元素列表,暂停之前的列表当前对角线urs
的元素。名称ls
来自ls
上的第一个元素是当前节点的左邻居。构建下一个对角线需要urs
列表。
build_urs
函数继续在右上对角线上构建下一个节点,将当前节点传递给暂停。 左和上邻居取自ls
,可以访问右和下邻居通过对角线上的下一个节点。
请注意,我在GNil
和urs
列表中放了一堆ls
个。这样做始终确保build_ur
和build_dl
可以使用这些列表中的至少两个元素。
build_dl
功能类似。
对于这种简单的数据结构,这种实现看起来过于复杂。事实上,我很惊讶它起作用,因为我在写作时被信仰驱使,并且无法完全理解为什么它有效。因此,我想知道一个更简单的解决方案。
我正在考虑逐行构建网格。这种方法具有较少的边界情况,但我不能消除在不同方向上构建后续行的需要。这是因为当我走到最后一行并想从头开始构建另一行时,我必须以某种方式知道当前行中第一个节点的 down 节点,在我从当前的函数调用返回之前,我似乎无法知道。如果我无法消除双向性,我需要两个内部节点构造函数:一个具有已挂起的左和顶部,另一个具有已挂起的右和顶部。
此外,这里有一个这个实现的要点以及省略的函数:https://gist.github.com/mkacz91/0e63aaa2a67f8e67e56f
答案 0 :(得分:2)
如果您需要功能性解决方案,那么您正在寻找的数据结构是zipper。我已经在Haskell中编写了其余代码,因为我发现它更符合我的口味,但它很容易移植到OCaml。 Here's a gist没有交错评论。
{-# LANGUAGE RecordWildCards #-}
module Grid where
import Data.Maybe
我们可以从了解刚才列表的数据结构开始:您可以将拉链视为列表内部的指针。你有一个wathever在你指向的元素的左边,然后是你指向的元素,最后是右边的元素。
type ListZipper a = ([a], a, [a])
给定一个列表和一个整数n
,您可以关注位于n
的元素。当然,如果n
大于列表的长度,那么你就失败了。需要注意的一件重要事情是列表的左侧部分向后存储:因此可以在恒定时间内将焦点向左移动。随着向右移动。
focusListAt :: Int -> [a] -> Maybe (ListZipper a)
focusListAt = go []
where
go _ _ [] = Nothing
go acc 0 (hd : tl) = Just (acc, hd, tl)
go acc n (hd : tl) = go (hd : acc) (n - 1) tl
现在让我们转向网格。 Grid
只是一个行列表(列表)。
newtype Grid a = Grid { unGrid :: [[a]] }
Grid
的拉链现在由一个网格给出,表示当前焦点的所有above
,另一个代表below
的所有内容,以及一个列表拉链(高级:注意这个看起来有点像嵌套列表拉链&amp;可以用更通用的术语重新表述。)
data GridZipper a =
GridZipper { above :: Grid a
, below :: Grid a
, left :: [a]
, right :: [a]
, focus :: a }
首先关注右侧行,然后关注右侧元素,我们可以将Grid
关注到某个坐标x
和y
。
focusGridAt :: Int -> Int -> Grid a -> Maybe (GridZipper a)
focusGridAt x y g = do
(before, line , after) <- focusListAt x $ unGrid g
(left , focus, right) <- focusListAt y line
let above = Grid before
let below = Grid after
return GridZipper{..}
一旦我们有拉链,我们就可以轻松移动。左边或右边的代码并不令人惊讶地相似:
goLeft :: GridZipper a -> Maybe (GridZipper a)
goLeft g@GridZipper{..} =
case left of
[] -> Nothing
(hd:tl) -> Just $ g { focus = hd, left = tl, right = focus : right }
goRight :: GridZipper a -> Maybe (GridZipper a)
goRight g@GridZipper{..} =
case right of
[] -> Nothing
(hd:tl) -> Just $ g { focus = hd, left = focus : left, right = tl }
当上升或下降时,我们必须要小心,因为我们需要专注于我们在新行中留下的那个上方(或下方)的位置。我们还必须将我们关注的上一行重新组合成一个好的旧列表(通过将反向left
附加到focus : right
)。
goUp :: GridZipper a -> Maybe (GridZipper a)
goUp GridZipper{..} = do
let (line : above') = unGrid above
let below' = (reverse left ++ focus : right) : unGrid below
(left', focus', right') <- focusListAt (length left) line
return $ GridZipper { above = Grid above'
, below = Grid below'
, left = left'
, right = right'
, focus = focus' }
goDown :: GridZipper a -> Maybe (GridZipper a)
goDown GridZipper{..} = do
let (line : below') = unGrid below
let above' = (reverse left ++ focus : right) : unGrid above
(left', focus', right') <- focusListAt (length left) line
return $ GridZipper { above = Grid above'
, below = Grid below'
, left = left'
, right = right'
, focus = focus' }
最后,我还添加了几个辅助函数来生成网格(每个单元格包含一对坐标)和实例,以便能够在终端中显示网格和拉链。
mkGrid :: Int -> Int -> Grid (Int, Int)
mkGrid m n = Grid $ [ zip (repeat i) [0..n-1] | i <- [0..m-1] ]
instance Show a => Show (Grid a) where
show = concatMap (('\n' :) . concatMap show) . unGrid
instance Show a => Show (GridZipper a) where
show GridZipper{..} =
concat [ show above, "\n"
, concatMap show (reverse left)
, "\x1B[33m[\x1B[0m", show focus, "\x1B[33m]\x1B[0m"
, concatMap show right
, show below ]
main
创建一个大小为5 * 10的小网格,聚焦于坐标(2,3)处的元素并稍微移动一下。
main :: IO ()
main = do
let grid1 = mkGrid 5 10
print grid1
let grid2 = fromJust $ focusGridAt 2 3 grid1
print grid2
print $ goLeft =<< goLeft =<< goDown =<< goDown grid2
答案 1 :(得分:0)
实现无限网格的简单解决方案包括使用由坐标对索引的哈希表。
以下是不检查整数溢出的示例实现:
type 'a cell = {
x: int; (* position on the horizontal axis *)
y: int; (* position on the vertical axis *)
value: 'a;
}
type 'a grid = {
cells: (int * int, 'a cell) Hashtbl.t;
init_cell: int -> int -> 'a;
}
let create_grid init_cell = {
cells = Hashtbl.create 10;
init_cell;
}
let hashtbl_get tbl k =
try Some (Hashtbl.find tbl k)
with Not_found -> None
(* Check if we have a cell at the given relative position *)
let peek grid cell x_offset y_offset =
hashtbl_get grid.cells (cell.x + x_offset, cell.y + y_offset)
(* Get the cell at the given relative position *)
let get grid cell x_offset y_offset =
let x = cell.x + x_offset in
let y = cell.y + y_offset in
let k = (x, y) in
match hashtbl_get grid.cells k with
| Some c -> c
| None ->
let new_cell = {
x; y;
value = grid.init_cell x y
} in
Hashtbl.add grid.cells k new_cell;
new_cell
let left grid cell = get grid cell (-1) 0
let right grid cell = get grid cell 1 0
let down grid cell = get grid cell 0 (-1)
(* etc. *)