Список словаря против последовательности словаря - PullRequest
0 голосов
/ 24 октября 2018

У меня проблемы с пониманием различий между списками F # и 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?Элементы четко видны при печати here.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 25 октября 2018

Я добавил несколько printfn s к обоим примерам, чтобы вы могли увидеть разницу:

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 вызывается толькоодин раз и проходит по всему входному списку, создавая новый список словарей только один раз.

0 голосов
/ 25 октября 2018

Причина как раз в том, что 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...