确定列表中的所有元素是否属于同一DU案例

时间:2017-08-19 14:24:09

标签: f#

我有10-15个案例的歧视联盟,所有案件的数据都是int<'a>

type MyUnionType =
    | Case1 of int<someUnit>
    | Case2 of int<someUnit>
    | 
    ...
    | CaseN of int<someOtherUnit>

我是函数式编程的新手,并且正在努力编写具有以下签名的函数:

mySum:MyUnionType option list -> MyUnionType option

如果所有Some元素属于同一个DU情况,该函数应该总结所有整数。例如:

mySum [Some (Case1 2<a>), Some (Case1 3<a>), None] = Some Case1 5<a>
mySum [Some (Case1 2<a>), Some (Case2 3<a>), None] = None
mySum [None] = None

我知道Option.mapList.choose以及可以提供帮助的相关内容,但我正在努力确定所有元素是否属于同一个案例。

是否有一种优雅的FP-idiomatic方式来编写这个功能? (如果它简化了问题,你可以假设列表永远不会是空的。)

(虽然我对monoids / monads / morphisms还没有明确的把握,但是如果相关的话,不要害怕使用这些词,尽管请稍微停止一些zygohistomorphic prepromorphisms。)

1 个答案:

答案 0 :(得分:4)

首先,如果您在汇总之前从列表中删除所有None个案例,那么即将呈现给您的代码将会大大简化。因此,对于我的其余部分,我将假设您首先通过List.choose id步骤运行列表以消除所有None值。

考虑这一点的最简单方法可能是将其分解为一系列单步骤。首先,您可以将列表的第一项初始化为&#34;到目前为止的总和。值。 (如果在通过List.choose id运行列表后有第一项,则列表为空或仅包含None s,因此该情况​​下的总和将为{ {1}})。现在,如果这是列表中唯一的项目,那么您已经找到了整个列表的总和。否则,您查看列表的 rest 的第一项,并询问以下问题:

  1. 该项目与目前的总和是否相同?
  2. 如果答案是肯定的,那么你将其值添加到目前为止的总和,并继续循环。如果答案是否定的,那么你到目前为止的总和是#34;值None而不是None。实际上,&#34;它与迄今为止的总和相同&#34;问题实际上是两个问题:

    1. 到目前为止&#34;总和&#34;一个真正的价值? (即,不是Some (case))?
    2. 该项目是否与目前的总和相同的DU情况?
    3. 如果对这两个问题的回答是&#34;是&#34;,那么你将这两个值加起来得到一个新的&#34;到目前为止&#34;值。如果它&#34; no&#34;,那么你只需设置&#34;总和到目前为止&#34;到None,您的最终结果也是None

      将该方法转换为代码如下所示:

      None

      现在你需要一个函数来应用&#34;组合&#34;这样的操作到整个清单。 (A&#34;组合&#34;操作是任何采用相同类型的两个项目并生成相同类型的单个项目的操作;添加是一个这样的操作,但是乘法也是如此,以及一堆其他的东西) 。有两个基本的&#34;将此组合操作应用于整个列表&#34; F#,reducefold中的函数。不同之处在于let addToSum sumSoFar nextItem = match sumSoFar with | None -> None // Short-circuit if we previously found a mismatch | Some x -> match x, nextItem with | Case1 a, Case1 b -> Some (Case1 (a + b)) | Case2 a, Case2 b -> Some (Case2 (a + b)) // ... | CaseN a, CaseN b -> Some (CaseN (a + b)) | _ -> None // Mismatch 将列表的第一项作为最初的&#34;总和到目前为止&#34;值,并不能在空列表上工作。而reduce要求您提供其迄今为止的&#34;的总和的初始值。累加器,但它可以在空列表上工作(对于空列表,fold的结果将只是您提供的初始&#34;总和&#34;值)。在您的情况下,因为您最初不知道您的&#34;总和到目前为止的类型&#34;值应该保持,你必须使用fold。所以我建议这样的事情:

      reduce

      除了let sumMyList values = values |> List.choose id |> List.reduce addToSum 无法处理空列表,并且如果您拥有的列表完全是List.reduce个案例,那就会爆炸。 (你能明白为什么吗?)所以我再添加一个步骤来处理空列表:

      None

      那应该可以帮助你找到你想要的东西。希望它也让您深入了解为您的问题设计功能性解决方案的过程。

      P.S。如果您想要更多地练习解决解决问题的功能方法,我建议在http://exercism.io/使用F#轨道。我通过这些练习学到了 lot