将列表拆分为F#中的两个相等列表

时间:2011-02-01 18:46:00

标签: list f# split equals

我是F#的新手,我需要一些F#问题的帮助。

我需要实现一个cut函数,将列表分成两半,以便输出为......

  

cut [1; 2; 3; 4; 5; 6] ;;

val it:int list * int list =([1; 2; 3],[4; 5; 6])

我可以假设列表的长度是均匀的。

我还希望定义一个辅助函数gencut(n,xs),将xs切成两部分,其中n给出第一部分的大小:

  

gencut(2,[1; 3; 4; 2; 7; 0; 9]);;

val it:int list * int list =([1; 3],[4; 2; 7; 0; 9])

我通常不会在这里寻求运动帮助,但我真的不知道从哪里开始。任何帮助,即使它只是在正确的方向上推动,也会有所帮助。

谢谢!

7 个答案:

答案 0 :(得分:13)

由于你的列表长度均匀,并且你把它整齐地切成两半,我推荐以下(首先是伪代码):

  • 从两个指针开始:slowfast
  • slow一次遍历列表中的一个元素,fast一次两个元素。
  • slow将每个元素添加到累加器变量,而fast移动到foward。
  • fast指针到达列表的末尾时,slow指针只有元素数量的一半,所以它位于数组的中间。
  • 返回元素slow踩到+剩余的元素。这应该是两个整齐排列的名单。

上述过程需要遍历列表并在O(n)时间内运行。

由于这是作业,我不会给出完整的答案,但只是为了让你在中途开始,这就是把清单切成两半所需要的:

let cut l =
    let rec cut = function
        | xs, ([] | [_]) -> xs
        | [], _ -> []
        | x::xs, y::y'::ys -> cut (xs, ys)
    cut (l, l)

注意x::xs步骤1元素,y::y'::ys第二步。

此函数返回列表的后半部分。修改它非常容易,因此它也会返回列表的前半部分。

答案 1 :(得分:4)

您正在寻找F#中的列表切片。 @Juliet在这篇SO帖子中有一个很好的答案:Slice like functionality from a List in F#

基本上它归结为 - 由于F#列表中没有恒定的时间索引访问,因此它不是内置的,但您可以详细解决此问题。她的方法应用于你的问题将产生一个(效率不高但工作)的解决方案:

let gencut(n, list) = 
    let firstList = list |> Seq.take n |> Seq.toList
    let secondList = list |> Seq.skip n |> Seq.toList
    (firstList, secondList)

答案 2 :(得分:2)

(我不喜欢我之前的回答所以我删除了它)

攻击list问题的第一个开始是查看List模块,该模块充满了更高阶函数,这些函数概括了许多常见问题,并且可以为您提供简洁的解决方案。如果你在那里找不到合适的东西,那么你可以查看Seq模块,了解@BrokenGlass演示的解决方案(但是你可能遇到性能问题)。接下来,您将考虑递归和模式匹配。在处理列表时,您必须考虑两种递归:尾部和非尾部。有权衡。尾递归解决方案涉及使用累加器来传递状态,允许您将递归调用放在尾部位置,并避免使用大型列表进行堆栈溢出。但是你通常会得到一个反向列表!例如,

尾递归gencut解决方案:

let gencutTailRecursive n input =
    let rec gencut cur acc = function
        | hd::tl when cur < n ->
            gencut (cur+1) (hd::acc) tl
        | rest -> (List.rev acc), rest //need to reverse accumulator!
    gencut 0 [] input

非尾递归gencut解决方案:

let gencutNonTailRecursive n input =
    let rec gencut cur = function
        | hd::tl when cur < n ->
            let x, y = gencut (cur+1) tl //stackoverflow with big lists!
            hd::x, y
        | rest -> [], rest
    gencut 0 input

获得gencut解决方案后,定义cut非常简单:

let cut input = gencut ((List.length input)/2) input

