6.7.2.1 处理列表

728 篇文章 1 订阅
349 篇文章 0 订阅

6.7.2.1 处理列表

 

我们看一个有关使用筛选和映射更大的示例,在 F# 库中的两个函数适用于各种集合类型,但我们将只用它来处理我们已经很熟悉的列表;在 C# 中,这些方法可用于任何实现了 IEnumerable<T> 接口的集合,所以,我们将使用泛型 .NET List <T> 类。清单 6.21 显示了我们将要处理数据的初始化。

 

清单 6.21 有关城市人口的数据 (C# and F#)

 

// C# version using a simple class

class CityInfo {                           [2]

  publicCityInfo(string name, int population) {

   Name = name; Population = population;

  }

 public string Name { get; private set; }

 public int Population { get; private set; }

}

var places = new List<CityInfo> { newCityInfo("Seattle", 594210),      [3]

  newCityInfo("Prague", 1188126), new CityInfo("New York",7180000),

  newCityInfo("Grantchester", 552), new CityInfo("Cambridge",117900) };

 

// F# version using tuples

> let places =                          [1]

    [("Seattle", 594210); ("Prague", 1188126); ("NewYork", 7180000);

     ("Grantchester", 552); ("Cambridge", 117900) ];;

val places : (string * int) list

 

在 F# 中,我们将使用常见的例子,有关城市名字和人口的信息的列表[1]。虽然,我们可以把 F# 元组转换成我们已经实现的 Tuple 类,但这一次,我们会使用更典型 C# 表示,声明CityInfo 类[2],用它创建包含城市信息的列表[3]。

在 C# 中,我们可以使用在 .NET 3.5 中的 Where 和 Select 方法处理数据,两者都是扩展方法,因此,能够使用通常的点表示法来调用:

 

var names =

 places.Where(city => city.Population > 1000000)

      .Select(city => city.Name);

 

另外,这也展示了使用高阶操作的好处。作为参数值给定的 lambda 函数,描述了筛选的条件(第一种情况),或为每个城市(第二种情况)指定返回值。这是我们必须指定的;但不需要了解集合的底层结构,也不指定应该如何获得结果,所有这些被封装在高阶操作中。

在 F# 中执行相同的操作。首先,想要筛选数据集,然后,只选择城市的名字。我们可以通过调用 List.filter,并用结果作为给 List.map 函数的最后参数。可以发现,这看起来很丑陋,且难于阅读:

 

let names =

 List.map fst

        (List.filter (fun (_, pop) -> 1000000 < pop) places)

 

当然,F# 可以做得更好。前面的 C# 版本之所以优雅,是因为编写的操作,与执行(先筛选,再映射)的顺序相同,可以把一个操作写在单独的一行上。在 F# 中,通过使用管道,能够得到相同的代码布局:

 

let names =

 places |> List.filter (fun (_, pop) -> 1000000 < pop)

      |> List.map fst

 

在这里,管道运算符首先将左边的值(places)传递给右边的筛选函数;下一步,把前面的操作结果传递给下一个操作(映射)。尽管我们使用这个运算符有相当一段时间了,但这个示例最终展示了为什么会被称为“管道”。数据元素依次通过管道进行处理,管道是由使用管道运算符,把几个操作链接起来创建的。

注意,操作的顺序有时重要,有时不重要。这里,我们必须先执行筛选;如果在第一步先做映射,得到的列表仅包含城市名字,就不会有人口的信息,而这是筛选所需要的。

在 F# 中写列表处理函数,可以把管理和其他函数式技术组合起来,比如散函数应用和函数组合。我们简单地看一下写处理代码时,下一个步应该做什么:

 

let names =

 places |> List.filter (snd >> ((<) 1000000))

      |> List.map fst

 

使用函数组合生成筛选函数,而没有显式使用 lambda 表达式。第一个函数是 snd,它返回元组的第二个元素;在这里,它表示人口。在组合中使用的第二个函数,是散应用运算符,我们仅指定第一个参数值,因此,会得到一个函数,当第二个参数值大于给定数字时,返回 true。

 

提示

 

写代码(不只是在函数风格中)时,应该始终考虑到,在以后需要对其进行修改时,理解代码是很困难的。在前面的示例中,使用函数组合的版本并不是特别简短,看上去也不更优雅;事实上,我们觉得其可读性比显式使用 lambda 函数的版本要差,因此,在这种情况下,我们更倾向于使用 lambda 表示法。很多情况下,函数组合能显著简化代码。不幸的是,没有简单的规则可以遵循,我们能提供的最好忠告,是使用常识,并想象有其他人试图理解这段代码。

 

处理集合数据是我们经常要做的任务,因此,编程语言的设计者都努力使其尽可能容易;现在,C# 和 F# 都提供了更方便的方式,来解决我们刚才用高阶函数实现的这个任务。理解高阶函数的原理是必要的,因为,它能处理任何数据结构,而不仅仅是列表。

 

C# 3.0 的查询表达式和 F# 的序列表达式

 

你可能已经看到过,在 C# 中使用查询表达式(query expressions)写数据查询的示例。使用这个功能写我们前面的代码,看起来像这样:

 

var names = from p in places

         where 1000000 < p.Population

         select p.Name

 

这经常作为一项关键的新功能来演示的,但它不会没有底层机制,比如 lambda 函数和高阶操作。我们已经关注使用这些,因为为学会显式使用它们时,能够用类似的函数式方法来处理任何数据,而不仅是集合。

简化的语法非常有用,在 F# 中也有类似的功能,称为序列表达式(sequence expressions)。我们将在第十二章讨论有关内容,但对于好奇的人来说,下面是用 F# 写的同样查询:

 

let names =

  seq{ for (name, pop) in places do

       if (1000000 < pop) then yield name }

 

它看起来非常像括起来,并用 seq 标记的普通代码块,这是故意的,因为在 F# 中,它是更通用的语言构造,也能用于处理其他值。在第十二章,我们将看到用它来处理选项值,也会看到 C# 查询表达式能用于类似的目的。

 

我们已经看过使用两个最常见的列表处理函数,它们非常有用,现在要深入了解第三个这样的函数,并自己来实现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值