2016-12-29 1 views
7

У меня есть файл CSV с двумя столбцами, текстом и счетчиком. Цель состоит в том, чтобы преобразовать файл из этого:В F #, как использовать Seq.unfold в контексте более крупного конвейера?

some text once,1 
some text twice,2 
some text thrice,3 

Для этого:

some text once,1 
some text twice,1 
some text twice,1 
some text thrice,1 
some text thrice,1 
some text thrice,1 

повторять каждый раз количество строк и распространение графа над тем, что многие линии.

Это кажется мне хорошим кандидатом на Seq.unfold, генерируя дополнительные строки, когда мы читаем файл. У меня есть следующие функции генератора:

let expandRows (text:string, number:int32) = 
    if number = 0 
    then None 
    else 
     let element = text     // "element" will be in the generated sequence 
     let nextState = (element, number-1) // threaded state replacing looping 
     Some (element, nextState) 

FSI дает следующие функции подписи:

val expandRows : text:string * number:int32 -> (string * (string * int32)) option 

Выполнение следующих в FSI:

let expandedRows = Seq.unfold expandRows ("some text thrice", 3) 

дает ожидаемый:

val it : seq<string> = seq ["some text thrice"; "some text thrice"; "some text thrice"] 

вопрос в том, как я могу подключить это в контексте более крупного конвейера ETL? Например:

File.ReadLines(inFile)     
    |> Seq.map createTupleWithCount 
    |> Seq.unfold expandRows // type mismatch here 
    |> Seq.iter outFile.WriteLine 

Ошибка ниже на expandRows в контексте трубопровода.

Type mismatch. 
Expecting a 'seq<string * int32> -> ('a * seq<string * int32>) option'  
but given a  'string * int32 -> (string * (string * int32)) option' 
The type 'seq<string * int 32>' does not match the type 'string * int32' 

Я ожидал, что expandRows будет возвращать последовательность строк, как в моем изолированном тесте. Поскольку это не «Ожидание» или «данное», я смущен. Может ли кто-нибудь указать мне в правильном направлении?

Сущность для кода здесь: https://gist.github.com/akucheck/e0ff316e516063e6db224ab116501498

ответ

6

Seq.map производит последовательность, но Seq.unfold не принимает последовательность, она принимает одно значение. Таким образом, вы не можете напрямую подключить вывод Seq.map к Seq.unfold. Вы должны сделать это элемент за элементом.

Но тогда для каждого элемента ваш Seq.unfold произведет последовательность, поэтому конечным результатом будет последовательность последовательностей. Вы можете собрать всю эту «подпоследовательность» в одной последовательности с Seq.collect:

File.ReadLines(inFile) 
    |> Seq.map createTupleWithCount 
    |> Seq.collect (Seq.unfold expandRows) 
    |> Seq.iter outFile.WriteLine 

Seq.collect принимает функцию и последовательность ввода.Для каждого элемента входной последовательности предполагается, что функция создаст другую последовательность, а Seq.collect объединит все эти последовательности в одном. Вы можете думать о Seq.collect как Seq.map и Seq.concat в сочетании с одной функцией. Кроме того, если вы отправляетесь с C#, Seq.collect называется SelectMany.

+0

Очень полезное объяснение; именно то, что мне нужно. благодаря! – akucheck

+0

Рад, что я мог помочь. –

2

Похоже, что вы хотите сделать, это на самом деле

File.ReadLines(inFile)     
|> Seq.map createTupleWithCount 
|> Seq.map (Seq.unfold expandRows) // Map each tuple to a seq<string> 
|> Seq.concat // Flatten the seq<seq<string>> to seq<string> 
|> Seq.iter outFile.WriteLine 

, как это кажется, что вы хотите, чтобы преобразовать каждый кортеж с графом в своей последовательности в a seq<string> через Seq.unfold и expandRows. Это делается путем сопоставления.

Впоследствии вы хотите свернуть ваш seq<seq<string>> на большой seq<string>, который вниз через Seq.concat.

+3

'map >> concat' ===' collect' –

+0

Dope. Я забыл о сборе. Спасибо за напоминание! – Ringil

6

В этом случае, поскольку вы просто хотите повторить значение несколько раз, нет причин использовать Seq.unfold. Вы можете использовать Seq.replicate вместо:

// 'a * int -> seq<'a> 
let expandRows (text, number) = Seq.replicate number text 

Вы можете использовать Seq.collect составить его:

File.ReadLines(inFile) 
|> Seq.map createTupleWithCount 
|> Seq.collect expandRows 
|> Seq.iter outFile.WriteLine 

На самом деле, единственная работа, выполняемая этой версии expandRows является «распаковать» кортеж и составить его значения в карри.

Хотя F # не приходит с такой обобщенной функцией в своей основной библиотеке, вы можете легко определить его (и other similarly useful functions):

module Tuple2 = 
    let curry f x y = f (x, y)  
    let uncurry f (x, y) = f x y  
    let swap (x, y) = (y, x) 

Это позволит вам составить свой трубопровод из хорошо известных функциональных строительные блоки:

File.ReadLines(inFile) 
|> Seq.map createTupleWithCount 
|> Seq.collect (Tuple2.swap >> Tuple2.uncurry Seq.replicate) 
|> Seq.iter outFile.WriteLine 
+0

Мне нравится идея упрощения путем удаления Seq.unfold, но я не вижу ссылки на Seq.replicate в документе MSDN. Что я не замечаю? – akucheck

+0

Интересно. Работает в FSI, компилирует, не работает во время выполнения в Mono w System.MissingMethodException. Нужно копать в этом ... – akucheck

+1

@akucheck 'Seq.replicate' был добавлен в F # 4: https://github.com/Microsoft/visualfsharp/blob/fsharp4/CHANGELOG.md –

Смежные вопросы