F# 中的字典理解(?)(从 C# 转换)
Dictionary comprehensions(?) in F# (converting from C#)
好的,所以,我才刚刚开始学习 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);
}
}
我尝试以 "functional" 的方式重写循环产生了以下代码,但我对此感觉不太好。
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, Analysis) 对的情况下记录警告,更糟糕的是
- 我 运行 使用 for 和 Seq.filter 遍历数据(至少)两次。
我该如何改进?我做错了吗?
鉴于您的要求,您拥有的可能是最好的。您可以使用模式匹配稍微收紧代码。
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)
我认为更实用的方法:
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。
这就是您的直接问题的答案 - 但老实说,我可能会选择一个单独的函数来查找和拆分重复项,以及一个单独的函数来构建地图而无需关心对于潜在的重复项 - 即使这意味着多次传递数据。
好的,所以,我才刚刚开始学习 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);
}
}
我尝试以 "functional" 的方式重写循环产生了以下代码,但我对此感觉不太好。
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, Analysis) 对的情况下记录警告,更糟糕的是
- 我 运行 使用 for 和 Seq.filter 遍历数据(至少)两次。
我该如何改进?我做错了吗?
鉴于您的要求,您拥有的可能是最好的。您可以使用模式匹配稍微收紧代码。
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)
我认为更实用的方法:
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。
这就是您的直接问题的答案 - 但老实说,我可能会选择一个单独的函数来查找和拆分重复项,以及一个单独的函数来构建地图而无需关心对于潜在的重复项 - 即使这意味着多次传递数据。