F#从CSV文件加载树

Cra*_*ard 5 tree f#

我正在努力学习F#,我真的很喜欢到目前为止我所看到的.我正在尝试将一些C#代码实现为F#思维方式,作为练习和学习的练习.

如果之前已经回答过,我真的很抱歉,但我找不到解决我所有问题的答案.

我们拥有销售队伍结构,我们有销售主管和普通销售人员.主管可能有也可能没有主管.

所有销售数据均来自CSV格式的其他系统.在阅读记录时,我们不知道SalesPerson是否有报告.

我似乎不明白如何在F#的不可变世界中加载树.我相信有办法.

我们简化的遗留C#代码定义(翻译成Enligsh)

public class SalesPerson
{
    public int Id { get; set; }
    public SalesPerson Supervisor { get; set; }
    public List<SalesPerson> Reports { get; private set; } = new List<SalesPerson>();
    public PersonalSales double { get; set; }
    public GroupSales double { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是代码的过度简化版本.但问题仍然存在:如何加载树?

我提出了以下F#类型

type SalesPerson = {
    Id : int
    Supervisor : SalesPerson option
    Reports : List<SalesPerson> option
    PersonalSales : double
    GroupSales : double
}
Run Code Online (Sandbox Code Playgroud)

我甚至不确定这是否是定义类型的F#方式.

我的问题是:

  1. 主管指向另一个SalesPerson,它是不可变的.如果它被新的替换(因为不可变数据有效),引用将会中断.
  2. 报告是不可改变的.我认为我可以使用C#,List<T>但我不确定这是否是F#方式.
  3. 主管的报告记录不遵循主管记录.它们可能会在下方出现X行,而不是所有行.但是,系统确保Supervisor记录始终位于该主管的任何报告记录之前.
  4. 如何在加载树后更新GroupSales计算字段.

示例CSV文件如下所示:

1,,100.00
2,,110.00
3,1,50.00
4,1,75.00
5,2,80.00
6,,92.00
Run Code Online (Sandbox Code Playgroud)

所以:

1 -> 2 reports
2 -> 1 report
3,4,5,6 -> No reports
Run Code Online (Sandbox Code Playgroud)

我真的很感激你可能会对这些问题发出任何"光明".

谢谢...

The*_*Fox 4

如果将树结构分成单独的类型,这会变得容易一些。不可变树的通常方法是这样的:

let rawData =
    [ 1, None, 100.00
      2, None, 110.00
      3, Some 1, 50.00
      4, Some 1, 75.00
      5, Some 2, 80.00
      6, None, 92.00 ]

let dataMap = rawData |> List.groupBy (fun (_, superId, _) -> superId) |> Map
let getChildrenData personId = dataMap |> Map.tryFind personId |> Option.defaultValue []

type Tree<'a> = { Data: 'a; Children : List<Tree<'a>> }

type SalesPerson = { Id : int; SupervisorId : int option; PersonalSales : double; GroupSales : double }

let salesPersonTree =
    let rec buildNode (id, superId, sales) =
        let children = getChildrenData (Some id) |> List.map buildNode
        let groupSales = (children |> List.sumBy (fun x -> x.Data.GroupSales)) + sales
        { Data = { Id = id; SupervisorId = superId; PersonalSales = sales; GroupSales = groupSales }
          Children = children }

    let topLevelItems = getChildrenData None
    topLevelItems |> List.map buildNode
Run Code Online (Sandbox Code Playgroud)

总之:按父节点对数据进行分组,然后使用递归函数从顶部节点(没有父节点的节点)开始构建树。因为我们已经构建了所有后代节点,所以我们完成了任何给定节点的构建,我们可以使用后代数据来计算GroupSales

您无法直接从给定节点访问父节点,但您确实拥有父节点 ID。只要保留原始salesPeople列表,您就可以获得任何给定父 ID 的完整数据。

拥有通用树类型的一个优点是您可以拥有适用于任何树的可重用函数(例如map、fold、tryFind)。