Functional/reasonable 在 F# 中更新 a/multiple/changing(有点)二叉树的方法
Functional/reasonable method for updating a/multiple/changing (kinda) binary tree in F#
=========
我在哪里
我目前正在编写一个(非常)小的游戏,我以前用 C# 编写过它。我在 F# 和一般的函数式编程方面没有太多经验,因为我在今年夏末或多或少地偶然发现了它。否则我至少有几年的一般编程经验和对"more advanced than basic"-概念和模式的理解。
游戏逻辑
很好很好。先给大家看一张图吧。
在这个游戏中,玩家将 "nodes" 放置在地图上,并将它们连接起来,如上图所示。一些节点(标记为 IN*)将 "output" 一个常数值,而其他节点(ADD 和 MULT)将接受两个输入并产生一个输出,例如中间节点接受 5 和 9 并将它们相加,输出 14。
*有什么比"IN"更好的名字建议吗?它们将始终代表数字 0
如图所示,玩家还可以将一个节点的输出连接到多个(无限)节点。一个节点甚至可以将其输出连接到另一个节点的两个输入。因此,“(有点)二叉树”(可能有这个名字吗?)。节点永远不能连接产生循环,所以如果A输出到B,B就不能输出到A。
重要的是,这个结构可以随时以任何方式改变。不能保证所有节点都连接到一个公共根。可能存在多个 "trees",然后可以随时连接。
先前的解决方案(oop)
当我之前在 C# 中解决这个问题时,我有一个节点对象列表。所有节点对象都有两个可能的输入和一个输出。当我更新这个时,我想我寻找没有输出的节点来确定所有根。然后,我在当前节点的输入上递归调用 .GetValue()
或 .Update()
之类的东西,然后对其输入执行相同的操作,依此类推。虽然不完美,多亏了这种“(有点)二叉树”结构,但它确实有效。
问题/主要问题
我如何在 F# 中以更实用的方式实现它?我想我需要一个列表,至少更接近 UI,但我不认为 classes 之类的引用类型是 "the functional way" 我宁愿为每个使用 Records节点。我知道理论上我可以在节点的输入字段中存储引用节点的 ID。这似乎有点不安全和笨重,因为没有什么(据我所知)可以保证存储的 ID 实际上匹配现有节点,并且所有节点的 ID 都是唯一的。
我不认为(至少不容易)我可以存储其他节点的副本而不是 ID,因为节点将具有 first-class 函数来定义它们的行为,并且,至于我知道,他们不能平等地比较。
是否有任何既定的功能方法来处理这种结构,或者是否有另一种更好的方法来处理这种结构?我考虑过存储连接,但我认为这不会有任何帮助。
当然,我非常感谢您提供任何帮助和建议!如果我试图解决错误的问题,请告诉我!谢谢!
从问题来看,似乎要求的是所谓的 direct acyclic graph。
这是分布式计算中的一种常见模式,用于 AirFlow、Luigi、Spark 和 Dataflows 等工具。
树很适合处理,但并非所有问题都可以用树来表示。在 FP 中使用图形有点糟糕。有向无环图是中间的东西,可以很好地使用。
下面是在 F# 中表示 DAG 的简单方法。我认为这对于问题中概述的场景来说太简单了,但也许它可以帮助@Evil_Bengt 朝着正确的方向发展。
module FsDag =
type ReducerOperation = Add|Multiply
type DAG =
// Input is a DAG node containing constant input
| Input of int
// Reducer DAG nodes aggregates a list of DAG inputs
// using a reducer operation. Reducer is a common expression
// for operations that operates on collections and produce value.
// Common reducer functions are List.reduce, List.fold and List.sum
| Reducer of ReducerOperation*DAG list
// "Constructor" functions for DAG nodes
let input i = Input i
let reducer rop dags = Reducer (rop, dags)
let add dags = reducer Add dags
let multiply dags = reducer Multiply dags
// Evals a DAG into an int value
let rec eval n =
match n with
| Input i -> i
| Reducer (op, sources) ->
let r, z =
match op with
| Add -> ( + ), 0
| Multiply -> ( * ), 1
sources |> List.map eval |> List.fold r z
open FsDag
[<EntryPoint>]
let main argv =
let i0 = input 5
let i1 = input 9
let dag = multiply [(add [i0; i1]); i1]
printfn "DAG %A" dag
printfn "Eval %A" (eval dag)
0
=========
我在哪里
我目前正在编写一个(非常)小的游戏,我以前用 C# 编写过它。我在 F# 和一般的函数式编程方面没有太多经验,因为我在今年夏末或多或少地偶然发现了它。否则我至少有几年的一般编程经验和对"more advanced than basic"-概念和模式的理解。
游戏逻辑
很好很好。先给大家看一张图吧。
在这个游戏中,玩家将 "nodes" 放置在地图上,并将它们连接起来,如上图所示。一些节点(标记为 IN*)将 "output" 一个常数值,而其他节点(ADD 和 MULT)将接受两个输入并产生一个输出,例如中间节点接受 5 和 9 并将它们相加,输出 14。
*有什么比"IN"更好的名字建议吗?它们将始终代表数字 0
如图所示,玩家还可以将一个节点的输出连接到多个(无限)节点。一个节点甚至可以将其输出连接到另一个节点的两个输入。因此,“(有点)二叉树”(可能有这个名字吗?)。节点永远不能连接产生循环,所以如果A输出到B,B就不能输出到A。
重要的是,这个结构可以随时以任何方式改变。不能保证所有节点都连接到一个公共根。可能存在多个 "trees",然后可以随时连接。
先前的解决方案(oop)
当我之前在 C# 中解决这个问题时,我有一个节点对象列表。所有节点对象都有两个可能的输入和一个输出。当我更新这个时,我想我寻找没有输出的节点来确定所有根。然后,我在当前节点的输入上递归调用 .GetValue()
或 .Update()
之类的东西,然后对其输入执行相同的操作,依此类推。虽然不完美,多亏了这种“(有点)二叉树”结构,但它确实有效。
问题/主要问题
我如何在 F# 中以更实用的方式实现它?我想我需要一个列表,至少更接近 UI,但我不认为 classes 之类的引用类型是 "the functional way" 我宁愿为每个使用 Records节点。我知道理论上我可以在节点的输入字段中存储引用节点的 ID。这似乎有点不安全和笨重,因为没有什么(据我所知)可以保证存储的 ID 实际上匹配现有节点,并且所有节点的 ID 都是唯一的。
我不认为(至少不容易)我可以存储其他节点的副本而不是 ID,因为节点将具有 first-class 函数来定义它们的行为,并且,至于我知道,他们不能平等地比较。
是否有任何既定的功能方法来处理这种结构,或者是否有另一种更好的方法来处理这种结构?我考虑过存储连接,但我认为这不会有任何帮助。
当然,我非常感谢您提供任何帮助和建议!如果我试图解决错误的问题,请告诉我!谢谢!
从问题来看,似乎要求的是所谓的 direct acyclic graph。
这是分布式计算中的一种常见模式,用于 AirFlow、Luigi、Spark 和 Dataflows 等工具。
树很适合处理,但并非所有问题都可以用树来表示。在 FP 中使用图形有点糟糕。有向无环图是中间的东西,可以很好地使用。
下面是在 F# 中表示 DAG 的简单方法。我认为这对于问题中概述的场景来说太简单了,但也许它可以帮助@Evil_Bengt 朝着正确的方向发展。
module FsDag =
type ReducerOperation = Add|Multiply
type DAG =
// Input is a DAG node containing constant input
| Input of int
// Reducer DAG nodes aggregates a list of DAG inputs
// using a reducer operation. Reducer is a common expression
// for operations that operates on collections and produce value.
// Common reducer functions are List.reduce, List.fold and List.sum
| Reducer of ReducerOperation*DAG list
// "Constructor" functions for DAG nodes
let input i = Input i
let reducer rop dags = Reducer (rop, dags)
let add dags = reducer Add dags
let multiply dags = reducer Multiply dags
// Evals a DAG into an int value
let rec eval n =
match n with
| Input i -> i
| Reducer (op, sources) ->
let r, z =
match op with
| Add -> ( + ), 0
| Multiply -> ( * ), 1
sources |> List.map eval |> List.fold r z
open FsDag
[<EntryPoint>]
let main argv =
let i0 = input 5
let i1 = input 9
let dag = multiply [(add [i0; i1]); i1]
printfn "DAG %A" dag
printfn "Eval %A" (eval dag)
0