2016-12-29 4 views
7

Mam plik CSV z dwiema kolumnami, tekstem i liczbą. Celem jest przekształcenie plików z tego:W języku F #, Jak korzystać z Seq.unfold w kontekście większego potoku?

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

do tego:

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

razy powtarzając każdy Liczba linii i rozprzestrzeniania licznik nad tym wielu liniach.

To wydaje mi się dobrym kandydatem do Seq.unfold, generując dodatkowe linie, gdy czytamy plik. Mam następującą funkcję generatora:

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 daje w wyniku funkcją następujący podpis:

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

Wykonywanie następujących czynności w FSI:

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

daje oczekiwany:

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

The Pytanie brzmi: jak podłączyć to do kontekstu większego potoku ETL? Na przykład:

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

Poniższy błąd występuje w rozwinięciu w kontekście potoku.

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' 

Spodziewałem się, że program expandRows zwraca ciąg seq, jak w moim izolowanym teście. Ponieważ nie jest to ani "oczekiwanie", ani "dane", jestem zdezorientowany. Czy ktoś może wskazać mi właściwy kierunek?

GIST kodu jest tutaj: https://gist.github.com/akucheck/e0ff316e516063e6db224ab116501498

Odpowiedz

6

Seq.map tworzy sekwencję, ale Seq.unfold nie wykonuje sekwencji, zajmuje pojedynczą wartość. Więc nie możesz bezpośrednio potrącić wyjścia Seq.map do Seq.unfold. Zamiast tego musisz zrobić to element po elemencie.

Ale wtedy, dla każdego elementu twój Seq.unfold wytworzy sekwencję, więc ostatecznym rezultatem będzie sekwencja sekwencji. Można zebrać wszystkie te „podsekwencje” w pojedynczej sekwencji z Seq.collect:

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

Seq.collect wykonuje funkcję i sekwencję wejściową.Dla każdego elementu sekwencji wejściowej funkcja powinna tworzyć inną sekwencję, a Seq.collect będzie łączyć wszystkie te sekwencje w jedną. Możesz pomyśleć o Seq.collect jako Seq.map i Seq.concat połączonych w jednej funkcji. Ponadto, jeśli przychodzisz z C#, Seq.collect nazywa się tam SelectMany.

+0

Bardzo pomocne wyjaśnienie; dokładnie to, czego potrzebowałem. dzięki! – akucheck

+0

Cieszę się, że mogę pomóc. –

2

Wygląda na to, co chcesz zrobić, to faktycznie

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 

jak wydaje się, że chcesz przekonwertować każdej krotki z liczby w kolejności do a seq<string> przez Seq.unfold i expandRows. Odbywa się to poprzez mapowanie.

Następnie chcesz spłaszczyć swój seq<seq<string>> w dużym seq<string>, który jest w dół przez Seq.concat.

+3

'mapa >> concat' ===' collect' –

+0

Dope. Zapomniałem o zbieraniu. Dzięki za przypomnienie! – Ringil

6

W tym przypadku, ponieważ po prostu chcesz powtórzyć wartość kilka razy, nie ma powodu, aby używać Seq.unfold. Można użyć Seq.replicate zamiast:

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

można użyć Seq.collect aby je komponować:

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

W rzeczywistości, tylko praca wykonywana przez tę wersję expandRows jest „rozpakować” krotki i skomponuj jego wartości w formie curry.

Chociaż F # nie przychodzi z taką ogólną funkcję w swojej podstawowej biblioteki, można łatwo zdefiniować go (i 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) 

byłoby to pozwalają skomponować rurociągu ze znanym funkcjonalny bloki konstrukcyjne:

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

Uwielbiam pomysł upraszczania poprzez usunięcie Seq.unfold, ale nie widzę odniesienia do Seq.replicate w dokumencie MSDN. Co ja przeoczyłem? – akucheck

+0

Interesujące. Działa w FSI, kompiluje, kończy się niepowodzeniem w środowisku wykonawczym w Mono w System.MissingMethodException. Muszę się w to zagłębić ... – akucheck

+1

@ kontrola czeku 'Seq.replicate' został dodany w F # 4: https://github.com/Microsoft/visualfsharp/blob/fsharp4/CHANGELOG.md –

Powiązane problemy