6

我想创建一个具有签名的函数,该函数seq<#seq<'a>> ->seq<seq<'a>>类似于 Zip 方法,采用任意数量的输入序列(而不是 Zip2 和 Zip3 中的 2 或 3 个)并返回序列序列而不是元组作为结果。

也就是说,给定以下输入:

[[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
4

5 回答 5

8
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
于 2012-08-02T14:42:01.247 回答
4

因为这似乎是zipn在 f# 中编写 a 的规范答案,所以我想添加一个“纯”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 的答案简洁得多。但是,它需要一个listLazyList两个最终都将在内存中具有完整的序列。

let rec transpose = 
  function 
  | (_ :: _) :: _ as M -> List.map List.head M :: transpose (List.map List.tail M)
  | _ -> []
于 2014-11-05T17:00:19.577 回答
2

为了处理不同长度的子列表,我使用选项类型来发现元素是否用完。

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),您应该小心这些操作。

于 2012-08-02T11:32:47.157 回答
1

我意识到这个答案不是很有效,但我确实喜欢它的简洁性:

[[1;2;3]; [4;5;6]; [7;8;9]] 
    |> Seq.collect Seq.indexed 
    |> Seq.groupBy fst 
    |> Seq.map (snd >> Seq.map snd);;
于 2016-08-31T15:41:04.023 回答
0

另外一个选项:

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)
于 2019-06-15T21:48:54.183 回答