此问题与我之前询问的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 。这似乎是一种循环型。
任何人都可以帮我吗?
答案 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_sexp
,Comparable.Make
函数对Comparable
界面。
注意:如果此时太多,那么您可以使用String.Map.t
数据结构,并按房间名称执行查找。这不是一个坏主意。