Группировка чередующихся данных - PullRequest
3 голосов
/ 27 февраля 2011

Данный текстовый файл содержит ключи и значения в чередующемся порядке, например:

KeyA
ValueA
KeyB
ValueB
KeyC
ValueC
...

Я хотел бы создать словарь / хэш-таблицу из этих данных.Как бы я поступил так функционально?

Ответы [ 5 ]

6 голосов
/ 27 февраля 2011

@ BrokenGlass был на высоте, признав Seq.pairwise идеальным выбором для извлечения ваших данных. Но для более функционального решения используйте неизменяемый Microsoft.FSharp.Collections.Map вместо изменяемого System.Collections.Generic.Dictionary:

System.IO.File.ReadAllLines @"keyvalue.txt"
|> Seq.pairwise
|> Seq.mapi (fun i x -> if i % 2 = 0 then Some(x) else None)
|> Seq.choose id
|> Map.ofSeq

А если ваш файл данных огромен, рассмотрите чтение значений в виде потока, чтобы повысить производительность:

seq { 
    use sr = System.IO.File.OpenText @"keyvalue.txt"
    while(not sr.EndOfStream) do yield (sr.ReadLine(), sr.ReadLine())
}
|> Map.ofSeq
5 голосов
/ 27 февраля 2011

Я думаю, что все ответы, опубликованные ранее, дают хорошее решение. Интересно, что это не то, что может быть элегантно решено с помощью выражений последовательности F # - вы должны использовать списки и рекурсию, некоторые хитрые функции (например, pairwise) или использовать интерфейс IEnumerator.

Я написал конструктор вычислений, который позволяет вам работать с IEnumerator (см. Полный исходный код на fssnip.net ). Используя это вычисление, вы можете решить его очень красиво:

let loadFile path = 
  // Recursive function that generates IEnumerator of key * value pairs
  let rec loop source = iter {
    // Read key & value and continue if both are available
    let! key = source
    let! value = source
    match key, value with
    | Some key, Some value -> 
       // Produce key * value pair and continue looping
       yield key, value
       yield! loop source
    | _ -> () }

  // Create sequence that reads data and convert it to dictionary
  Enumerator.toSeq (fun () ->
    loop (File.ReadAllLines(@"keyvalue.txt").GetEnumerator())) |> dict

Я считаю вычисления iter очень хорошими - в некоторых случаях вы не можете решить проблему с помощью F # seq. Тогда вы можете использовать рекурсию и списки - но тот же самый шаблон рекурсии может быть также написан довольно аккуратно, используя iter.

3 голосов
/ 27 февраля 2011

Я бы сделал что-то подобное, хотя я не уверен, что это самый «функциональный» подход:

let dic = Dictionary<string,string>()
File.ReadAllLines(@"keyvalue.txt")
|> Seq.pairwise
|> Seq.iteri( fun i (a,b)-> if i % 2 = 0 then dic.Add(a,b))
2 голосов
/ 27 февраля 2011
let loadFile path =
    let rec loop acc = function
    | k::v::rest -> loop ((k, v)::acc) rest
    | []         -> dict acc
    | _          -> failwith "odd number of lines"

    path |> System.IO.File.ReadAllLines |> List.ofArray |> loop []
0 голосов
/ 27 февраля 2011

Разбейте его на список строк, используйте List.choose или Seq.choose , чтобы разбить его на нечетные / четные списки, а затем, возможно, используйте List.zip в этих двух списках, чтобы получить их в список кортежей ключ / значение.

...