【问题标题】:List of Dictionary's vs Sequence of Dictionary's字典列表与字典序列
【发布时间】:2018-10-24 20:47:06
【问题描述】:

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

这段代码sn-p:

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#


    【解决方案1】:

    正如你所说,原因恰恰是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
    

    【讨论】:

      【解决方案2】:

      我在两个示例中添加了一些 printfns,以便您可以看到不同之处:

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

      产生以下输出:

      seq: a
      here seq
      seq: b
      here seq
      seq: c
      here seq
      seq: a
      a
      seq: b
      b
      seq: c
      c
      

      虽然这个:

      let arr = 
          ["a"; "b"; "c"]
          |> List.map (fun a -> printfn "list: %s" a
                                let dic = Dictionary ()
                                dic.Add("key", a)
                                dic)
      arr
      |> Seq.iter (fun a -> 
          printfn "here list";
          a.["key"] <- "something"
      )
      arr
      |> Seq.iter (fun a -> printfn "%s" a.["key"])
      

      产生这个输出:

      list: a
      list: b
      list: c
      here list
      here list
      here list
      something
      something
      something
      

      如您所见,行为完全不同。

      Seq.map 是惰性的,这意味着它仍然是一个函数,仅在绝对必要时才被调用。每次调用它时,它都会从一开始就根据需要映射每个元素。 Seq.map 被调用两次,每个Seq.iter 调用一次,每次它为每个元素创建一个新字典,然后被垃圾收集器丢弃。

      另一方面,List.map 只被调用一次,它遍历整个输入列表,只创建一个新的字典列表。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-04-06
        • 2018-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多