3.4.1 处理数字列表

3.4.1 处理数字列表

假设我们想写一个类似于 SumList 的方法,只是把加法改成乘法。此更改看起来很简单:我们可以复制 SumList 方法,然后修改它。在修改的方法中只有两个变化:

int MultiplyList(FuncList<int> numbers) {
  if (numbers.IsEmpty) return 1;
  else return numbers.Head * MultiplyList(numbers.Tail);
}

第一个变化是,递归调用的分支中用乘法代替加法,第二个变化是,空列表的返回值是 1 而不是 0。我们在第 2 章说过,这种解决方案是可以工作的,但是,复制代码块决不是好做法。相反,我们想写的是参数化的方法或函数,对列表中的元素,可以加也可以乘,取决于参数。这使得我们可以隐藏可重复使用的函数,列表处理程序,递归中的困难部分,写出的 SumList 或 MultiplyList 将成为一块蛋糕。

这个示例类似于我们第二章(2.3.1 节)中讨论过的。这个解决方案写一个方法或函数,取两个参数值:初始值和操作应该在聚合元素时执行。让我们看看如何在 C# 中实现这一想法。

在 C# 中传递函数作为参数值

你已经见过,在 C# 中,传递函数作为参数值,可以用委托实现,特别是 Func 委托。在清单 3.17 中,这个委托有两个 int 类型的参数值,并返回一个 int 结果。这个代码显示了我们如何以一种递归方法实现聚合,取一个委托作为参数。

Listing 3.17 Adding and multiplying list elements (C#)

int AggregateList(FuncList<int> list, int init, Func<int,int,int> op) {
  if (list.IsEmpty)
    return init;
  else {
    int rest = AggregateList(list.Tail, init, op);
    return op(rest, list.Head);
  }
}
static int Add(int a, int b) { return a + b; }
static int Mul(int a, int b) { return a * b; }
var list = FuncList.Cons(1, FuncList.Cons(2,
  FuncList.Cons(3, FuncList.Cons(4,
  FuncList.Cons(5, FuncList.Empty<int>())))));
Console.WriteLine(AggregateList(list, 0, Add));
Console.WriteLine(AggregateList(list, 1, Mul));

让我们先看看 AggregateList 方法,它取输入的待处理列表作为第一个参数,接下来的两个参数指定输入应做些什么。第二个参数是初始值,是一个整数,列表空时使用,我们想从该方法返回这个初始值。

最后一个参数是委托,用于其他分支,这里,我们首先对列表的其余部分进行递归计算聚合结果,调用 op 委托去计算这个结果和列表的头的聚合。在我们后面的示例中,对给定的参数它可以是加法,也可以是乘法。我们这里使用的委托类型是泛型 Func, 来自 .NET 3.5 的委托,将在第 5 章中进一步讨论。简单地说,它允许我们指定数和参数值的类型,以及使用 .NET 的泛型返回类型。这意味着,当我们调用 op 时,编译器知道我们应提供两个整数作为参数值,它将返回一个整数结果。

在后面的代码中,我们声明两个与委托类型兼容的简单方法:一个用于两个数字的加法,另一个用于乘法。其余部分代码演示如何调用 AggregateList 方法,来得到与我们在前面的示例中相同的结果,由 SumList 和 MultiplyList 返回。

当然,以这种方式写这辅助方法,是有点冗长乏味的,因为它们在代码中的其他任何地方都不再使用。在 C# 2.0 中,可以使用匿名方法使代码更好一点,在 C# 3.0 中,有更优雅的方式编写此代码,使用 lambda 表达式。Lambda 表达式,则相应的功能和在 F# 中的对应功能(称为的 lambda 函数),在真正的函数代码中几乎无处不在,所以我们将在第 5 章中更充分的讨论。在下一节,我们要去看看这一章中的最后一个代码示例,看看如何在 F# 中实现相同的行为。

在 F# 中传递函数作为参数值

在 F# 中的函数 aggregateList 非常类似于我们已经实现的方法。重要的区别在于, F# 天然地支持传递函数作为其他函数的参数值,所以,我们不必为此使用委托。

在 F# 中函数是一种特殊类型。类似于元组,函数的类型是由其他的基本类型构成。对于元组有类型,在代码中表示成,元素类型 之间的星号(例如,int * string)。而函数的类型,表示成参数值的类型和返回类型。它提供了类型安全,与 C# 中的委托所做的相同。一个函数,如果取一个数,加上 1,它的类型是 int -> int,表示它取一个整数,并返回一个整数。取两个数,并返回一个数的函数的类型是 int -> int-> int,我们的 aggregateList 函数的第一个参数的类型就是这个。清单 3.18 显示了示例的 F# 版本。

Listing 3.18 Adding and multiplying list elements (F# Interactive) Takes

> let rec aggregateList (op:int -> int -> int) init list =
  match list with
  | [] -> init
  | hd::tail ->
let resultRest = aggregateList op init tail
op resultRest head
;;
val aggregateList : (int -> int -> int) -> int -> int list -> int
> let add a b = a + b
  let mul a b = a * b
  ;;
val add : int -> int -> int
val mul : int -> int -> int
> aggregateList add 0 [ 1 .. 5 ];;
val it : int = 15
> aggregateList mul 1 [ 1 .. 5 ];;
val it : int = 120

就像在 C# 版本中一样,函数的前两个参数指定列表中的元素如何聚合,第二个参数是初始值,第一参数是一个 F# 函数。在此示例中,我们只想使用整数,使代码更简单,所以,我们为第一个参数加上了类型批注,它指定 op 函数的类型是一个函数,取两个整数并返回一个整数。

接下来,我们看到熟悉的列表处理模式:一个分支用于空列表,一个用于 cons cell。在 F# Interactive 中输入 AggregateList 函数的代码后,它打印出函数的签名,看起来,第一次看到这种签名有点令人望而生畏,但你很快就会熟悉它。在图 3.2 中你可以看到每个签名部分是什么意思。

3-2

图 3.2 aggregateList 函数的类型签名的详细信息。第一个参数值指定如何聚合两个数字,第二个是初始值,第三是输入的列表。

最后,我们编写了两个简单的函数(add 和 mul),分别都有一个类型签名,与 aggregateList 函数的第一个参数的类型相对应,验证这个函数将如期工作。我们写的这两个函数,只是想使示例看上去完全像前面的 C# 版本,但 F# 使我们能够取任意二元运算符,并且使用它就好像是一个普通的函数。这意味着,我们不需要写加法函数,而是直接用加号:

> aggregateList (+) 0 [ 1 .. 5 ];;
val it : int = 15

这个功能通常很有帮助的,使用运算符使 F# 代码非常简洁。注意,当使用运算符代替函数时,必须把它括在括号中,而不是只写 +,必须要写成 (+)。

你可能会想,aggregateList 不是一个特别有用的函数,除了加(乘)列表中的元素以外,没有其它用途,但下一节要显示一个令人吃惊的例子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值