【问题标题】:Splitting a list of items into two lists of odd and even indexed items将项目列表拆分为奇数和偶数索引项目的两个列表
【发布时间】:2011-12-18 01:30:21
【问题描述】:

我想做一个函数,它接受一个列表并返回两个列表:第一个包含所有奇数项,第二个包含每个偶数项。

例如,给定[1;2;4;6;7;9],我想返回[ [1;4;7] ; [2;6;9] ]

到目前为止,我已经写了这篇文章,但我不知道如何进步。

let splitList list =
    let rec splitOdd oList list1 list2 =
        match oList with
        | [] -> []
        | head :: tail -> splitEven tail (list1::head) list2
    and splitEven oList list1 list2 =
        match oList with
        | [] -> []
        | head :: tail -> splitOdd tail list1 (list2::head)
    splitOdd list [] []

【问题讨论】:

  • 你不能使用 List.partition 吗?
  • @Mathias: 不,List.partition 使用谓词,在这种情况下没有合适的谓词。

标签: list f# functional-programming ocaml tail-recursion


【解决方案1】:

不堆栈溢出的实现:

let splitList list = List.foldBack (fun x (l,r) -> x::r, l) list ([],[])

【讨论】:

  • 值得注意的是foldBack将列表转换为数组以允许尾到头遍历。这通常是一件好事,因为数组遍历速度很快,但是对于较大的列表应该考虑成本。
  • 该死的,这是非常好的解决方案。我花了几秒钟才意识到它是如何工作的,但我不得不承认这个解决方案非常优雅(y)。
  • 那么,你能更详细地解释一下它是如何工作的吗?如果我理解正确 List.foldBack 将该函数应用于列表中的每个元素,从尾部到头部? ([],[]) 是一个元组,存储状态。有趣的部分如何运作? x::r 是什么意思?谢谢
  • @DávidMolnár 来自 fold 的定义,伪代码 split [a;b;c;d] --> (a::r , l) { where (l, r) <-- split [b;c;d] }; split [] --> ([] , []) 。因此,[] --> ([],[])[d] --> ([d],[]); [c;d] --> ([c],[d]); [b;c;d] --> ([b;d],[c]); [a;b;c;d] --> ([a;c] , [b;d])...
  • cf..
【解决方案2】:

如果您指的是项目位置的奇数和偶数值,这是一个(非尾递归)解决方案:

let rec splitList = function
    | [] -> [], []
    | [x]-> [x], []
    | x1::x2::xs -> let xs1, xs2 = splitList xs
                    x1::xs1, x2::xs2

【讨论】:

  • 完全是我想要的……非常感谢!关于这种语言,肯定有很多东西要学,尤其是它的简洁性。
  • 这个实现的问题在于它不是尾递归的,它会导致大约 80000 个元素的列表上的堆栈溢出。
【解决方案3】:

这是一个简单的非递归解决方案:

let splitList ll =
    ll
    |> List.mapi (fun i x -> (i % 2 = 0, x))
    |> List.partition fst
    |> fun (odd,even) -> [List.map snd odd, List.map snd even];;

val splitList : 'a list -> 'a list list

应用于您的样本,它会产生您想要的结果:

splitList [1;2;4;6;7;9];;

val it : int list list = [[1; 4; 7]; [2; 6; 9]]

【讨论】:

    【解决方案4】:

    看起来这就是你想要的,这确实是一种很好的方法,因为它是尾递归的。

    let splitList items =
      let rec splitOdd odds evens = function
        | [] -> odds, evens
        | h::t -> splitEven (h::odds) evens t
      and splitEven odds evens = function
        | [] -> odds, evens
        | h::t -> splitOdd odds (h::evens) t
      let odds, evens = splitOdd [] [] items
      List.rev odds, List.rev evens
    

    【讨论】:

      【解决方案5】:

      另一个(效率较低的)选项

      let splitList xs = 
          let odd, even =
              xs
              |> List.zip [ 1 .. (List.length xs) ]
              |> List.partition (fun (i, _) -> i % 2 <> 0)
          [ odd |> List.map snd; even |> List.map snd ]
      

      如果您想避免创建临时列表,请考虑使用序列:

      let splitListSeq xs =
          xs
          |> Seq.mapi (fun i x -> (i % 2 = 0, x))
          |> Seq.groupBy (fun (b, _) -> b)
          |> Seq.map snd
          |> Seq.map ((Seq.map snd) >> Seq.toList)
          |> Seq.toList
      

      还有一个,类似于丹尼尔的版本:

      let splitListRec xs =
          let rec loop l r = function
              | []      -> [l; r]
              | x::[]   -> [x::l; r]
              | x::y::t -> loop (x::l) (y::r) t
          loop [] [] xs |> List.map List.rev
      

      【讨论】:

        【解决方案6】:

        我在 OCaml 中的 2 美分,因为仍有悬赏。

        也许你可以给我们一个提示你想要什么。优雅? FP?尾递归?性能?

        编辑:

        我删除了较长的解决方案。要使 List.partition 起作用,谓词丢失了。 这里是:

        let so_split lst = 
          let flag = ref false in
          List.partition (fun e -> flag := not !flag; !flag) lst
        

        有任何改进吗?测试解决方案:

        # so_split [1;2;4;6;7;9];;
        - : int list * int list = ([1; 4; 7], [2; 6; 9])
        

        【讨论】:

        • 嗨,赏金确实说“一个或多个答案是典型的,值得额外的赏金。” :) 这既是为了奖励答案,也是为了引起人们对问题的关注,这样更多的人可能会对它感兴趣。
        • 好的,我对它感兴趣。当有人询问更多信息时,您通常不会重复已知文本,而是解释它,或者更好......
        【解决方案7】:

        听起来你想要 List.partition (http://msdn.microsoft.com/en-us/library/ee353782.aspx)。此函数接受一个谓词和一个列表,并将返回一个对(2 元组),其中第一个元素是所有通过测试的元素,第二个是所有未通过测试的元素。因此,您可以使用以下方式对赔率和偶数进行分类:

        List.partition odd [1;2;4;6;7;9]
        

        如果您真的想要一个列表,您可以使用fstsnd 从元组中提取元素并将它们放入列表中。

        祝你好运!

        【讨论】:

        • 再次,问题是关于奇偶索引。 partition 不会将索引传递给用于分区的函数。
        • 我尝试了这样的方法: List.partition (fun i -> (i % 2 = 0)) [1;2;4;6;7;9] 并得到了这个结果 ([ 2;4;6],[1;7;9])。所以,这个解决方案非常擅长区分奇数和偶数。
        【解决方案8】:

        为了完整起见,这里是一个无聊的、更命令式的解决方案:

        let splitList (list:int list) =
            let odds = [for i in 0..list.Length-1 do
                          if i%2=1 then
                            yield list.[i]]
            let evens = [for i in 0..list.Length-1 do
                            if i%2=0 then
                                yield list.[i]]
            odds,evens 
        

        【讨论】:

        • 在 F# 中,列表推导式是否定义了延迟生成的序列?
        • 懒惰的东西你需要使用'seq{}',但看起来一样
        • 我只是想知道这是否需要对列表进行两次传递,或者是否可以将其编译为一个(惰性)传递,随着它的进行添加到一个或另一个结果序列的尾部。 (我是tailrecursion-modulo-cons 的忠实粉丝)。
        • 列表推导只会产生一个列表(或序列)。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-09
        • 2017-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多