[英]How do I write a ZipN-like function in F#?
我想創建一個帶有簽名seq<#seq<'a>> ->seq<seq<'a>>
的函數,它就像一個Zip方法,采用任意數量的輸入序列(而不是2或3)如在Zip2和Zip3中那樣,並返回一系列序列而不是元組作為結果。
也就是說,給出以下輸入:
[[1;2;3];
[4;5;6];
[7;8;9]]
它將返回結果:[[1; 4; 7]; [2; 5; 8]; [3; 6; 9]]
除了序列而不是列表。
我對F#很新,但是我已經創建了一個能夠滿足我想要的功能,但我知道它可以改進。 這不是尾遞歸,似乎它可能更簡單,但我不知道如何。 我還沒有找到一種很好的方式來獲得我想要的簽名(接受,例如,作為輸入的int list list
)而沒有第二個函數。
我知道這可以直接使用枚舉器實現,但我有興趣以功能方式實現它。
這是我的代碼:
let private Tail seq = Seq.skip 1 seq
let private HasLengthNoMoreThan n = Seq.skip n >> Seq.isEmpty
let rec ZipN_core = function
| seqs when seqs |> Seq.isEmpty -> Seq.empty
| seqs when seqs |> Seq.exists Seq.isEmpty -> Seq.empty
| seqs ->
let head = seqs |> Seq.map Seq.head
let tail = seqs |> Seq.map Tail |> ZipN_core
Seq.append (Seq.singleton head) tail
// Required to change the signature of the parameter from seq<seq<'a> to seq<#seq<'a>>
let ZipN seqs = seqs |> Seq.map (fun x -> x |> Seq.map (fun y -> y)) |> ZipN_core
let zipn items = items |> Matrix.Generic.ofSeq |> Matrix.Generic.transpose
或者,如果你真的想自己寫:
let zipn items =
let rec loop items =
seq {
match items with
| [] -> ()
| _ ->
match zipOne ([], []) items with
| Some(xs, rest) ->
yield xs
yield! loop rest
| None -> ()
}
and zipOne (acc, rest) = function
| [] -> Some(List.rev acc, List.rev rest)
| []::_ -> None
| (x::xs)::ys -> zipOne (x::acc, xs::rest) ys
loop items
由於這似乎是在f#中編寫zipn
的規范答案,我想添加一個“純” seq
解決方案,它可以保留懶惰,並且不會強迫我們像Matrix.transpose
函數一樣在內存中加載完整的源序列。 有些情況下這非常重要,因為它a)更快,b)適用於包含100個MB數據的序列!
這可能是我在一段時間內編寫的最不具有慣用性的f#代碼,但它完成了工作(嘿,為什么在f#中有序列表達式,如果你不能用它們來編寫函數式語言的程序代碼)。
let seqdata = seq {
yield Seq.ofList [ 1; 2; 3 ]
yield Seq.ofList [ 4; 5; 6 ]
yield Seq.ofList [ 7; 8; 9 ]
}
let zipnSeq (src:seq<seq<'a>>) = seq {
let enumerators = src |> Seq.map (fun x -> x.GetEnumerator()) |> Seq.toArray
if (enumerators.Length > 0) then
try
while(enumerators |> Array.forall(fun x -> x.MoveNext())) do
yield enumerators |> Array.map( fun x -> x.Current)
finally
enumerators |> Array.iter (fun x -> x.Dispose())
}
zipnSeq seqdata |> Seq.toArray
val it : int [] [] = [|[|1; 4; 7|]; [|2; 5; 8|]; [|3; 6; 9|]|]
順便說一句,傳統的矩陣轉置比@ Daniel的答案簡潔得多。 但是,它需要一個list
或LazyList
,它們最終都會在內存中擁有完整的序列。
let rec transpose =
function
| (_ :: _) :: _ as M -> List.map List.head M :: transpose (List.map List.tail M)
| _ -> []
為了處理具有不同長度的子列表,我已經使用選項類型來發現我們是否已經用完了元素。
let split = function
| [] -> None, []
| h::t -> Some(h), t
let rec zipN listOfLists =
seq { let splitted = listOfLists |> List.map split
let anyMore = splitted |> Seq.exists (fun (f, _) -> f.IsSome)
if anyMore then
yield splitted |> List.map fst
let rest = splitted |> List.map snd
yield! rest |> zipN }
這將映射
let ll = [ [ 1; 2; 3 ];
[ 4; 5; 6 ];
[ 7; 8; 9 ] ]
至
seq
[seq [Some 1; Some 4; Some 7]; seq [Some 2; Some 5; Some 8];
seq [Some 3; Some 6; Some 9]]
和
let ll = [ [ 1; 2; 3 ];
[ 4; 5; 6 ];
[ 7; 8 ] ]
至
seq
[seq [Some 1; Some 4; Some 7]; seq [Some 2; Some 5; Some 8];
seq [Some 3; Some 6; null]]
這對你的方法采取了不同的方法,但避免使用你以前的一些操作(例如Seq.skip,Seq.append),你應該小心。
我意識到這個答案不是很有效,但我確實喜歡它的簡潔:
[[1;2;3]; [4;5;6]; [7;8;9]]
|> Seq.collect Seq.indexed
|> Seq.groupBy fst
|> Seq.map (snd >> Seq.map snd);;
另外一個選項:
let zipN ls =
let rec loop (a,b) =
match b with
|l when List.head l = [] -> a
|l ->
let x1,x2 =
(([],[]),l)
||> List.fold (fun acc elem ->
match acc,elem with
|(ah,at),eh::et -> ah@[eh],at@[et]
|_ -> acc)
loop (a@[x1],x2)
loop ([],ls)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.