码迷,mamicode.com
首页 > 其他好文 > 详细

第七章 F# 库(三)

时间:2014-07-22 23:02:32      阅读:208      评论:0      收藏:0      [点我收藏+]

标签:使用   os   文件   数据   io   for   

第七章 F# 库(三)


序列(Microsoft.FSharp.Collections.Seq)模块

 

Microsoft.FSharp.Collections.Seq 模块包含所有处理集合的模块,只要它支持 IEnumerable 接口, .NET 框架的 BCL 中的大多数集合都是的。这个模块之所以称为序列(Seq),是因为序列是IEnumerable 接口的别名,是对其简称,为了读、写更方便。给定类型定义时使用这个别名。

 

注意

 

FSLib 包含几个模块,用于处理不同类型的集合,包括Array(数组)、Array2(二维数组)、(三维数组)、Hashtbl(哈希表)、IEnumerable、LazyList、List、Map 和 Set。我们只讨论序列,是由于它有能力处理大量不同类型的集合,因此,通常更受青睐。另外,虽然每一种模块都有它们专门的函数,但是,仍有许多函数是共有的。

 

这些函数的一部分可以用列表推导(list comprehension)语法替换,我们在第三、四章有过讨论。对于简单的任务和非类型化的集合(untyped collections),保用列表推导更简单;但是,对于更复杂的任务,还是要使用这些函数。

map 和 iter:这两个函数把给定的函数应用到集合中的每一项;

concat:这个函数把集合的集合连接成一个集合;

fold:这个函数通过把集合中的项集中到一起,创建汇总;

exists 和 forall:这两个函数判断集合的内容;

filter, find 和 tryFind:这些函数挑选集合中符合条件的元素;

choose:这个函数同时执行筛选和映射;

init 和 initInfinite:这两个函数初始化集合;

unfold:这个函数以更灵活的方法初始化集合;

cast:这个函数转换 IEnumerable 的非泛型版本,而不是 IEnumerable<T>。

 

 

map 和 iter 函数

 

我们首选看一下map 和 iter 函数,这两个函数把给定的函数应用到集合中的每一项。它们之间的不同在于,map 是通过转换集合中的每一个元素,创建一个新的集合;而 iter 是把一个有副作用的运算应用到集合中的每一项。典型的副作用就是把元素写到控制台。下面的示例演示了map 和 iter 函数的应用:

 

let myArray = [|1; 2; 3|]

 

let myNewCollection =

 myArray |>

 Seq.map (fun x -> x * 2)

 

printfn "%A" myArray

 

myNewCollection |> Seq.iter (fun x ->printf "%i ... " x)

 

代码的运行结果如下:

 

[|1; 2; 3|]

2 ... 4 ... 6 ...

 

 

concat 函数

 

前面的示例使用了数组(array),因为这种集合类型初始化很方便,实际上,是可以使用 BCL 中所有的集合类型。下面的示例使用System.Collections.Generic 命名空间下的列表(List)类型,来演示concat 函数的用法,其类型为#seq< #seq<‘a> > -> seq<‘a>,将可枚举(IEnumerable)值的集合连接成一个可枚举值:

 

open System.Collections.Generic

 

let myList =

  lettemp = new List<int[]>()

 temp.Add([|1; 2; 3|])

 temp.Add([|4; 5; 6|])

 temp.Add([|7; 8; 9|])

 temp

 

let myCompleteList = Seq.concat myList

 

myCompleteList |> Seq.iter (fun x ->printf "%i ... " x)

 

代码的运行结果如下:

 

1 ... 2 ... 3 ... 4 ... 5 ... 6 ... 7 ... 8... 9 ...

 

 

fold 函数

 

下面的示例演示 fold 函数,它的类型为(‘b -> ‘a -> ‘b) -> ‘b -> #seq<‘a> ->‘b。这个函数在整个函数调用过程中,使用一个累加器值,产生集合的汇总。函数有两个参数,第一个参数是逻辑器,它是前一个函数的结果,第二个参数集合中元素;函数体组合这两个值,产生一个新的值,其类型与累加器相同。在下面的示例中,myPhrase 的元素连接到一个累加器中,这样,所有的字符串最后形成一个字符串。

 

