OCaml:将JSON解析为循环类型

时间:2015-12-19 23:17:35

标签: data-structures ocaml adventure

此问题与我之前询问的another question有关。

我正在从JSON文件中读取数据并尝试将它们解析为我所创建的数据类型。

{
  "rooms":
  [
    {
      "id": "room1",
      "description": "This is Room 1.  There is an exit to the north.\nYou should drop the white hat here.",
      "items": ["black hat"],
      "points": 10,
      "exits": [
        {
          "direction": "north",
          "room": "room2"
        }
      ],
      "treasure": ["white hat"]
    },
    {
      "id": "room2",
      "description": "This is Room 2.  There is an exit to the south.\nYou should drop the black hat here.",
      "items": [],
      "points": 10,
      "exits": [
        {
          "direction": "south",
          "room": "room1"
        }
      ],
      "treasure": ["black hat"]
    }
  ]  
}

我的用户定义的房间类型是:

type room = {
  room_id          :  int ;
  room_description :  string ;
  room_items       :  item list ;
  room_points      :  int ;
  room_exits       :  exit list ;
  room_treasure    :  item list ;
}
and exit = direction * room

但是,房间有一个“退出”字段,它本身就是一个“房间”类型。然后,当我尝试为 room1 创建记录时,我首先需要定义 room2 ,但为了定义 room2 ,我需要知道room1 。这似乎是一种循环型。

任何人都可以帮我吗?

2 个答案:

答案 0 :(得分:2)

如果你坚持OCaml的不可变和渴望的子集,那么就没有真正的方法来构建任意的循环结构。问题正如你所陈述的那样。

可以使用let rec构建循环结构的特定示例,但我不相信这可以扩展到构建任意结构(例如)解析JSON。

您可以通过删除不可变数据的要求来解决问题。如果您将其他房间的链接转换为OCaml引用(可变字段),您可以构建循环结构,就像在JavaScript的命令部分中一样。

实现此功能的一种方法可能是使用room_exits的数组而不是列表。 OCaml数组是可变的。

这里有一些代码可以在3个节点上创建完整的图形(对于只包含邻居节点的普通节点类型):

# type node = { nabes: node array };;
type node = { nabes : node array; }
# type graph = node list;;
type graph = node list
# let z = { nabes = [||] };;
val z : node = {nabes = [||]}
# let temp = Array.init 3 (fun _ -> { nabes = Array.make 2 z});;
val temp : node array =
  [|{nabes = [|{nabes = [||]}; {nabes = [||]}|]};
    {nabes = [|{nabes = [||]}; {nabes = [||]}|]};
    {nabes = [|{nabes = [||]}; {nabes = [||]}|]}|]
# temp.(0).nabes.(0) <- temp.(1);;
- : unit = ()
# temp.(0).nabes.(1) <- temp.(2);;
- : unit = ()
# temp.(1).nabes.(0) <- temp.(0);;
- : unit = ()
# temp.(1).nabes.(1) <- temp.(2);;
- : unit = ()
# temp.(2).nabes.(0) <- temp.(0);;         
- : unit = ()
# temp.(2).nabes.(1) <- temp.(1);;
- : unit = ()
# let k3 : graph = Array.to_list temp;;
val k3 : graph =
  [{nabes =
     [|{nabes = [|<cycle>; {nabes = [|<cycle>; <cycle>|]}|]};
       {nabes = [|<cycle>; {nabes = [|<cycle>; <cycle>|]}|]}|]};
   {nabes =
     [|{nabes = [|<cycle>; {nabes = [|<cycle>; <cycle>|]}|]};
       {nabes = [|{nabes = [|<cycle>; <cycle>|]}; <cycle>|]}|]};
   {nabes =
     [|{nabes = [|{nabes = [|<cycle>; <cycle>|]}; <cycle>|]};
       {nabes = [|{nabes = [|<cycle>; <cycle>|]}; <cycle>|]}|]}]

您还可以通过中间结构链接来解决问题。例如,您可以使用将房间名称映射到房间的字典。然后您到其他房间的链接可以使用名称(而不是直接链接到OCaml值)。我过去使用过这种方法,效果很好。 (事实上​​,这是你的JSON隐式工作的方式。)

答案 1 :(得分:1)

这就是为什么在上一个回答中我将函数room_exits放入Game接口,而不是放入Room。这背后的直觉是房间出口,即其他房间,不是房间的一部分。如果你定义一些结构,如“房间是墙壁,宝藏和其他房间”,那么你定义的东西不仅仅是一个房间,这基本上意味着你正在定义整个迷宫。所以房间,只是一个房间,即它的内容。房间的连接方式是Maze。 (我在之前的回答中使用了Game,但也许Maze是一个更好的名字。

总而言之,在您的特定情况下,您只需要从房间数据表示中删除对其他房间的引用,并将迷宫信息存储为maze(或game)内的关联容器数据结构:

 type exits = (dir * room) list

 type maze = {
   ...
   entry : room;
   rooms : exits Room.Map.t
 }

或者甚至更精确,您可以使用Dir.Map作为关联容器,而不是关联列表:

type exits = room Dir.Map.t

后一种表示保证每个方向不超过一个房间。

注意:上述定义假设Room实现了Comparable接口,并且您正在使用Core库。 (我想你是因为我记得从课程页面到RWO的链接)。要实现类似的接口,您需要实现compare函数和Sexpable接口。使用类型生成器很容易,基本上它看起来像这样:

module Room = struct 
  type t = {
    name : string;
    treasures : treasure list;
    ...
  } with compare, sexp

  include Comparable.Make(struct 
    type nonrec t = t with compare, sexp
  end)
end

with compare, sexp会自动生成compare函数,以及sexp_of_t仿函数实现所需的t_of_sexpComparable.Make函数对Comparable界面。

注意:如果此时太多,那么您可以使用String.Map.t数据结构,并按房间名称执行查找。这不是一个坏主意。