F#将序列拆分为每个第n个元素的子列表

时间:2010-10-22 17:58:09

标签: f#

说我有100个元素的序列。每10个元素我想要一个前10个元素的新列表。在这种情况下,我将得到10个子列表的列表。

Seq.take(10)看起来很有希望,我怎样才能反复调用它来返回列表清单?

8 个答案:

答案 0 :(得分:14)

现在有Seq.chunkBySize可用:

[1;2;3;4;5] |> Seq.chunkBySize 2 = seq [[|1; 2|]; [|3; 4|]; [|5|]]

答案 1 :(得分:7)

这还不错:

let splitEach n s =
    seq {
        let r = ResizeArray<_>()
        for x in s do
            r.Add(x)
            if r.Count = n then
                yield r.ToArray()
                r.Clear()
        if r.Count <> 0 then
            yield r.ToArray()
    }

let s = splitEach 5 [1..17]
for a in s do
    printfn "%A" a
(*
[|1; 2; 3; 4; 5|]
[|6; 7; 8; 9; 10|]
[|11; 12; 13; 14; 15|]
[|16; 17|]
*)

答案 2 :(得分:2)

我有三种解决方案的演变。它们都没有保留输入元素的顺序,希望没问题。

我的第一个解决方案非常难看(使用ref cell):

//[[4; 3; 2; 1; 0]; [9; 8; 7; 6; 5]; [14; 13; 12; 11; 10]; [17; 16; 15]]
let solution1 =
    let split s n =
        let i = ref 0
        let lst = ref []
        seq {
            for item in s do
                if !i = n then
                    yield !lst
                    lst := [item]
                    i := 1
                else
                    lst := item::(!lst)
                    i := !i+1
            yield !lst
        } |> Seq.toList
    split {0..17} 5

我的第二个解决方案在第一个解决方案中使用了ref cell,但因此强制使用直接IEnumerator访问(一边推,另一边弹出)!

//[[17; 16; 15]; [14; 13; 12; 11; 10]; [9; 8; 7; 6; 5]; [4; 3; 2; 1; 0]]
let solution2 =
    let split (s:seq<_>) n =
        let e = s.GetEnumerator()
        let rec each lstlst lst i =
            if e.MoveNext() |> not then
                lst::lstlst
            elif i = n then
                each (lst::lstlst) [e.Current] 1
            else 
                each lstlst ((e.Current)::lst) (i+1)
        each [] [] 0
    split {0..17} 5

我的第三个解决方案基于第二个解决方案,除了它通过将列表作为输入而不是seq来“欺骗”,这使得使用模式匹配的最优雅解决方案,因为Tomas指出缺少seq(这就是为什么我们被迫使用直接IEnumerator访问)。

//[[17; 16; 15]; [14; 13; 12; 11; 10]; [9; 8; 7; 6; 5]; [4; 3; 2; 1; 0]]
let solution3 =
    let split inputList n =
        let rec each inputList lstlst lst i =
            match inputList with
            | [] -> (lst::lstlst)
            | cur::inputList ->
                if i = n then
                    each inputList (lst::lstlst) [cur] 1    
                else
                    each inputList lstlst (cur::lst) (i+1)
        each inputList [] [] 0
    split [0..17] 5

如果保留元素的顺序很重要,可以使用List.rev来实现此目的。例如,在solution2中,将split函数的最后一行更改为:

each [] [] 0 |> List.rev |> List.map List.rev

答案 3 :(得分:0)

脱离我的头脑:

let rec split size list =
if List.length list < size then
    [list]
else
    (list |> Seq.take size |> Seq.toList) :: (list |> Seq.skip size |> Seq.toList |> split size)

答案 4 :(得分:0)

我认为Brian的解决方案可能是最合理的简单选择。带序列的问题是它们不能用通常的模式匹配(如功能列表)轻松处理。避免这种情况的一个选择是使用F#PowerPack中的LazyList

另一种选择是定义用于处理IEnumerator类型的计算构建器。我最近写了类似的东西 - you can get it here。然后你可以写下这样的东西:

let splitEach chunkSize (s:seq<_>) =
  Enumerator.toSeq (fun () -> 
    let en = s.GetEnumerator()
    let rec loop n acc = iter {
      let! item = en
      match item with 
      | Some(item) when n = 1 -> 
          yield item::acc |> List.rev 
          yield! loop chunkSize []
      | Some(item) -> 
          yield! loop (n - 1) (item::acc)
      | None -> yield acc |> List.rev }
    loop chunkSize [] )

这使得能够使用一些函数模式进行列表处理 - 最值得注意的是,您可以将其写为通常的递归函数(类似于您为列表/惰性列表编写的函数),但它必须在封面下({ } let!的constructo接受下一个元素并修改枚举器。

答案 5 :(得分:0)

也许这个简单的纯实现可能很有用:

let splitAt n xs =  (Seq.truncate n xs, if Seq.length xs < n then Seq.empty else Seq.skip n xs)

let rec chunk n xs =
    if Seq.isEmpty xs then Seq.empty
    else
        let (ys,zs) = splitAt n xs
        Seq.append (Seq.singleton ys) (chunk n zs)

例如:

> chunk 10 [1..100];;
val it : seq<seq<int>> =
  seq
    [seq [1; 2; 3; 4; ...]; seq [11; 12; 13; 14; ...]; 
     seq [21; 22; 23; 24; ...]; seq [31; 32; 33; 34; ...]; ...]

> chunk 5 [1..12];;
val it : seq<seq<int>> =
  seq [seq [1; 2; 3; 4; ...]; seq [6; 7; 8; 9; ...]; seq [11; 12]]

答案 6 :(得分:0)

如有疑问,请使用折叠。

let split n  = let one, append, empty = Seq.singleton, Seq.append, Seq.empty
               Seq.fold (fun (m, cur, acc) x -> 
                           if m = n then (1, one x, append acc (one cur))
                           else (m+1, append cur (one x), acc))
                        (0, empty, empty)
               >> fun (_, cur, acc) -> append acc (one cur)

这具有完全功能的优点,但只触摸输入序列的每个元素一次(*)(与上面提出的Seq.take + Seq.skip解决方案相反)。

(*)假设O(1)Seq.append。我当然希望如此。

答案 7 :(得分:0)

我发现这是最快的:

setTimeout(
  function() 
  {
      $('#header .nav').removeAttr('style');
  }, 400);

即。窗口列表,压缩整数列表,删除所有重叠元素,然后删除元组的整数部分。