let myPhrase = [|"How";"do"; "you"; "do?"|]

 

let myCompletePhrase =

 myPhrase |>

 Seq.fold (fun acc x -> acc + " " + x) ""

 

printfn "%s" myCompletePhrase

 

代码的运行结果如下:

 

How do you do?

 

 

exists 和 forall 函数

 

下面的示例演示了两个函数,能够用来确定集合的内容。这两个函数的类型都是(‘a -> bool) -> #seq<‘a> ->bool。exists 函数确定集合中任意元素是否满足特定的条件;必须满足的条件由传递给exists 的函数决定,如果有任意的元素满足条件,exists 就返回真;forall 函数也很相似,但不同的是,必须集合中的所有元素都满足条件,才返回真。下面的示例第使用exists 确定集合中的元素是否有 2 的倍数,再使用 forall 来确定集合的所有元素是否都是 2 的倍数:

 

let intArray = [|0; 1; 2; 3; 4; 5; 6; 7; 8;9|]

 

let existsMultipleOfTwo =

 intArray |>

 Seq.exists (fun x -> x % 2 = 0)

 

let allMultipleOfTwo =

 intArray |>

 Seq.forall (fun x -> x % 2 = 0)

 

printfn "existsMultipleOfTwo: %b"existsMultipleOfTwo

printfn "allMultipleOfTwo: %b"allMultipleOfTwo

 

代码的运行结果如下:

 

existsMultipleOfTwo: true

allMultipleOfTwo: false

 

 

filter、find 和 tryFind 函数

 

下面的示例看的三个函数与exists 和 forall 有些相似;这些函数分别是filter,其类型为type (‘a -> bool) -> #seq<‘a> -> seq<‘a>,find,其类型为 (‘a-> bool) -> #seq<‘a> -> ‘a,和 tryfind,其类型为 type(‘a -> bool) -> #seq<‘a> -> ‘a。它们之所以与 exists 和 forall 相似,是因为它们都是用函数来确定集合的内容;但这些函数并不返回布尔值,而是返回实际找到的值。函数filter 用传递给它的函数测试集合中的每一项;函数filter 然后返回一个列表,包含满足这个函数条件的所有元素;如果没有元素满足条件,返回空列表。find 和 tryFind 函数则返回集合中满足条件的第一个元素,条件由传递进来的函数决定;当集合中没有满足条件的元素时,其行为有所改变。如果没有找到元素,find 会引发异常;tryFind 则返回可选类型(option)None。因为 .NET 中的异常代价高昂,所以,最好用tryFind 代替 find。

在下面的示例中,将检查一个单词列表,首选用 filter 创建一个列表,只包含以 at 结尾的单词;再用 find 查找第一个以 ot 结尾的单词;最后,用tryfind 单词中是否有以 tt 结尾的单词。

 

let shortWordList = [|"hat";"hot"; "bat"; "lot"; "mat";"dot"; "rat";|]

 

let atWords =

  shortWordList

  |>Seq.filter (fun x -> x.EndsWith("at"))

 

let otWord =

  shortWordList

  |>Seq.find (fun x -> x.EndsWith("ot"))

 

let ttWord =

  shortWordList

  |>Seq.tryFind (fun x -> x.EndsWith("tt"))

 

atWords |> Seq.iter (fun x -> printf"%s ... " x)

printfn ""

printfn "%s" otWord

printfn "%s" (match ttWord with |Some x -> x | None -> "Not found")

 

代码的运行结果:

 

hat ... bat ... mat ... rat ...

hot

Not found

 

 

choose 函数

 

