简体   繁体   English

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

[英]F#: Recursive functions: Split a list into two equal parts

I'm having some errors with split and mergesort. 我在split和mergesort时遇到一些错误。 (Note this is a formatted assignment requiring specific input & output types for each function) (请注意,这是格式化的分配,要求每个功能具有特定的输入和输出类型)

Here is my code right now: 现在是我的代码:

(* 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)

The acc function is not working with split and the "then" in the last line of code is not right. acc函数不适用于split,并且最后一行代码中的“ then”不正确。

The idea is as follows: the given list l is split into two equal (if the length of l is odd then one of the “halves” is one item longer than the other) lists l1 and l2. 想法如下:给定的列表l分为两个相等的列表(如果l的长度是奇数,则“一半”中的一个比另一个长)因此列出了列表l1和l2。 These lists are sorted recursively and then the results are merged back to give a single sorted list. 这些列表以递归方式排序,然后将结果合并回给单个排序列表。 Code this in F#. 用F#编码。 Your algorithm can use < as a comparison operator. 您的算法可以使用<作为比较运算符。 Your code must have a function split that produces a pair of lists, a function merge that merges sorted lists and a function mergesort that implements the overall algorithm. 您的代码必须具有产生一对列表的函数拆分,合并已排序列表的函数合并以及实现整体算法的函数mergesort。

EDIT: I believe I am not allowed to use |> List.splitAt on this assignment. 编辑:我相信我不允许在此作业上使用|> List.splitAt I am trying to implement a helper function which will do the same thing. 我正在尝试实现一个辅助功能,将执行相同的操作。

EDIT2: Thank you Guy Coder, your answers are very thorough. EDIT2:谢谢Guy Coder,您的回答非常详尽。 I need the function split to take in only a list. 我需要将功能拆分为仅包含一个列表。 Perhaps I may create a helper function inside that uses index based upon list.Length/2 ? 也许我可以在内部创建一个基于list.Length/2使用索引的辅助函数? It is assumed that the list input is a float list , and the function returns 2 float lists . 假定列表输入是一个float list ,并且该函数返回2个float lists

EDIT3: I feel much closer: here is my split function and the error I receive. 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
let halve xs = xs |> List.splitAt (xs.Length / 2)

Examples: 例子:

> 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])
// 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])

Another variation from RosettaCode 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 [] []

How this one works. 这是如何工作的。

The match has three patterns 比赛有三种模式

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

This one says we don't need to know the length of the list because if we have at least two items in the list | x::y::tail 这说我们不需要知道列表的长度,因为如果列表中至少有两个项目| x::y::tail | x::y::tail then put one in list one and the other in list two. | x::y::tail然后将一个放在列表1中,另一个放在列表2中。 This is repeated until there is one or no items in the list. 重复此操作,直到列表中没有一个或没有任何项目。 If there is one item in the list put it into the first list and repeat. 如果列表中有一项,则将其放入第一个列表并重复。 Now the input list is empty so return the two list. 现在输入列表为空,因此返回两个列表。

A third variation from fssnip.net 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

by Joel Huang which uses Continuation-passing style JoJoel Huang)使用延续传递风格

This one passes a function to the inner function. 该函数将一个函数传递给内部函数。 The function being (fun x -> x) and the inner function receiving it in the cont parameter. 函数为(fun x -> x) ,内部函数在cont参数中接收它。 This one also has three match patterns. 这也有三种匹配模式。

| []   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.  

However since it is passing a function for each recursive call, the evaluation of the function that is built up is not evaluated until the list is done being passed through the recursive functions. 但是,由于它正在为每个递归调用传递一个函数,因此,直到列表通过递归函数传递完成之后,才会评估所构建函数的评估。 It also uses a counter, but counts down to 0 to avoid having to pass an extra parameter. 它还使用一个计数器,但递减至0,以避免必须传递额外的参数。 This is advanced coding so don't spend a lot of time trying to understand it, but be aware that because functions are first-class this is possible. 这是高级编码,因此无需花费大量时间来理解它,但是请注意,因为函数是一流的,所以这是可能的。

A fourth variation from TheInnerLight - posted just the other day. TheInnerLight的第四个变体-前几天 发布

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

How I created split. 我如何创建拆分。

Starting with the format 从格式开始

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

name the function split 将功能命名为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 []

Now I know I need one input list and two output list with the two output list in a tuple 现在我知道我需要一个输入列表和两个输出列表,其中两个输出列表在一个元组中

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

since the split will divide the list in half, that can be calculated before iterating through the list 由于拆分会将列表分为两半,因此可以在遍历列表之前进行计算

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

To turn this into a conditional we need a counter. 要将其变为有条件的,我们需要一个计数器。 So initialize the counter to 0 in splitInner l listMid 0 [] [] and pass it through to the match patterns. 因此,在splitInner l listMid 0 [] []中将计数器初始化为0 ,然后将其传递给匹配模式。 Since for the last match pattern we do not care what the value of the count is, _ is used instead. 由于对于最后一个匹配模式,我们不在乎计数的值是什么,因此使用_代替。
We also need to know what to compare the counter against, which is listMid so pass that through to splitInner . 我们还需要知道将计数器与之进行比较的东西(即listMid ,然后将其传递给splitInner
Also increment the counter for each use of head. 每次使用磁头时也要增加计数器。

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

and now add the conditional 现在添加条件

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

rename head to x and tail to xs as a personal preference 根据个人喜好将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 [] []

and reverse the list before returning them as the result. 并在返回结果之前反转列表。

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

You stil have two problems in your Edit3: 您在Edit3中仍然遇到两个问题:

The first one, marked by the compiler, is when you call splitInner (l 0 [] []) . 第一个由编译器标记的是在您调用splitInner (l 0 [] []) You don´t need to use any parenthesis here. 您无需在此处使用任何括号。

This compiles and shows an incorrect result: 这将编译并显示不正确的结果:

(* 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])

The error is because in the line if c < (l.Length/2) then you are comparing against a different list each time. 该错误是因为在该行中, if c < (l.Length/2) then您每次都与一个不同的列表进行比较。

Lets see what happens on each recursive call: 让我们看看每次递归调用会发生什么:

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

What you need is to compare against a "fixed" value calculated on your original list. 您需要将其与原始列表中计算出的“固定”值进行比较。

(* 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])

Guy Coder proposed quite a number of way to split a list into two parts, here is another one I have been taught when studying mergeSort (and, I believe, one of the easiest) : 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 [] []

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM