我在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#代码的任何部分以使其更优雅?
答案 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]