答案 3 :(得分:2)

这是使用内置库函数执行此操作的另一种方法,与其他一些答案相比,这些函数可能更容易理解,也可能不容易理解。此解决方案也只需要在输入中进行一次遍历。在我查看你的问题之后我的第一个想法是你想要一些List.partition,它将列表分成两个基于给定谓词的列表。但是,在您的情况下,此谓词将基于当前元素的索引,该分区无法处理,缺少查找每个元素的索引。

我们可以使用fold或foldBack完成创建我们自己的等效行为。我将在这里使用foldBack,因为这意味着你之后不必反转列表(参见斯蒂芬斯的优秀答案)。我们在这里要做的是使用fold来提供我们自己的索引以及两个输出列表,所有这些都作为累加器。这是一个通用函数,它将基于n索引将列表拆分为两个列表:

let gencut n input =
    //calculate the length of the list first so we can work out the index
    let inputLength = input |> List.length 
    let results =
        List.foldBack( fun elem acc-> 
                            let a,b,index = acc     //decompose accumulator
                            if (inputLength - index) <= n then (elem::a,b,index+1)
                            else (a,elem::b,index+1)  ) input ([],[],0)
    let a,b,c = results
    (a,b) //dump the index, leaving the two lists as output.

所以在这里你看到我们使用初始累加器值([],[],0)启动foldBack。但是,因为我们从列表的末尾开始,所以需要从列表的总长度中减去表示当前索引的0,以获得当前元素的实际索引。

然后我们简单地检查当前指数是否在n的范围内。如果是,我们通过将当前元素添加到列表a,单独保留列表b并将索引增加1来更新累加器:(elem :: a,b,index + 1)。在所有其他情况下,我们完全相同,但将元素添加到列表b中:(a,elem :: b,index + 1)。

现在你可以轻松地创建一个函数,通过在这个函数上创建另一个函数将列表分成两半,如下所示:

let cut input = 
    let half = (input |> List.length) / 2
    input |> gencut half

我希望能帮到你一点!

> cut data;;
val it : int list * int list = ([1; 2; 3], [4; 5; 6])

> gencut 5 data;;
val it : int list * int list = ([1; 2; 3; 4; 5], [6])

编辑:您可以通过提供长度作为初始累加器值并在每个周期取消它而不是增加它来避免索引否定 - 可能更简单:)

let gencut n input =
    let results =
        List.foldBack( fun elem acc-> 
                            let a,b,index = acc     //decompose accumulator
                            if index <= n then (elem::a,b,index-1)
                            else (a,elem::b,index-1)  ) input ([],[],List.length input)
    let a,b,c = results
    (a,b) //dump the index, leaving the two lists as output.

答案 4 :(得分:2)

我有相同的家庭作业,这是我的解决方案。我只是一名学生,也是F#的新人。

let rec gencut(n, listb) = 
    let rec cut  n (lista : int list)  (listb : int list) =
        match (n , listb ) with
        | 0, _   ->  lista, listb
        | _, []  -> lista, listb
        | _, b :: listb -> cut  (n - 1) (List.rev (b :: lista ))  listb
    cut n [] listb

let cut xs = gencut((List.length xs) / 2, xs)  

可能不是最好的递归解决方案,但它确实有效。我想

答案 5 :(得分:1)

您可以使用List.nth进行随机访问和列表推导来生成辅助函数:

let Sublist x y data = [ for z in x..(y - 1) -> List.nth data z ]

这将从数据返回项[x..y]。使用它你可以轻松生成gencut和cut函数(记得检查x和y的边界):)

答案 6 :(得分:1)

检查一下:

let gencut s xs =  
    ([for i in 0 .. s - 1 -> List.nth xs i], [for i in s .. (List.length xs) - 1 -> List.nth xs i])
你打电话

let cut xs =                            
    gencut ((List.length xs) / 2) xs

n持续时间只有一次迭代分为两次