functional-programming - 在 F# 中更新/多个/更改(有点)二叉树的功能/合理方法
问题描述
=========
我在哪里
我目前正在编写一个我以前用 C# 编写的(非常)小游戏。总的来说,我在 F# 和函数式编程方面没有太多经验,因为我在今年夏末或多或少地偶然发现了它。否则,我至少有几年的一般编程经验和对“比基本更高级”的概念和模式的理解。
游戏逻辑
在这个游戏中,玩家将在地图上放置“节点”并将它们连接起来,如上图所示。一些节点(标记为 IN*)将“输出”一个常数值,而其他节点(ADD 和 MULT)将接受两个输入并产生一个输出,例如中间节点接受 5 和 9 并将它们相加,输出 14。
*有比“IN”更好的名字建议吗?它们将始终代表数字 0<n<10。这些数字有英文名称吗?(在瑞典语中是“ental”、“one-numbers”)
如图所示,玩家还可以将一个节点的输出连接到多个(无限)节点。一个节点甚至可以将其输出连接到另一个节点的两个输入。因此,“(有点)二叉树”(这可能有名字吗?)。节点永远无法连接以产生循环,因此如果 A 输出到 B,则 B 不能输出到 A。
重要的是,这种结构可以随时以任何方式发生变化。不能保证所有节点都连接到一个公共根。可能存在多个“树”,然后可以随时连接。
先前的解决方案(oop)
当我之前在 C# 中解决了这个问题时,我有一个节点对象列表。所有节点对象都有两个可能的输入和一个输出。当我更新这个时,我想我寻找没有输出的节点来确定所有根。然后我,在当前节点的输入上递归调用类似.GetValue()
或的东西.Update()
,然后对其输入执行相同的操作,依此类推。虽然并不完美,这要归功于这种“(有点)二叉树”结构,但它确实有效。
问题/主要问题
我将如何在 F# 中以更实用的方式实现这一点?我认为我需要一个列表,至少更接近 UI,但由于我不认为类之类的引用类型是“功能方式”,我宁愿为每个节点使用 Records。我知道我理论上可以将引用节点的 ID 存储在节点的输入字段中。这似乎有点不安全和笨重,因为没有任何东西(据我所知)可以保证存储的 ID 实际匹配现有节点,并且所有节点的 ID 都是唯一的。
我不认为(至少不容易)我可以存储其他节点的副本而不是 ID,因为节点将具有定义其行为的一流函数,并且据我所知,它们无法进行相等性比较.
是否有任何既定的功能方法来处理这种结构,或者是否有另一种更好的方法来解决这个问题?我已经考虑过存储连接,但我认为这不会有任何帮助。
当然,我非常感谢任何帮助和建议!如果我试图解决错误的问题,请告诉我!谢谢!
解决方案
从这个问题看来,所要求的似乎是所谓的直接无环图。
这是分布式计算中的一种常见模式,并用于 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
推荐阅读
- javascript - 动态添加元素时滚动跟随
- arrays - 使用两个数组进行花括号扩展以进行每个组合
- flutter - 当我的 GestureDetector 是 ListTile 的尾随时,如何使我的 GestureDetector 可点击?
- memory-management - 实现内置特征时类型不匹配,但根据文档和 AllocRef 的来源应该是正确的
- python - 在pyplot中切断部分x轴
- jetbrains-ide - JetBrains GoLand 中的 Alt+Enter 根本不显示导入选项
- swift - SwiftUI - 如何根据列表为文本设置不同的值
- javascript - 反转取自事件值的整数:仅适用于第一个事件
- pchart - pChart drawStackedBarChart 设置条形大小属性
- sql-server - 使用 SSIS 包将日期时间戳列添加到最终目标表