如何在Discriminated Union上将案例ID与案例值分开?

时间:2016-08-24 13:20:05

标签: f#

我想从项目列表中构建一个字典。

项目具有以下定义:

type Item =
    | A of TotalPrice * Special
    | B of TotalPrice * Special
    | C of TotalPrice
    | D of TotalPrice

我希望字典的键映射到案例ID:

| A
| B
| C
| D

然后我会将case id的值设为列表。

如何将案例ID与案例值分开?

示例:

let dictionary = items |> List.map (fun item -> item) // uh...

附录

module Checkout

(*Types*)
type UnitPrice = int
type Qty = int

type Special =
    | ThreeForOneThirty
    | TwoForFourtyFive

type TotalPrice = { UnitPrice:int ; Qty:int }

type Item =
    | A of TotalPrice * Special
    | B of TotalPrice * Special
    | C of TotalPrice
    | D of TotalPrice

(*Functions*)
let totalPrice (items:Item list) =
    let dictionary = items |> List.map (fun item -> item) // uh...
    0

(*Tests*)
open FsUnit
open NUnit.Framework

[<Test>]
let ``buying 2 A units, B unit, A unit = $160`` () =

    // Setup
    let items = [A ({UnitPrice=50; Qty=2} , ThreeForOneThirty)
                 B ({UnitPrice=30; Qty=1} , TwoForFourtyFive)
                 A ({UnitPrice=50; Qty=1} , ThreeForOneThirty)]

    items |> totalPrice |> should equal 160

3 个答案:

答案 0 :(得分:3)

您的数据针对您的用例进行了严格定义。如果您想自己引用各种项目,您需要自己定义它们:

type ItemKind = A | B | C | D

type Item = { Kind: ItemKind; Price: TotalPrice; Special: Special option }

然后您可以轻松地构建项目字典:

let dictionary = items |> List.map (fun i -> i.Kind, i) |> dict

虽然我必须注意到这样的字典可能不可能:如果items列表包含多个相同类型的项目,其中一些不会包含在字典中,因为它不能包含多个相同的键。也许我不明白你追求的字典是什么。

答案 1 :(得分:1)

如果要使用A,B,C和D等键创建字典,则会失败,因为A和B是类型为TotalPrice * Special -> Item的构造函数,而C和D是TotalPrice -> Item类型的构造函数。字典必须对键的类型做出决定。

获取DU构造函数名称应该可以通过反射来实现,但对于你的情况是否真的有必要?

对于您的情况,也许不同的类型结构会更有效,即。 Fyodor Soikin提议。

答案 2 :(得分:0)

可能以下内容将澄清为何数据结构和代码不合适,因此也明确说明这主要与FP无关,如某些评论等所示。

我的猜测是这个问题与“如何分组”有关,而且实际上有一个groupBy函数!

(*Types*)
type UnitPrice = int
type Qty = int

type Special =
    | ThreeForOneThirty
    | TwoForFourtyFive

type TotalPrice = { UnitPrice:int ; Qty:int }

type Item =
    | A of TotalPrice * Special
    | B of TotalPrice * Special
    | C of TotalPrice
    | D of TotalPrice


let items = [A ({UnitPrice=50; Qty=2} , ThreeForOneThirty)
             B ({UnitPrice=30; Qty=1} , TwoForFourtyFive)
             A ({UnitPrice=50; Qty=1} , ThreeForOneThirty)]

let speciallyStupidTransformation = 
    function 
    | ThreeForOneThirty -> 34130
    | TwoForFourtyFive -> 2445

let stupidTransformation = 
    function 
    | A (t,s) -> "A" + (s |> speciallyStupidTransformation |> string) 
    | B (t,s) -> "B" + (s |> speciallyStupidTransformation |> string)
    | C (t) -> "C"
    | D(t) -> "D"

let someGrouping = items |> List.groupBy(stupidTransformation)

val it : (string * Item list) list =


[("A34130",
    [A ({UnitPrice = 50;
         Qty = 2;},ThreeForOneThirty); A ({UnitPrice = 50;
                                           Qty = 1;},ThreeForOneThirty)]);
   ("B2445", [B ({UnitPrice = 30;
                  Qty = 1;},TwoForFourtyFive)])]

是的,这还是一个坏主意。但它在某种程度上是独一无二的,可能会被进一步滥用以汇总一些金额或其他任何东西。

为此添加更多代码,如下所示:

let anotherStupidTransformation =
    function
    | A(t,_) -> (t.UnitPrice, t.Qty)
    | B(t,_) -> (t.UnitPrice, t.Qty)
    | C(t) -> (t.UnitPrice, t.Qty)
    | D(t) -> (t.UnitPrice, t.Qty)

let x4y x y tp q =
   if q%x = 0 then y*q/x else tp/q*(q%x)+(q-q%x)/x*y  

let ``34130`` = x4y 3 130
let ``2445`` = x4y 2 45      

let getRealStupidTotal =
    function 
    | (s, (tp,q)) -> 
        (s|> List.ofSeq, (tp,q))
        |>  function
            | (h::t, (tp,q)) ->
                match t |> List.toArray |> System.String with 
                | "34130" -> ``34130`` tp q
                | "2445" -> ``2445`` tp q
                | _ -> tp



let totalPrice = 
    items
    |> List.groupBy(stupidTransformation)
    |> List.map(fun (i, l) -> i, 
                    l 
                    |> List.map(anotherStupidTransformation)
                    |> List.unzip
                    ||> List.fold2(fun acc e1 e2 ->
                         ((fst acc + e1) * e2, snd acc + e2)  ) (0,0))
    |> List.map(getRealStupidTotal)
    |> List.sum

val totalPrice : int = 160

可能会也可能不会产生一些正确的测试用例。

对于上面的测试数据,据我所知,初始代码至少是可以的。总和确实是160 ......

我会在任何地方使用此代码吗?不。

可读吗?不。

可以修复吗?不是改变数据结构的方式,以避免几个愚蠢的转换......