2017-09-04 6 views
5

私はF#を学びたいと思っています。私はこれまで見たことが本当に好きです。 私はいくつかのC#コードを練習し、学ぶための練習としてF#の考え方を実装しようとしています。F#CSVファイルからツリーをロード

私は本当に謝罪していますが、私のすべての問題を解決する答えは見つけられませんでした。

私たちには、販売管理者と一般の販売担当者がいる販売組織があります。スーパーバイザーは、スーパーバイザーを持っていてもいなくてもよい。

すべての販売データは、別のシステムからのもので、CSV形式です。 レコードを読み取っている時点で、SalesPersonにレポートがあるかどうかわかりません。

私はF#の不変の世界で木を読み込む方法を理解していないようです。私は道があると確信しています。

(Enligsh翻訳)我々の単純化されたレガシーC#コードが定義

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#型を思い付きました。

私の問題は、次のとおりです。

  1. スーパーバイザが別の販売員を指し、それは不変です。新しいものに置き換えられた場合(不変のデータが機能するため)、参照は中断されます。
  2. レポートは不変です。私はC#のList<T>を使用することができると思いますが、それがF#の方法であるかどうかはわかりません。
  3. スーパーバイザのレポートのレコードは、スーパーバイザレコードの後に​​はありません。彼らは、X個の行が下に来るかもしれません。ただし、スーパーバイザレコードがそのスーパーバイザのレポートレコードの前に必ず来るようにします。
  4. ツリーのロード後にGroupSales計算フィールドを更新する方法。

サンプル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 

私は本当にあなたがこれらの問題に輝く可能性のある "光" をいただければ幸いです。あなたが別の型にツリー構造を分離した場合

おかげで...

答えて

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の全データを取得できます。

一般的なツリータイプを使用する利点の1つは、ツリー上で動作する再利用可能な関数(map、fold、tryFindなど)を持つことができる点です。

+0

素晴らしい....私はあなたがどのように構造を構築したかを見始めています。しかし、私はどのように行うべきか分からないポイント#4を持っています。あなたは 'GroupSales'をどのようにしますか? (自分の「PersonalSales」+子供の「PersonalSales」)。私はそれを一度だけ計算したいと思います、私たちがそれを求める毎回ではありません。 TIA、David –

+0

@CrazySpaniard可能なのは、与えられたノードを構築するときに使用できる子孫ノードのデータがあるからです。しかし、それは、 'GroupSales'を計算できるようになるまで、レコードタイプの作成を遅らせることを意味します。これは、先ほどと同じように、より早い段階でタイプセーフティを行うことを意味するか、または未使用のタプルを長く置いておくか、生データの中間タイプを作成することを意味します。 – TheQuickBrownFox

+0

@CrazySpaniardそして、あなたが 'GroupSales' =' PersonalSales' +子供の 'GroupSales'を意味していたとします。これは' PersonalSales' + *子孫 '* 'PersonalSales'に相当します。 – TheQuickBrownFox

2

@TheQuickBrownFoxはあなたのドメインをモデル化しています。 Treeを表現するために、レコード/クラスを使用して

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

あなたはFPでの多くの経験を持っていない時に把握することが容易であるかもしれないものを扱うオブジェクト指向の方法、 です。

私はあなたにお見せしたいa more functional approach

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

Leafノードは、階層の終わりにSalesPerson秒です。 SupervisorとそのすべてのミニオンはBranchで表されています。

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

Treeもルートノードを持っているでしょう - 1つだけすることができます - あなたは簡単のようなものにrawDataを変換する関数を書くことができます:あなたが展開する可能性がGroupSalesを実装するには

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 

Supervisor。このツリーのインスタンスを構築する

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

一つの方法は、getTree関数からツリーを変換することです。ツリーを処理、変換、最適化することは幅広い話題です。常にfor fun and profitがあなたの旅を始めるのに適しています。

UPDATE - GroupSales

私は最初の実行時にゼロに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計算を使用することができるように、/ CALC GroupSalesbottom-up instead of top-downを変換します。

+0

私はこのようなツリーのためにリーフ/ブランチDUを使用していましたが(子供リストあり)、必要があるかどうか疑問です。ノードに空の子リストがある場合、リーフです。また、無効な状態の可能性も開きます。空のリストを持つブランチを作成できます。固定数の子供がいる場合は、DUのアプローチが理にかなっていると思います。バイナリツリー。 – TheQuickBrownFox

+0

私はレコードの代わりにDUを使用することは、多かれ少なかれ機能しているとは思わない。 – TheQuickBrownFox

+0

@TheQuickBrownFox私は違いがあると信じていますが、関数型言語はすべて型(およびそれらを処理する関数)に関するものです。あなたのデザインでは、セールスパーソンとスーパーバイザーの区別はありません。これを考えれば、 'groupSales'フィールドが彼がグループを所有しているので、' Supervisor'レベルに適していることが分かります。 'SalesPerson'を装飾せずにこれをどのように実装しますか? – Funk

関連する問題