下一个要看的序列函数是一个聪明的函数,它可以同时完成筛选(filter)并映射(map),这个函数称为 choose(选择),其类型为(‘a -> ‘b option) -> #seq<‘a> -> seq<‘b>。要使用这个函数,传递给choose 的函数必须是返回选项(option)类型。如果列表中的元素可能转换成有用的内容,函数就会返回 Some,包含这个新值;如果没有希望的值,函数返回 None。

在下面的示例中,我们把一个浮点数列表乘以 2,如果是整数,就返回;否则,就滤掉。这样,剩下的列表就是整数了。

 

let floatArray = [|0.5; 0.75; 1.0; 1.25;1.5; 1.75; 2.0 |]

 

let integers =

 floatArray |>

 Seq.choose

   (fun x ->

     let y = x * 2.0

     let z = floor y

     if y - z = 0.0 then

       Some (int z)

     else

       None)

 

integers |> Seq.iter (fun x -> printf"%i ... " x)

 

代码的运行结果如下:

 

1 ... 2 ... 3 ... 4 ...

 

 

init 和 initInfinite 函数

 

接下来,我们看两个初始化集合的函数:init,其类型为:int -> (int -> ‘a) -> seq<‘a>,initInfinite,其类型为:(int-> ‘a) -> seq<‘a>。可以用 init [ 这里可能是笔误,原文为initInfinite ] 函数来创建一个有限大小的集合;调用这个函数,传递给它次数,以及传递对这一次的说明。可以使用initInfinite 函数创建无限大小的集合;调用这个函数,传递给它的参数是每一次创建新元素的方法。在理论上,可以创建无限大小的列表,但是,实际中是受机器执行计算的限制。

下面的示例演示了用 init 来创建有 10 个整数的列表,每一个值都是 1;还演示了创建可以包含所有可能的 32 位整数的列表,并用 take 函数取列表的前 10 项。

 

let tenOnes = Seq.init 10 (fun _ -> 1)

let allIntegers = Seq.initInfinite (fun x-> System.Int32.MinValue + x)

let firstTenInts = Seq.take 10 allIntegers

 

tenOnes |> Seq.iter (fun x -> printf"%i ... " x)

printfn ""

printfn "%A" firstTenInts

 

代码的运行结果如下:

 

1 ... 1 ... 1 ... 1 ... 1 ... 1 ... 1 ... 1... 1 ... 1 ...

[-2147483648; -2147483647; -2147483646;-2147483645; -2147483644; -2147483643;

-2147483642; -2147483641; -2147483640;-2147483639]

 

 

unfole 函数

 

我们已经在第三章中见到过 unfold 函数,它比函数 init 和 initInfinite 更加灵活。unfold 的第一个好处是可以整数计算的过程中传递一个累加器,这样,就能够保存计算过程吕的一些状态,而不必只依靠在列表中的当前位置来计算值,如在init 和 initInfinite 中所做的;第二个好处,产生的列表既可以是有限的,也可以是无限的。这两个好处的实现,是通过把的返回类型传递给 unfold。函数的返回类型是‘a * ‘b option,表示一个可选(option)类型,包含一个元组值。在这个可选类型中的第一个值是将要放在列表中的值;第二个值是累加累加器。如果想让列表持续下去,放返回 Some,在其中包含这个元组;如果想停止,就返回 None。

下面的示例是第三章中的重复,演示了用 unfold 计算斐波那契(Fibonacci)数。可以看到,累加器用来保存元组,其值表示在斐波那契数列中的下两个数。因为斐波那契数的列表是无限的,因此,根本不需要返回 None。

 

let fibs =

  (1,1)|> Seq.unfold

    (fun(n0, n1) ->

     Some(n0, (n1, n0 + n1)))

 

let first20 = Seq.take 20 fibs

printfn "%A" first20

 

代码运行的结果如下:

 

[1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144;233; 377; 610; 987;

1597; 2584; 4181; 6765]

 

下面的示例演示了用 unfold 来产生一个可终止的列表,假设我们想计算一个数列,其值是当前值的一半,就如原子核的衰变;假设超过某一特定值,数字变得很小,我们就可以忽略它了。这样的序列是通过当值达到限定时,返回 None 来建模的:

 

