F#中的字典理解(?)(从C#转换)

时间:2015-06-26 19:56:24

标签: c# f# functional-programming dictionary-comprehension

好的,所以,我刚刚开始学习F#。我对大学等地的函数式语言有所了解,但在使用F#等语言进行真实世界编程时,我仍然非常环保。

我每天都在C#工作,但今天我有机会花一些时间在公司的代码库上,从F#的角度来看。我决定尝试用F#重写一些C#代码,以便在现实的商业环境中感受语言。

以下是我努力翻译的一些C#代码的解释:

// MyData is a class with properties Id, Analysis, and some other relevant properties
// Each pair of (Id, Analysis) is (should be) distinct
IEnumerable<MyData> data = // fetch from DB...

// dataDict[id[analysis]] = MyData object (or "row") from DB
var dataDict = new Dictionary<String, Dictionary<String, MyData>> ();
foreach(var d in data)
{
    if(!dataDict.ContainsKey(d.Id))
        dataDict.Add(d.Id, new Dictionary<string, MyData>());

    if (dataDict[d.Id].ContainsKey(d.Analysis))
    {
        logger.Warn(String.Format("Id '{0}' has more than one analysis of type '{1}', 
            rows will be ignored", d.Id, d.Analysis));
    }
    else
    {
        dataDict[d.Id].Add(d.Analysis, d);
    }
} 

我尝试以“功能”的方式重写循环导致了以下代码,但我感觉不太好。

let dataDict = 
      dict [ 
        for d in data 
          |> Seq.distinctBy(fun d -> d.Id) -> d.Id, 
             dict [                                                                                                   
                 for x in data |> Seq.filter(fun a -> a.Id = d.Id) -> x.Analysis, x
             ]
      ]

此代码存在以下几个问题:

  • 如果出现重复(Id,分析)对,则不会记录警告,更糟糕的是
  • 我使用for和Seq.filter两次运行数据(至少)两次。

我该如何改进?我做错了吗?

2 个答案:

答案 0 :(得分:2)

我认为更具功能性的方法是什么:

let intoMap (data: seq<MyData>) = 
    Seq.fold (fun (datamap, dups) (data: MyData) -> 
        match datamap |> Map.tryFind data.Id with
        | Some submap when submap |> Map.containsKey data.Analysis -> 
            datamap, data :: dups
        | Some submap ->
            let ext = Map.add data.Analysis data submap
            (Map.add data.Id ext datamap), dups
        | None ->
            let submap = Map.ofArray [| (data.Analysis, data) |]
            (Map.add data.Id submap datamap), dups
        ) (Map.empty, List.empty) data

它是数据的折叠,因此它遍历序列一次。它也更具功能性,因为它没有副作用 - 它们不是记录重复项,而是收集它们并成为输出的一部分。你以后可以随心所欲地做任何事。

另外,我使用不可变Map而不是Dictionary - 我发现Dictionary是F#代码中的一种代码味道。它提供的可变性在一些更深奥的场景中有用,但是对于实际持有和传递数据,我会专门使用Map。

这就是你当前问题的答案 - 但说实话,我可能会找一个单独的函数来查找和分割重复项,以及一个单独的函数来构建一个地图而不需要注意潜在的重复 - 即使这意味着多次传递数据。

答案 1 :(得分:1)

根据您的要求,您拥有的可能是最好的。您可以使用模式匹配来稍微收紧代码。

let dataDict = Dictionary<_,Dictionary<_,_>>()
for d in data do
    match dataDict.TryGetValue(d.Id) with
    | true, m when m.ContainsKey(d.Analysis) ->
        (d.Id, d.Analysis)
        ||> sprintf "Id '%s' has more than one analysis of type '%s', rows will be ignored" 
        |> logger.Warn
    | true, m -> 
        m.Add(d.Analysis, d)
    | _ ->
        let m = Dictionary()
        m.Add(d.Analysis, d)
        dataDict.Add(d.Id, m)