Data structures 如何用函数式语言实现网格?

Data structures 如何用函数式语言实现网格?,data-structures,functional-programming,ocaml,lazy-evaluation,self-reference,Data Structures,Functional Programming,Ocaml,Lazy Evaluation,Self Reference,我在函数式语言中以不同的方式实现常量网格。一个完美的解决方案应该以每一步的伪常量时间提供遍历,而不是使用命令式构造(惰性是可以的)。仍然欢迎不完全满足这些要求的解决方案 我的建议基于这样的四路链接节点 一个基本的操作是构造一个给定大小的网格。似乎该操作将确定类型,即哪些方向将是惰性的(显然,没有惰性就无法实现此数据结构)。因此,我建议(在OCaml中) 参照顺序为:左、上、右、下。左上角暂停。然后按对角线方向构建网格 下面是一个make_grid函数,它使用坐标元组作为节点值来构造给定大小的

我在函数式语言中以不同的方式实现常量网格。一个完美的解决方案应该以每一步的伪常量时间提供遍历,而不是使用命令式构造(惰性是可以的)。仍然欢迎不完全满足这些要求的解决方案

我的建议基于这样的四路链接节点

一个基本的操作是构造一个给定大小的网格。似乎该操作将确定类型,即哪些方向将是惰性的(显然,没有惰性就无法实现此数据结构)。因此,我建议(在OCaml中)

参照顺序为:左、上、右、下。左上角暂停。然后按对角线方向构建网格

下面是一个
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]
它构造一个节点,给定当前位置
x
y
、前一个对角线
ls
的暂停元素列表、前一个对角线
urs
的暂停元素列表。名称
ls
来源于
ls
上的第一个元素是当前节点的左邻居。构建下一条对角线需要
urs
列表

build\u urs
功能继续在右上角对角线上构建下一个节点,以暂停方式通过当前节点。左邻居和上邻居来自
ls
,右邻居和下邻居可以通过对角线上的下一个节点访问

注意,我在
urs
ls
列表中放了一堆
GNil
s。这样做是为了始终确保
build\u-ur
build\u-dl
可以使用这些列表中的至少两个元素

构建ur“>

build\u dl
函数的工作原理类似

对于这样一个简单的数据结构,这个实现似乎过于复杂。事实上,我很惊讶它能工作,因为我在编写它的时候受到了信念的驱使,无法完全理解它为什么能工作。因此,我想知道一个更简单的解决方案

我正在考虑按行构建网格。这种方法边界情况较少,但我无法消除在不同方向构建后续行的需要。这是因为当我以一行结束并想从头开始构建另一行时,我必须知道当前ro中第一个节点的向下节点w、 在我从当前函数调用返回之前,我似乎不知道这一点。如果我不能消除双向性,我将需要两个内部节点构造:一个具有挂起的左侧和顶部,另一个具有挂起的右侧和顶部


另外,这里是这个实现的要点以及省略的函数:

如果您想要一个功能解决方案,您正在寻找的数据结构是a。我已经用Haskell编写了其余的代码,因为我觉得它更符合我的口味,但它很容易移植到OCaml。没有交叉注释

{-#语言记录通配符#-}
模块网格在哪里
导入数据,也许吧
我们可以从理解列表的数据结构开始:你可以把拉链想象成列表深处的一个指针。你可以看到watherver在你指向的元素的左边,然后是你指向的元素,最后是右边的任何东西

类型a=([a],a[a])
给定一个列表和一个整数
n
,您可以将焦点放在位于
n
位置的元素上。当然,如果
n
大于列表的长度,那么您就失败了。需要注意的一点是,列表的左侧部分是向后存储的:因此,在con中可以将焦点移到左侧持续时间。向右移动也是如此

focusListAt::Int->[a]->可能(lista)
focusListAt=go[]
哪里
没什么
go acc 0(hd:tl)=仅(acc,hd,tl)
go acc n(hd:tl)=go(hd:acc)(n-1)tl
现在让我们转到网格。网格将只是一个行列表

newtype Grid a=Grid{unGrid::[[a]]}
网格
的拉链现在由一个网格给出,该网格表示当前焦点上方的一切
,另一个网格表示当前焦点下方的一切
,还有一个列表拉链(高级:请注意,这看起来有点像嵌套的列表拉链,可以用更通用的术语重新定义)

数据网格=
gridzippers{over::Grid a
,下图::网格a
,左::[a]
,右::[a]
,焦点::a}
通过首先聚焦于右边的行,然后聚焦于右边的元素,我们可以将
网格
聚焦于某些坐标
x
y

focusGridAt::Int->Int->Grid a->Maybe(Grid a)
聚焦网格x y g=do
(前、行、后)没有
(hd:tl)->只要$g{focus=hd,left=tl,right=focus:right}
goRight::Gridzippers a->Maybe(Gridzippers a)
戈赖特g@GridZipper{..} =
案件权利
[]->什么都没有
(hd:tl)->只要$g{focus=hd,left=focus:left,right=tl}
在上移或下移时,我们必须小心一点,因为我们需要将注意力集中在我们在新行中留下的那一行的正上方(或下方)。我们还必须将我们关注的前一行重新组合到一个好的旧列表中(通过将反向的
添加到
焦点:右

goUp::gridzippers a->Maybe(gridzippers a)
goUp gridzippers{..}=do
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 :
  int -> int ->
  (int * int) grid Lazy.t list -> 
  (int * int) grid Lazy.t list -> (int * int) grid
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. *)