F#:递归函数:将列表拆分为两个相等的部分

时间:2016-01-31 18:32:17

标签: list recursion split f#

我在分割和合并时遇到了一些错误。 (注意,这是一个格式化的分配,需要为每个函数指定特定的输入和输出类型)

这是我现在的代码:

(* val split : l:'a list -> 'a list * 'a list *)
let split (l:'a list -> 'a list * 'a list) = 
    let rec splitInner = function
        | [],[] -> [],[]
        | x::xs, acc ->
            if xs.Length>(acc.Length) then splitInner (xs x::acc)
            else xs acc
    splitInner (l, acc)

error FS0001: This expression was expected to have type
    'a list * 'b list    
but here has type
    'c list

(* val merge : 'a list * 'a list -> 'a list when 'a : comparison *)
let rec merge l = 
    match l with
    | (xs,[])->xs
    | ([],ys)->ys
    | (x::xs, y::yr) ->
        if x<=y then x::merge(xr,y::yr)
        else y::merge(x::xr,yr)

(* val mergesort : l:'a list -> 'a list when 'a : comparison *)
let rec mergesort l = 
    match l with
    | [] -> []
    | [x] -> [x]
    | xs -> let (ys,zs) = split xs then merge(mergesort ys, mergesort zs)

acc功能不适用于拆分和&#34;然后&#34;在最后一行代码中是不对的。

这个想法如下:给定的列表l被分成两个相等的(如果l的长度是奇数,那么“一半”中的一个是比另一个长的一个项目)列表l1和l2。这些列表以递归方式排序,然后将结果合并回来以提供单个排序列表。在F#中编码。您的算法可以使用&lt;作为比较运算符。您的代码必须具有生成一对列表的函数拆分,合并排序列表的函数合并以及实现整体算法的函数mergesort。

编辑:我相信我不允许在此作业中使用|> List.splitAt。我正在尝试实现一个辅助函数,它将执行相同的操作。

EDIT2:谢谢Guy Coder,你的答案非常透彻。我需要功能拆分只接受一个列表。也许我可以在里面创建一个使用基于list.Length/2的索引的辅助函数?假设列表输入为float list,函数返回2 float lists

EDIT3:我感觉更接近:这是我的分割功能和我收到的错误。

(* val split : l:'a list -> 'a list * 'a list *)
let split l = 
    let rec splitInner l counter list1 list2 =
        match (l, counter) with
        | (x::xs,c) ->
            if c < (l.Length/2) then
                let list1 = x :: list1
                let counter = counter+1
                splitInner xs counter list1 list2
            else
                let list2 = x :: list2
                let counter = counter+1
                splitInner xs counter list1 list2
        | ([],_) -> ((reverse list1), (reverse list2))
    splitInner (l 0 [] [])
split [1;2;3;4;5;6;7;8;9;10]

error FS0001: This expression was expected to have type
    int -> 'a list -> 'b list -> 'c list    
but here has type
    'd list

4 个答案:

答案 0 :(得分:2)

let halve xs = xs |> List.splitAt (xs.Length / 2)

示例:

> halve [1..10];;
val it : int list * int list = ([1; 2; 3; 4; 5], [6; 7; 8; 9; 10])
> halve [1..9];;
val it : int list * int list = ([1; 2; 3; 4], [5; 6; 7; 8; 9])

答案 1 :(得分:2)

// val reverse : l:'a list -> 'a list
let reverse l =
    let rec reverseInner l acc =
        match l with
        | x::xs -> 
            let acc = x :: acc
            reverseInner xs acc
        | [] -> acc
    reverseInner l []

// val split : l:'a list -> 'a list * 'a list
let split l = 
    let listMid = (int)((length l)/2)
    let rec splitInner l index counter list1 list2 =
        match (l,counter) with 
        | (x::xs,c) ->  
            if c < index then
                let list1 = x :: list1
                let counter = counter + 1
                splitInner xs index counter list1 list2
            else
                let list2 = x :: list2
                let counter = counter + 1
                splitInner xs index counter list1 list2
        | ([], _) -> ((reverse list1), (reverse list2))
    splitInner l listMid 0 [] []

split [1;2;3;4;5;6;7;8;9;10]
val it : int list * int list = ([1; 2; 3; 4; 5], [6; 7; 8; 9; 10])

split [1;2;3;4;5;6;7;8;9;10;11]
val it : int list * int list = ([1; 2; 3; 4; 5], [6; 7; 8; 9; 10; 11])

RosettaCode

的另一个变体
let split list =
    let rec aux l acc1 acc2 =
        match l with
            | [] -> (acc1,acc2)
            | [x] -> (x::acc1,acc2)
            | x::y::tail ->
                aux tail (x::acc1) (y::acc2)
    in aux list [] []

这个如何运作。

比赛有三种模式

| []           which only matches when the input list is empty
| [x]          which only matches when there is only one item in the list
| x::y::tail   which matches all the other patterns 

这个说我们不需要知道列表的长度,因为如果我们在列表| x::y::tail中至少有两个项目,那么将一个放在列表中,另一个放在列表2中。重复此过程,直到列表中有一个或没有项目。如果列表中有一个项目将其放入第一个列表并重复。现在输入列表为空,因此返回两个列表。

fssnip.net

的第三个变体
let splitList divSize lst = 
  let rec splitAcc divSize cont = function
    | [] -> cont([],[])
    | l when divSize = 0 -> cont([], l)
    | h::t -> splitAcc (divSize-1) (fun acc -> cont(h::fst acc, snd acc)) t
  splitAcc divSize (fun x -> x) lst
使用Joel Huang

Continuation-passing style

这个函数将函数传递给内部函数。函数为(fun x -> x),内部函数在cont参数中接收它。这个也有三种匹配模式。

| []   only matches on empty list  
| l    only matches on list with one item  
| h::t matches when there are two or more items in the list.  

但是,由于它为每个递归调用传递一个函数,因此在列表完成传递递归函数之前,不会评估构建函数的求值。它还使用计数器,但倒计时为0以避免必须传递额外的参数。这是高级编码,因此不要花费大量时间来理解它,但请注意,因为函数是一流的,所以这是可能的。

前一天来自TheInnerLight - posted的第四个变体。

let split lst = 
    let rec helper lst l1 l2 ctr =
        match lst with
        | [] -> l1, l2 // return accumulated lists
        | x::xs -> 
            if ctr%2 = 0 then 
                helper xs (x::l1) l2 (ctr+1) // prepend x to list 1 and increment
            else 
                helper xs l1 (x::l2) (ctr+1) // prepend x to list 2 and increment
    helper lst [] [] 0

我是如何创建分裂的。

从格式

开始
let funXYZ list =
    let rec funXYZInner list acc =
        match list with
        | head :: tail ->
            let acc = (somefunc head) :: acc
            funXYZInner tail acc
        | [] -> acc
    funXYZInner list []

命名函数split

let split list =
    let rec splitInner l acc =
        match l with
        | head :: tail ->
            let acc = (somefunc head) :: acc
            splitInner tail acc
        | [] -> acc
    split list []

现在我知道我需要一个输入列表和两个输出列表以及元组中的两个输出列表

let split l =
    let rec splitInner l list1 list2 =
        match l with
        | head :: tail ->
            let list1 = (somefunc head)
            let list2 = (somefunc head)
            splitInner tail list1 list2
        | [] -> (list1, list2)
    split l [] []

因为拆分会将列表分成两半,这可以在迭代列表之前计算

let split l =
    let listMid = (int)((length l)/2)
    let rec splitInner l list1 list2 =
        match l with
        | head :: tail ->
            let list1 = (somefunc head)
            let list2 = (somefunc head)
            splitInner tail list1 list2
        | [] -> (list1, list2)
    split l [] []

要把它变成条件,我们需要一个计数器。因此,将计数器初始化为0中的splitInner l listMid 0 [] []并将其传递给匹配模式。因为对于最后一个匹配模式,我们不关心计数的值是什么,而是使用_ 我们还需要知道要比较反击的内容,即listMid,然后将其传递给splitInner
每次使用头部时也要增加计数器。

let split l =
    let listMid = (int)((length l)/2)
    let rec splitInner l listMid counter list1 list2 =
        match (l ,counter) with
        | (head :: tail, c) ->
            let list1 = (somefunc head)
            let list2 = (somefunc head)
            let counter = counter + 1
            splitInner tail listMid counter list1 list2
        | ([],_) -> (list1, list2)
    splitInner l listMid 0 [] []

现在添加条件

let split l =        
    let listMid = (int)((length l)/2)
    let rec splitInner l listMid counter list1 list2 =
        match (l,counter) with
        | (head :: tail, c) ->
            if c < listMid then
                let list1 = head :: list1
                let counter = counter + 1
                splitInner tail listMid counter list1 list2
            else
                let list2 = head :: list2
                let counter = counter + 1
                splitInner tail listMid counter list1 list2
        | ([],_)-> (list1, list2)
    splitInner l listMid 0 [] []

head重命名为x,将tail重命名为xs作为个人偏好

let split l =
    let listMid = (int)((length l)/2)
    let rec splitInner l listMid counter list1 list2 =
        match (l,counter) with
        | (x::xs, c) ->
            if c < listMid then
                let list1 = x :: list1
                let counter = counter + 1
                splitInner xs listMid counter list1 list2
            else
                let list2 = x :: list2
                let counter = counter + 1
                splitInner xs listMid counter list1 list2
        | ([],_)-> (list1, list2)
    splitInner l listMid 0 [] []

并在返回列表之前将其反转。

let split l =
    let listMid = (int)((length l)/2)
    let rec splitInner l listMid counter list1 list2 =
        match (l, counter) with
        | (x :: xs, c) ->
            if c < listMid then
                let list1 = x :: list1
                let counter = counter + 1
                splitInner xs listMid counter list1 list2
            else
                let list2 = x :: list2
                let counter = counter + 1
                splitInner xs listMid counter list1 list2
        | ([],_)-> (reverse list1, reverse list2)
    splitInner l listMid 0 [] []

答案 2 :(得分:1)

你在Edit3中有两个问题:

第一个是由编译器标记的,就是当你调用splitInner (l 0 [] [])时。你不需要在这里使用任何括号。

这会编译并显示错误的结果:

(* val split : l:'a list -> 'a list * 'a list *)
let split l = 
    let rec splitInner l counter list1 list2 =
        match (l, counter) with
        | (x::xs, c) ->
            if c < (l.Length/2) then
                let list1 = x :: list1
                let counter = counter+1
                splitInner xs counter list1 list2
            else
                let list2 = x :: list2
                let counter = counter+1
                splitInner xs counter list1 list2
        | ([],_) -> ((reverse list1), (reverse list2))
    splitInner l 0 [] []

split [1;2;3;4;5;6;7;8;9;10]

> split [1;2;3;4;5;6;7;8;9;10];;
val it : int list * int list = ([1; 2; 3], [4; 5; 6; 7; 8; 9; 10])

错误是因为在行if c < (l.Length/2) then中,您每次都要与不同的列表进行比较。

让我们看看每次递归调用会发生什么:

First call to `splitInner`
Parameters
    l = [1;2;3;4;5;6;7;8;9;10]
    counter = 0
    list1 = []
    list2 = []

Values     
    l.Length / 2 = 5
    x = 1
    xs = [2;3;4;5;6;7;8;9;10]
    c = 0
    c < l.Length/2 = True


Second call to `splitInner`
Parameters
    l = [2;3;4;5;6;7;8;9;10]
    counter = 1
    list1 = [1]
    list2 = []

Values     
    l.Length / 2 = 4
    x = 2
    xs = [3;4;5;6;7;8;9;10]
    c = 1
    c < l.Length/2 = True


Third call to `splitInner`
Parameters
    l = [3;4;5;6;7;8;9;10]
    counter = 2
    list1 = [2;1]
    list2 = []

Values     
    l.Length / 2 = 4
    x = 3
    xs = [4;5;6;7;8;9;10]
    c = 2
    c < l.Length/2 = True


Third call to `splitInner`
Parameters
    l = [4;5;6;7;8;9;10]
    counter = 3
    list1 = [3; 2;1]
    list2 = []

Values     
    l.Length / 2 = 3
    x = 4
    xs = [5;6;7;8;9;10]
    c = 3
    c < l.Length/2 = False

您需要的是与原始列表中计算的“固定”值进行比较。

(* val split : l:'a list -> 'a list * 'a list *)
let split (l: 'a list) =
    let middle = l.Length / 2

    let rec splitInner l counter list1 list2 =
        match (l, counter) with
        | (x::xs, c) ->
            if c < middle then
                let list1 = x :: list1
                let counter = counter+1
                splitInner xs counter list1 list2
            else
                let list2 = x :: list2
                let counter = counter+1
                splitInner xs counter list1 list2
        | ([],_) -> ((reverse list1), (reverse list2))

    splitInner l 0 [] []

split [1;2;3;4;5;6;7;8;9;10]
val it : int list * int list = ([1; 2; 3; 4; 5], [6; 7; 8; 9; 10])

答案 3 :(得分:0)

Guy Coder提出了很多将列表分成两部分的方法,这是我在学习mergeSort时学到的另一部分(我相信,这是最简单的一部分):

let split lst = 
   let rec helper lst l1 l2 =
      match lst with
      | [] -> l1, l2 // return accumulated lists
      | x::xs -> helper xs l2 (x::l1) // prepend x to list 1 and swap lists
helper lst [] []