区分联合列表F#的两个值的总和

时间:2017-01-31 15:29:43

标签: f# discriminated-union

我有运动提出这样的功能,即从歧视的联合列表中总结相同类型的每个值:

type volume =
     | Litre of float
     | Galon of float
     | Bucket of float
     | Bushel of float

let list = [Litre(20.0);Litre(30.0);Galon(2.0);Bucket(5.0);Litre(5.0);Galon(3.0)];

输出应如下所示:

[Litre(55.0);Galon(5.0);Bucket(5.0)]

我已经找到了部分解决方案:

let rec sumSameTypes (list:volume list) =
    match list with
    | a::b::t -> if a.GetType() = b.GetType() then // and there is part where I don't know how to sum two of these elements
    | [] -> failwith "EMPTY"

2 个答案:

答案 0 :(得分:7)

由于这看起来更像是一个学习问题,我会尽量给出一些提示,而不是一个完整的答案。

虽然GetType可以(在这种情况下)用于检查两个受区分的联合值是否具有相同的情况,但这不是特别功能的样式。您将需要使用模式匹配来检查您所拥有的案例,这也允许您提取数值:

match a with
| Litre(n) -> // Do something with 'n'
// Add all the other cases here

我认为首先要考虑的是你想得到什么结果 - 我想最简单的选择是得到四个数字代表升,加仑,桶和蒲式耳的总数。

let rec sumSameTypes (list:volume list) : float * float * float * float =
    match list with
    | [] -> 
        // For empty list, we just have 0 of everything
        0.0, 0.0, 0.0, 0.0
    | x::xs ->
        // For non-empty list, process the rest recrsively
        // and pattern match on `x` to figure out which of 
        // the numbers you need to increment

这是非常基本的方法,但我认为这是开始使用的最佳方式。一般来说,你可能会使用类似地图的东西(从单位到数值),但是使用看起来更像的类型也是有意义的:

type Unit = Litre | Galon | Bushel | Bucket
type Volume = { Amount : float; Unit : Unit }

这样可以更轻松,因为您可以使用Map<Unit, float>作为结果。

答案 1 :(得分:0)

受经典尾递归和函数的启发:

let sum lst =
    let rec loop acc = function
        | []   -> acc
        | h::t -> loop (acc + h) t
    loop 0.0 lst

您可以使用带有4个累加器的本地尾递归函数(每种类型一个):

let sumByType lst =
    let rec loop litre gallon bucket bushel = function
        | []   -> // return a new volume list built using the accumulators values 
        | h::t -> // recursive calls with modified accumulators according to h type
    loop 0.0 0.0 0.0 0.0 lst