let decayPattern =

  Seq.unfold

    (funx ->

     let limit = 0.01

     let n = x - (x / 2.0)

     if n > limit then

       Some(x, n)

     else

       None)

  10.0

 

decayPattern |> Seq.iter (fun x ->printf "%f ... " x)

 

代码的运行结果如下:

 

10.000000 ... 5.000000 ... 2.500000 ...1.250000 ...

0.625000 ... 0.312500 ... 0.156250 ...0.078125 ... 0.039063 ...

 

 

generate 函数

 

generate 函数的类型为 (unit ->‘b) -> (‘b -> ‘a option) -> (‘b -> unit) -> seq<‘a>,可用于创建可枚举(IEnumerable)的集合,可以从一些各类的游标(cursor)创建集合,比如文件系统,或者数据库的记录集。游标可以是文件系统,如下面的示例所演示的,也可以更为常见的数据库游标。事实上,它可以是能够生成序列元素的任何类型。generate 函数有三个函数参数:一个是打开的游标(下面示例中的 opener 函数),另一个是做实际的产生集合的工作(generator 函数),还有一个是关闭游标(closer 函数)。那么,这个集合就可以被当作任何可枚举集合看待,但是,在后台,定义的这些函数会被调用,去打开数据源,并从中读取元素。下面的示例演示的函数从文件中读取以逗号分隔的词语列表:

 

open System

open System.Text

open System.IO

 

// test.txt: the,cat,sat,on,the,mat

let opener() = File.OpenText("test.txt")

 

let generator (stream : StreamReader) =

  letendStream = ref false

  letrec generatorInner chars =

    matchstream.Read() with

    |-1 ->

      endStream := true

      chars

    |x ->

     match Convert.ToChar(x) with

      | ‘,‘ -> chars

      | c -> generatorInner (c :: chars)

  letchars = generatorInner []

  ifList.length chars = 0 && !endStream then

    None

  else

    Some(newstring(List.toArray (List.rev chars)))

 

let closer (stream : StreamReader) =

  stream.Dispose()

 

let wordList =

  Seq.generate

    opener

    generator

    closer

 

wordList |> Seq.iter (fun s ->printfn "%s" s)

 

代码的运行结果如下:

 

the

cat

sat

on

the

mat

 

 

cast 函数

 

.NET 框架的 BCL 中包含两个版本的可枚举(IEnumerable)接口,一个定义在 System.Collections.Generic,另一个老一点的定义在 System.Collections。到此炎止,所有设计的示例都是用来处理来自 System.Collections.Generic 的新版本。然而,有时也可能需要处理非泛型集合,这样,F# 的IEnumerable 模块也提供了一个函数,用来处理从非泛型集合到泛型的转换。

在使用这个函数之前,我强烈建议你看一下,是否可以使用在第三、四章中讨论的列表推导(list comprehension)语法,这是因为列表推导可以许多非类型化集合的类型,通常是通过看 Item 索引属性的类型,因此,很少需要类型注解(type annotations),通常使编程更容易。

出于某种原因,如果宁可不使用列表推导语法,而使用 cast 函数把非泛型集合转换成体泛型也是可以的,下面的示例就是:

 

open System.Collections

open System.Collections.Generic

 

let floatArrayList =

  lettemp = new ArrayList()

  temp.AddRange([|1.0; 2.0; 3.0 |])

  temp

 

let (typedFloatSeq: seq<float>) =Seq.cast floatArrayList

 

使用 cast 函数就必须使用类型注解,告诉编译器我们产生的列表是什么类型。这里我们的列表是浮点型,因此,使用类型注解seq<float>,告诉编译器这是一个包含浮点数的可枚举集合。

第七章 F# 库(三),码迷,mamicode.com

第七章 F# 库(三)

标签:使用   os   文件   数据   io   for   

原文地址:http://blog.csdn.net/hadstj/article/details/24726375

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!