【问题标题】:F#: Recursive functions: Split a list into two equal partsF#:递归函数:将列表分成相等的两部分
【发布时间】:2016-01-31 18:32:16
【问题描述】:

我在拆分和合并排序方面遇到了一些错误。 (注意这是一个格式化的赋值,每个函数都需要特定的输入和输出类型)

这是我现在的代码:

(* 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 函数不能与 split 一起使用,并且最后一行代码中的“then”不正确。

这个想法如下:给定的列表 l 被分成两个相等的(如果 l 的长度是奇数,那么“一半”中的一个比另一个长)列表 l1 和 l2。这些列表以递归方式排序,然后将结果合并回来以提供单个排序列表。用 F# 编写代码。您的算法可以使用

编辑:我相信我不允许在此作业中使用|&gt; List.splitAt。我正在尝试实现一个辅助函数来做同样的事情。

EDIT2:谢谢 Guy Coder,您的回答非常详尽。我需要函数 split 只接收一个列表。也许我可以在里面创建一个使用基于list.Length/2 的索引的辅助函数?假设列表输入为float list,函数返回2float 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

【问题讨论】:

  • 我认为您输入一个列表并返回两个列表的元组。缺少的是确定在哪里拆分列表的值或计数。如果它是一个计数,那么两个列表是一个有效的输出。如果拆分是在字符串中的字符等值上,则输出可能是多个列表。请举一个输入和输出列表的例子。 :)
  • 对于 Edit3 你有 splitInner (1 0 [] []) 你需要 splitInner 1 0 [] [] 注意括号被删除了。您正在将一个元组传递给 curried 参数。我知道你经常使用元组;我更喜欢 curried 参数,因为它们更适合函数式编程。我确实会在有意义的时候使用元组。
  • 您必须将合并排序作为一个单独的问题提出。这一个已成为拆分问题。
  • 对于您的合并排序示例,请参阅:rosettacode.orgfssnip.net

标签: list recursion split f#


【解决方案1】:
// 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,那么将一个放在列表一中,另一个放在列表二中。重复此操作,直到列表中有一个项目或没有项目。如果列表中有一个项目,则将其放入第一个列表并重复。现在输入列表是空的,所以返回两个列表。

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 -&gt; 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 []

命名函数分裂

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

为了把它变成一个条件,我们需要一个计数器。因此,在splitInner l listMid 0 [] [] 中将计数器初始化为0,并将其传递给匹配模式。因为对于最后一个匹配模式,我们不关心计数的值是多少,所以使用_
我们还需要知道将计数器与 listMid 进行比较的对象,因此将其传递给 splitInner
每次使用 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 [] []

现在添加条件

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 重命名为 xtail 重命名为 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】:
    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])
    

    【讨论】:

      【解决方案3】:

      您的 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 &lt; (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])
      

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 2011-07-19
          • 1970-01-01
          • 2012-11-20
          • 2010-11-11
          • 1970-01-01
          • 1970-01-01
          • 2015-05-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多