F # load tree from CSV file

I am trying to learn F # and I really like what I have seen so far. I am trying to implement some C # code, an F # way of thinking as an exercise, to practice and learn.

I really apologize if the answer was given before, but I could not find the answer to solve all my problems.

We have a sales structure in which we have sales managers and ordinary sellers. An observer may or may not have a dispatcher.

All sales data comes from another system in CSV format. While reading a record, we donโ€™t know if SalesPerson has reports or not.

I don't seem to understand how to load a tree into the immutable world of F #. I am sure there is a way.

Our simplified deprecated C # code defines (Enligsh translation)

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; } } 

This is a simplified version of the code. However, the problem remains the same: how to load the tree?

I came up with the following type F #

 type SalesPerson = { Id : int Supervisor : SalesPerson option Reports : List<SalesPerson> option PersonalSales : double GroupSales : double } 

I'm not even sure if this is a way to determine the type of F #.

My problems:

  • The supervisor points to another SalesPerson and it is immutable. If it is replaced with a new one (because immutable data works), the link will break.
  • The reports are unchanged. I think I could use C # List<T> , but I'm not sure if this is the way F #.
  • Supervisor Reports do not match a Supervisor record. They can come across X lines below, and not all together. However, the system ensures that the Supervisor record is always available before any report record for this supervisor.
  • How to update the calculated GroupSales field after loading the tree.

An example CSV file would look like this:

 1,,100.00 2,,110.00 3,1,50.00 4,1,75.00 5,2,80.00 6,,92.00 

So:

 1 -> 2 reports 2 -> 1 report 3,4,5,6 -> No reports 

I would really appreciate any โ€œlightโ€ you could tell about these issues.

Thanks...

+5
source share
2 answers

It becomes a little easier if you divide the tree structure into a separate type. The usual approach to immutable trees is something like this:

 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 

Bottom line: group the data by parents, and then use the recursive function to create a tree, starting at the top nodes (those that don't have a parent). Since we have built all descendant nodes, we are completing the construction of any given node, we can use descendant data to calculate GroupSales .

You cannot access the parent directly from the given node, but you have a parent id. As long as you keep the original salesPeople list, you can get the full details for any parent id.

One of the advantages of having a generic Tree type is that you can use reusable functions (e.g. map, fold, tryFind) that work on any tree.

+4
source

@TheQuickBrownFox did a good job modeling your domain.

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

Using a record / class to represent Tree is an OO way to handle things, which may be easier to understand if you have little experience with FP.

I want to show you a more functional approach .

 type 'a Tree = | Leaf of 'a | Branch of 'a * 'a Tree list 

Leaf nodes are SalesPerson at the end of the hierarchy. Supervisor and all their minions are represented by Branch es and go all the way.

 type SalesMember = | SalesPerson of Employee | Supervisor of Employee * SalesMember List 

A Tree also has a root node - there can be only one - you can easily write a function to convert rawData to something like:

 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 

To implement GroupSales , you can extend Supervisor .

 type SalesMember = | SalesPerson of emp : Employee | Supervisor of emp : Employee * reports : List<SalesMember> * groupSales : double 

One way to instantiate this tree is to convert the tree from the getTree function. Processing, transforming and optimizing trees is a wide topic, as always for pleasure and profit - a good place to start your journey.

UPDATE - GroupSales

To keep it simple, I will only use one discriminated union, setting GroupSales to zero on first start. However, you can easily adapt the code to convert to another type of 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 

A less naive implementation converts / calc GroupSales from bottom to top rather than top to bottom , allowing you to use the already calculated GroupSales .

+2
source

Source: https://habr.com/ru/post/1271493/


All Articles