13.4.2 用度量单位格式化数据

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

13.4.2 用度量单位格式化数据

 

    当从 XML 数据读许多指标值的,我们只能将它们转换为浮点(float)值。那是可行的,因为面积和森林覆盖率都是数字,但是,它不能告诉我们数据的更多内容。将数据从非类型化的 XML 转换为 F# 类型化数据结构的目的是,用类型进行注释,有帮于了解这些值的含义。为了使类型更具体,我们可以使用在第 2 章中提到的度量单位。使用这个功能,可以指定面积以平方公里计,森林覆盖面积以占总地面积的百分比计。让我们首先来看几个例子,介绍一下度量单位。

 

使用度量的单位

 

    在 F# 中,使用度量单位是很容易的,这就是为什么我们在这一章介绍它们,作为简短的题外话。我们可以声明度量,使用 type 关键字,带专有的属性。严格地说,度量并不是类型,但我们可以将其用作为另一种类型的部分使用。让我们首先定义两个简单的度量,表示小时和公里:

 

[<Measure>] type km
[<Measure>] type h

 

    正如你所看到的,我们使用 Measure 属性来指定这个类型是度量。这是一个专有属性,F# 编译器能理解。不一定要我们自己定义单位,我们也可以使用在 FSharp.PowerPack.dll 库中的标准集,但是,现在,我们将使用自己的声明。现在,我们有了单位 km 和 h,就可以创建表示公里或小时的值。清单 13.15 显示了如何创建有单位的值,如何写一个函数,用它们参与计算。

 

Listing 13.15 Writing calculations using units of measure (F#)

 

> let length = 9.0<km>;;
val length : float<km> = 9.0

> length * length;;
val it : float<km^2> = 81.0

> let distanceInTwoHours(speed:float<km/h>) =
speed * 2.0<h>;;
val distanceInTwoHours : float<km/h> -> float<km>

> distanceInTwoHours(30.0<km/h>);;
val it : float<km> = 60.0

 

    当我们要指定数字常量的单位时,我们将括尖括号角中的单位加到值的后面。我们首先定义一个值,表示在长度,以公里计。如果我们用带单位的值写计算,F# 自动推断出结果的单位,所以,我们可以看到,距离乘了两次,得出以平方公里计的面积。当指定单位时,可以使用常规的表示法,所以,^ 表示乘方,/ 用于除,乘法写成并列单位。

    下一个示例显示,我们就可以写带参数的函数,其中包括有关单位的信息。我们的示例函数取速度作参数,返回在两小时行走的距离。我们想指定参数以公里每小时计,所以,我们添加包含单位的类型注释。写成把单位放在尖括号中,与指定类型参数值时的方式相同,比如 list<int> 类型。F# 编译器推断出返回的类型,就像处理普通的类型一样。和往常一样,当我们读到这个类型时,可以理解函数在执行什么。这也是有价值的检查,避免了写函数时犯低级错误:如果我们试图计算距离,但返回的类型,最后单位是时间,我们便知道错了。

    在我们的世界银行数据中,我们将使用单位 km^2 表示国家的总面积。所以,到目前为止,一切顺利:但是,我们获得的第二个指标是按百分比提供的。我们如何可以指定百分比的单位?虽然,度量单位主要是用来表示物理单位的,也可以使用它们来表示百分比:

 

[<Measure>] type percent
let coef = 33.0<percent>

 

 

    这段代码创建一个单位,指定数字表示百分比,然后,定义了一个常数 coef,值是 33%。严格地说,以百分比计的值没有单位,因为,它是一个系数,但是,它定义成单位是很有用的。为了演示,让我们尝试计算 50 公里距离的 33%。由于 coef 表示系数,我们能够简单地把这两个值乘起来:

 

> 50.0<km> * coef;;
val it : float<km percent> = 1650.0

 

    这是很明显的错误。我们期望的结果是以公里计,但是,如果你看看推导出的类型,可以看到,结果是以公里乘以新的单位百分比计。因为我们是以交互方式运行的代码,还可以看出,数字太大,而度量单位的伟大之处在于,我们可以在类型检查期间就发现错误,而不必实际运行这个程序。所以哪里出了问题呢?问题是一个百分比值表示一个系数乘以 100。要正确写出计算,需要除以 100% 的值:

 

> 50.0<km> * coef / 100.0<percent>;;
val it : float<km> = 16.5

 

    正如你所看到的,现在好得多了。我们将结果除以 100%,这意味着,在结果中没有百分比的单位了。F# 自动简化了单位,知道 km percent/percent 是等同于 km。此示例演示了使用度量单位的重要原因:就像其他类型一样,它们帮助我们尽可能早的捕获大量的错误。

 

注意

 

    度量单位有很多其他有趣的地方,这个介绍我们没有涉及到。例如,可以定义派生单位,比如 N (表示力,以牛顿计),这实际上就是 kg m/s^2。在函数或类型中使用单位作为泛型类型参数,也是可能的。有关度量单位的详细信息,请参阅 F# 联机文档,和架构师 Andrew Kennedy 关于该功能的博客(http://blogs.msdn.com/andrewkennedy)。

 

    让我们回到主要的示例中,把下载到的数据转换成,包含相关单位信息的类型化形式。我们将使用 percent 的原子单位,表示地区森林覆盖,km^2 单位表示面积。

 

格式化世界银行的数据

 

    当我们声明 readValues 函数,从 XML 文档中读取值的时候,我们提供了一个解析函数,作为最后一个参数。这用于将每个数据点转换到适当类型的值。我们下载该数组包含以平方公里计的面积的三个数据集,和森林覆盖率的三个数据集。清单 13.16 显示,如何把原始文档转换成从一个数据结构,从中可以轻松地提取重要信息。

 

Listing 13.16 Converting raw data into a typed data structure (F#)

 

let areas =
  Seq.concat(data.[0..2])
    |> readValues (fun a -> float(a) * 1.0<km^2>)
    |> Map.ofSeq
let forests =
  Seq.concat(data.[3..5])
    |> readValues (fun a -> float(a) * 1.0<percent>)
    |> Map.ofSeq

 

    第一个处理流运算把所有页中的数据连接第一个指标,将每个值从字符串转换以平方公里计的数字,然后,从数据生成一个映射。第二个命令,处理森林覆盖面积是类似的。

    数据处理的主要部分是使用流运算写的。它使用一种我们还没未介绍的新功能,从数据集中取前三个元素。这被称为切片(slicing),语法 data.[0..2] 生成一个序列,包含索引从 0 到 2 的数组项。用 Seq.concat 连接返回的序列,这样,就能得到一个包含所有年份数据的序列。流运算的下一步是读这些值,并将其转换为适当的带度量单位的类型。这原来是最简单的部分,就是一个简单的 lambda 表达式!要注意的是,世界银行使用点作为分隔符,所以,数字就如 1.0。内置的 float 函数始终使用固定区域设置,因此,在任何系统上,它都能正确解析字符串。

    我们使用 Map.ofSeq 函数,从数据生成 F# 映射类型。这个函数取包含元组的序列作为参数值,并以第一个元素作为键值,第二个元素作为值。在清单 13.16 中,键值的类型为 int * string,其中包含年和区域名。在第一种情况中的值的类型为 float<km^2>,第二种情况为 float<percent>。我们已经把数据转换成映射,因此,可以轻松地查看不同年份和区域的各项指标。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值