首页 > 解决方案 > 字典列表与字典序列

问题描述

在此示例中,我无法理解 F# 的 List 和 Seq 之间的区别。我认为主要区别在于 Seq 有点懒惰,但我一定遗漏了一些东西。

此代码片段:

open System.Collections.Generic
let arr = 
  ["a"; "b"; "c"]
  |> Seq.map (fun a -> let dic = Dictionary () in dic.Add("key", a); dic) in
arr
|> Seq.iter (fun a -> 
  printfn "here";
  a.["key"] <- "something"
  );
arr
|> Seq.iter (fun a -> printfn "%s" a.["key"])

here
here
here
a
b
c

鉴于(将第一个 Seq 替换为 List)

open System.Collections.Generic
let arr = 
  ["a"; "b"; "c"]
  |> List.map (fun a -> let dic = Dictionary () in dic.Add("key", a); dic) in
arr
|> Seq.iter (fun a -> 
  a.["key"] <- "something"
  );
arr
|> Seq.iter (fun a -> printfn "%s" a.["key"])

something
something
something

为什么我使用 Seq 时 Dictionary 的值不会改变?here打印时清楚地访问了元素。

提前致谢。

标签: f#

解决方案


正如您所说,原因恰恰Seq是“有点懒惰”。

从某种意义上说,它是“懒惰的”,每次您要求它时都会对其进行评估。所有的。直到最后一个非懒惰的事情。

特别是,调用Seq.map是惰性的。它不会在内存中创建一个充满字典的新结构。相反,它创建了一些你可以称之为“管道”的东西。这个管道从你的列表开始["a"; "b"; "c"],然后有一个指令:每次有人试图迭代这个序列时,为每个元素创建一个新字典。“每次”位在那里很重要 - 因为您要迭代序列两次(一次打印“这里”,另一次打印值),字典也被创建两次。您将“某物”推送到其中的字典和您从中获取“密钥”的字典不是同一个字典。

为了进一步说明,试试这个:

let s = ["a";"b";"c"] |> Seq.map( fun x -> printfn "got %s" x; x )
s |> Seq.iter(printfn "here's %s")
s |> Seq.iter(printfn "again %s")

这将打印以下内容:

got a
here's a
got b
here's b
got c
here's c
got a
again a
got b
again b
got c
again c

看看每个元素的“got”输出如何发生两次?那是因为Seq.map每次迭代都有效,而不仅仅是一次。


列表不是这样。每次你List.map,你都会在内存中创建一个全新的列表。它只是永远坐在那里(“永远”被定义为“直到垃圾收集器到达它”)并等待你用它做某事。如果你用它做多件事,它仍然是同一个列表,它不会被重新创建。这就是为什么您的字典始终是相同的字典,它们不会像Seq. 这就是为什么您可以修改它们并在下次查看时看到修改。


您可以借助Seq.cache. 此函数采用常规的按需评估序列并返回一个相同的序列,除了每个元素只被评估一次。

但与列表不同的是,它不会在调用它的那一刻Seq.cache评估整个序列。相反,它将创建一个可变缓存,每次评估时都会更新。

这对于序列非常大甚至无限的情况很有用,但您只需要在它的开头使用少量有限数量的元素。

插图:

let s = ["a";"b";"c"] 
        |> Seq.map( fun x -> printfn "got %s" x; x ) 
        |> Seq.cache
s |> Seq.iter(printfn "here's %s")
s |> Seq.iter(printfn "again %s")

输出:

got a
here's a
got b
here's b
got c
here's c
again a
again b
again c

推荐阅读