6.7.3 实现列表函数

6.7.3 实现列表函数

 

    不显示如何实现刚才我们看到的筛选和映射函数,先来看看从第 3 章开始创建的函数。因为,所有列表处理函数都有类似的结构,你在看下面的示例后,可以能够实现其他任何的函数。

    在第 3 章,我们写了一个函数,它可以列表中的所有元素求和或求积。后来,我们就意识到它是比首次出现时更有用:我们看到,它也能用来查找最小或最大元素。那时,我们没有讨论过泛型,因此,这个函数只处理整数。在清单 6.22 中,我们

看一个类似的函数,没有原先限制自动泛型化的类型批注。

 

Listing 6.22 Generic list aggregation (F# Interactive)

 

> let rec fold f init list =
     match list with
     | [] –> init
     | head::tail –>
       let state = f init head
       fold f state tail
;;
val fold : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a

 

    这个实现非常像第 3 章中的那个。更重要的是,我们去掉了类型注释,因此,推断的签名是更通用的。该函数现在取一个列表,其值的类型为 'b,由聚合产生的值可以有不同类型(类型参数为 'a)。这个处理函数取当前的聚合结果(类型为 'a),和来自列表中的上一个元素('b),并返回一个新的聚合的结果。

    我们将很快看到,泛型的使用使得聚合更有用。它在 F# 库中也可用。处理不可变的 F# 列表类型的版本位于 List 模块中。下面的代码片断显示了我们从原来的第 3 章中的用法,求列表中的所有元素的积:

 

> [ 1 .. 5 ] |> List.fold (*) 1
val it : int = 120

 

    由于我们将要处理泛型函数,编译器必须首先推断出类型参数的类型。这里,我们处理的是整数列表,所以,参数 'b 是 int,结果也是整数,所以 'a 也是 int。清单 6.23 显示其他一些使用 fold 的有趣例子。

 

Listing 6.23 Examples of using fold (F# Interactive)

 

> places |> List.fold (fun sum (_, pop) -> sum + pop) 0;;
val it : int = 9080788

> places |> List.fold (fun s (n, _) -> s + n + ", ") "";;
val it : string =
"Seattle, Prague, New York, Grantchester, Cambridge, "

> places
     |> List.fold (fun (b,str) (name, _) –>
           let n = if b then name.PadRight(20) else name + "/n"
           (not b, str+n)
         ) (true, "")
     |> snd
     |> printfn "%s";;

Seattle Prague
New York Grantchester
Cambridge

 

    在所有示例中,我们处理的都是有关城市信息的集合,因此,列表的类型总是相同的。这意味着,参数 'b 的实际类型总是 (string * int) 元组。然而,聚合的结果不同。在第一种情况下, 我们只求人口的和,因此,结果的类型是 int。在第二个示例中,我们要建立有城市名字的字符串,因此,我们开始的聚合是空字符串。作为第一个参数值的 lambda 函数将追加当前处理的城市名字和分隔符。

    在最后一个示例中,我们实现了改进格式的版本,将城市名字写成两列。这意味着,lambda 函数执行两个交替操作。第一种情况,用空格填充名字(填充第一列),在第二种情况中,则将添加换行符(以结束行)。这通过使用 bool 类型的临时值完成,最初设置为 true,然后,在每次迭代中切换。聚合值包含这个交替的临时值,和作为结果的字符串,因此,在结束时,我们需要从元组中删除临时值。

 

在 C# 中实现 fold

 

    与 fold 有相同行为的操作,在 .NET 库中也是可用的。虽然,它的名字叫Aggregate(聚合) 。通常,它是用作扩展方法,工作任何集合类型上,我们可以使用它,与在 F# 函数相同的方式。让我们在 C# 3.0 中重写来自清单 6.21 的上一个示例。在 F# 中,我们用一个元组来存储在聚合过程中的状态。你也许还记得以前的几章中,我们提到过,C# 3.0 的匿名类型有时可以用于同一目的。这是一个真的非常适合它们的示例:

 

var res =
  places.Aggregate(new { StartOfLine = true, Result = "" },
  (r, pl) => {
    var n = r.StartOfLine ? pl.Name.PadRight(20) : (pl.Name + "/n");
    return new { StartOfLine = !r.StartOfLine, Result = r.Result + n };
  }).Result;

 

    在 C# 中,初始的值被指定为第一个参数值。我们创建一个匿名类型,有一个标志 StartOfLine(用作临时值), 和属性Result,其中存储连接的字符串。作为第二个参数值的 lambda 函数,做与前面的 F# 示例同样的事,但返回的结果也是匿名类型,与初始值具有相同的结构。为使代码更多有效,我们也可以使用 StringBuilder 类代替连接字符串,但是,我们想要显示最简单的可能的示例。

    现在,已经知道了如何在 C# 中使用该函数,我们应该看看它是如何实现的。在清单 6.24 中,可以看到两个实现。其中一个是一个典型函数式实现,处理来自第 3 章的函数列表,另一个是命令式实现,处理泛型的 .NET List 类型,原则上,它与 .NET 库中的聚合的扩展方法相同。

 

Listing 6.24 Functional and imperative implementation of Fold (C#)

 

// Functional implementation using 'cons list'
R Fold(this FuncList list, Func func, R init) {
  if (list.IsEmpty)
    return init;
  else {
    var state = func(init, list.Head)
    return list.Tail.Fold(func, state);
  }
}

// Imperative implementation using 'List'
R Fold(this List list, Func func, R init) {
  R temp = init;
  foreach(var item in list)
    temp = func(temp, item);
  return temp;
}

 

    除了使用不同的集合类型之外,这两个方法的签名是一样。它对应于前面 F# 中的声明,虽然,我们必须显式地写出类型参数。在这两种情况下,我们使用列表作为第一个参数,方法被实现为处理集合类型的扩展。

    在函数式的版本中,们有两个分支。第一个是处理空列表的情况,第二个分支递归地处理 cons cell,并使用 fun(函数)参数聚合结果。命令式版本,声明一个本地可变值,存储聚合过程中的当前结果。聚合的值通过遍历的所有元素来计算,并在每个迭代中更新这个值。

    正如我们已经提到的,其他操作的实现有非常类似的过程。在 map 和 filter 的函数式版本中,return init; 可能返回一个空列表,在命令式版本中,可以使用可变列表作为临时值,其他改变在这两行:R Fold(… 和 temp = func(temp, item);。当执行映射时,只要调用给定的函数,而对于筛选,可能决定是否要追加当前元素。

    要结束我们关于高阶函数的讨论,将重点介绍几个函数之间有趣的关系,用来操作列表,和用来处理选项值的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值