我正在努力学习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; }
}
这是代码的过度简化版本。但是,问题仍然存在:如何加载树?
我提出了以下F#类型
type SalesPerson = {
Id : int
Supervisor : SalesPerson option
Reports : List<SalesPerson> option
PersonalSales : double
GroupSales : double
}
我甚至不确定这是否是定义类型的F#方式。
我的问题是:
List<T>
,但我不确定这是否是F#方式。示例CSV文件如下所示:
1,,100.00
2,,110.00
3,1,50.00
4,1,75.00
5,2,80.00
6,,92.00
所以:
1 -> 2 reports
2 -> 1 report
3,4,5,6 -> No reports
我真的很感激任何&#34;光&#34;你可能会对这些问题大放异彩。
...谢谢
答案 0 :(得分: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
总结:通过父对数据进行分组,然后使用递归函数从顶部节点(没有父节点的节点)开始构建树。因为我们构建了所有后代节点,所以我们完成了任何给定节点的构建,我们可以使用后代数据来计算GroupSales
。
您无法直接从给定节点访问父级,但您确实拥有父级ID。只要保留原始salesPeople
列表,就可以获得任何给定父ID的完整数据。
拥有通用树类型的一个好处是,您可以在任何树上使用可重复使用的函数(例如map,fold,tryFind)。
答案 1 :(得分:2)
@TheQuickBrownFox在为您的域建模方面做得很好。
type Employee = { Id : int; SupervisorId : int option; PersonalSales : double }
使用记录/类来表示Tree
是处理事物的OO方式,
当你没有很多FP经验时,这可能更容易掌握。
我想告诉你a more functional approach。
type 'a Tree =
| Leaf of 'a
| Branch of 'a * 'a Tree list
Leaf
个节点是层次结尾的SalesPerson
个。 Supervisor
及其所有爪牙都由Branch
es表示并一直向上。
type SalesMember =
| SalesPerson of Employee
| Supervisor of Employee * SalesMember List
Tree
也有一个根节点 - 只能有一个 - 您可以轻松编写一个函数来将rawData
转换为:
let rawData =
[ 0, None, 0.0
1, Some 0, 100.00
2, Some 0, 110.00
3, Some 1, 50.00
4, Some 1, 75.00
5, Some 2, 80.00
6, Some 0, 92.00 ]
let flatList =
rawData
|> List.map (fun (id, superId, sales) ->
{Id = id; SupervisorId = superId; PersonalSales = sales})
let getTree salesPeople =
// To do : validate root
let root = salesPeople |> List.find (fun p -> p.SupervisorId = None)
let children supervisorId =
salesPeople |> List.filter (fun p -> p.SupervisorId = Some supervisorId)
let rec loop employee =
match children employee.Id with
| [] -> SalesPerson employee
| list -> Supervisor (employee, List.map loop list)
loop root
let salesForce = getTree flatList
要实施GroupSales
,您可以展开Supervisor
。
type SalesMember =
| SalesPerson of emp : Employee
| Supervisor of emp : Employee * reports : List<SalesMember> * groupSales : double
构建此树实例的一种方法是从getTree
函数转换树。处理,转换和优化树木是一个广泛的主题,因为for fun and profit始终是开始旅程的好地方。
更新 - 群组销售
为了简单起见,我只使用一个 Discriminated Union ,在第一次运行时将GroupSales
设置为零。但是,您可以轻松地调整代码以转换为另一种类型的Tree
。
type Employee = { Id : int; SupervisorId : int option; PersonalSales : double }
type GroupSales = double
type SalesMember =
| SalesPerson of Employee
| Supervisor of Employee * SalesMember List * GroupSales
let rawData =
[ 0, None, 0.
1, Some 0, 100.00
2, Some 0, 110.00
3, Some 1, 50.00
4, Some 1, 75.00
5, Some 2, 80.00
6, Some 0, 92.00 ]
let flatList =
rawData
|> List.map (fun (id, superId, sales) ->
{Id = id; SupervisorId = superId; PersonalSales = sales})
let getTree salesPeople =
let root = salesPeople |> List.find (fun p -> p.SupervisorId = None)
let children supervisorId =
salesPeople |> List.filter (fun p -> p.SupervisorId = Some supervisorId)
let rec loop employee =
match children employee.Id with
| [] -> SalesPerson employee
| list -> Supervisor (employee, List.map loop list, 0.)
loop root
let transformTree root =
let rec getGroupSales = function
| SalesPerson emp
-> emp.PersonalSales
| Supervisor (sup, reports, _)
-> sup.PersonalSales + List.sumBy getGroupSales reports
let rec loop = function
| Supervisor (sup, reports, _) as mem
-> Supervisor (sup, List.map loop reports, getGroupSales mem)
| salesPerson -> salesPerson
loop root
let salesForce =
flatList
|> getTree
|> transformTree
不太天真的实现会转换/计算GroupSales
bottom-up instead of top-down,允许您使用已计算的GroupSales
。