如何改进这个F#功能

时间:2015-09-28 21:11:27

标签: f# functional-programming

我在C#方面经验丰富,但对F#和功能编程不熟悉。现在我想在F#中实现一个类库。这是其中一个功能: 它需要一个整数列表< = 9并且连续变换9,如9,9,9,9到9,10,11,12。例如[9; 9; 9; 1; 4; 0; 1; 9; 9; 9; 9]将改为[9; 10; 11; 1; 4; 0; 1; 9; 10; 11; 12。

C#功能很简单:

void ReleaseCap(List<int> items)
{
    for (int i = 1; i < items.Count; i++)
    {
        var current = items[i];
        var previous = items[i - 1];
        //If curernt value = 9 and previous >=9, then current value should be previous+1
        if (current == 9 && previous >= 9)
        {
            items[i] = previous + 1;
        }
    }
}

现在我的F#tail递归了。它不是通过索引循环List,而是递归地将项目从初始列表移动到已处理的列表,直到初始列表中的所有内容都消失为止:

let releaseCap items =
    let rec loop processed remaining = //tail recursion
        match remaining with
        | [] -> processed //if nothing left, the job is done.
        | current :: rest when current = 9 -> //if current item =9
            match processed with
            // previous value >= 9, then append previous+1 to the processed list
            | previous :: _ when previous >= 9 -> loop (previous+1 :: processed) rest 
            //if previous < 9, the current one should be just 9
            | _ -> loop (current :: processed) rest 
        //otherwise, just put the current value to the processed list
        | current :: rest -> loop (current :: processed) rest 
    loop [] items |> List.rev

虽然C#版本很简单直观,但F#代码很冗长而且不那么直观。是否可以改进F#代码的任何部分以使其更优雅?

3 个答案:

答案 0 :(得分:5)

您可以重复使用现有功能以简化代码。 通常,当您更改列表中的项目时,您会想到map,但在这种情况下,您需要记住以前计算中要记住的某些内容应该为每个项目传递,所以您应该瞄准fold相关功能。

这是一个:List.scan

let releaseCap items =
    items
        |> List.scan (fun previous current -> 
            if current = 9 && previous >= 9 then previous + 1
            else current) 0  
        |> List.tail

FP不只是使用递归而不是循环。递归通常用于基本和可重用的函数,然后通过组合这些函数,您可以解决复杂的问题。

注意:您正在将C#解决方案与F#解决方案进行比较,但是您是否注意到除了语言之外,两种解决方案之间存在重大差异?您的C#解决方案使用可变性。

答案 1 :(得分:2)

在你的C#代码中,你正确地假设列表的第一个元素永远不会改变,并从第二个元素开始。

如果在F#代码中包含此内容,则可以跳过累加器上的匹配。 结果会更简单一些。

   let reduceCap l = 
    let rec reduceCapRec acc l =
        let previous = List.head acc
        match l with
        | x::xs -> 
            if x = 9 && previous >= 9 
            then reduceCapRec ((previous+1) :: acc) xs
            else reduceCapRec (x :: acc) xs
        | [] -> acc

    reduceCapRec [List.head l] (List.tail l)
    |> List.rev

(虽然Gustavo的解决方案仍然好得多 - 我也是FP的新手)

答案 2 :(得分:2)

我认为折叠是重要的一个! : - )

所以:

let list = [9;9;9;1;4;0;1;9;9;9;9] 

let incr acc e =
    match acc, e with
    | h::t, e when e = 9 && h >= 9 -> (h + 1)::acc 
    | _ -> e::acc 


list 
|> List.fold incr []
|> List.rev

//val it : int list = [9; 10; 11; 1; 4; 0; 1; 9; 10; 11; 12]