7.4.1 用映射操作进行更新

尽管这个操作类似于映射,但是,真的实现,还需要作出重要的设计选择。分栏部分可能递归地包含多个部分,所以,文档是一种树形结构,我们需要决定以哪种顺序处理节点:

1、从根部开始,对所有嵌套在其中的部分,递归地调用映射操作。

2、从叶子开始,首先处理嵌套最深的部分,然后,返回到包含它们的部分。

在处理列表时,顺序无关紧要,但是,对于树形结构,却是相当重要的。想象一下,我们有一个文档,包含一个垂直分栏部分,和两个带有文本的水平分栏部分,如果我们运行映射函数,合并只含有文本的分栏部分,会发生什么事?

■ 如果从根开始,我们会对垂直的分栏部分调用合并函数。函数不可能合并列,因为它们还都包含其他的分栏部分;那么,它会递归地处理两个水平分栏部分。这些部分只包含文本,所以,函数会将它们合并。结果,我们会得到有两行文本的文档。

■ 如果开始从叶子,首先,对原始文本部分调用函数,应该保持不变。下一步,它将处理两个水平分栏部分,它们都只包含文本,所以,函数对其进行合并。最后,对垂直分栏部分的根调用函数,现在,其中只包含两个文本部件。结果,我们会得到只有一行文本的部分。

第二种顺序更可取,因为我们合并所有部分,使其只包含文本。对于这个实现,就是首先递归处理所有给定部分的子部分,之后,运行由用户指定的处理函数。

现在,我们知道了函数应该如何运行,下面要看看用户如何调用函数。当考虑使用高阶函数时,首要的是类型。下面是我们要实现的函数签名,与处理列表函数的对比(DocumentPart 简称为 DP):

List.map : (‘a -> ‘b) ->list<‘a> -> list<‘b>

mapDocument : (DP -> DP) -> DP ->DP

两个函数之间有一个重要的不同。在列表中,作为第一个参数的函数,只处理列表中的一个元素,第二个参数和结果都是元素的列表;在文档中,我们不区分一个部分与整个文档,作为第一个参数的处理函数,通常只处理它获取的输入的根部分,不会递归处理嵌套的部分。MapDocument 函数用递归遍历文档树的代码,补充了(complements)只处理一个部分的函数,这样,我们只用转换一个文档部分的函数,就能递归处理整个文档了,看清单 7.15。

清单 7.15 文档的映射操作 (F#)

let rec mapDocument f docPart =

let processed =

match docPart with   <-- 递归处理嵌套部分

| TitledPart(tx,content) –>

TitledPart(tx, mapDocument f content)  [1]

| SplitPart(orientation,parts) –>

letmappedParts = parts |> List.map (mapDocument f)   [2]

SplitPart(orientation, mappedParts)

| _ –> docPart

f(processed)    <-- 运行指定函数

回顾一下,函数首先递归处理当前部分的每个子部分,之后,运行指定的处理函数。递归处理相当简单,我们使用模式匹配处理两种包含子部分的部分。对于标题部分[1],我们处理正文,并返回包含原始标题的新的 TitledPart;对于分栏部分,使用 List.map 获得每个列或行的新版本,并使用这个结果去构造新的 SplitPart。我们递归处理了部件之后,就把它作为参数值给指定的处理函数,并返回结果。

现在,我们有高阶函数,我们就来看一下,如何用它来合并文本元素,这对于适应文档的布局是有帮助的:在宽屏幕上,我们想要显示更多的列,但在窄屏幕上,一列更具多可读性。清单 7.16 显示了如何把只包含文本的分栏部分收缩成一个部分。

清单 7.16 收缩包含文本的分栏部分 (F#)

let isText(part) =                             | 检查是否为文本部分

match part with | TextPart(_) ->true | _ –> false  |

let shrinkDocument part =

match part with

| SplitPart(_, parts) whenList.forall isText parts –>   [1]

let res =

List.fold(fun st (TextPart(tx)) ->   [2]

{ Text = st.Text + " " + tx.Text   <-- 连接文本,返回字体

Font = tx.Font } )

{ Text = ""; Font = null } parts   <-- 从空字符串、空字体开始

TextPart(res)

| part –> part    <-- 忽略其他情况

let doc =loadPart(XDocument.Load(@"C:\...\document.xml").Root)

let shrinkedDoc = doc |> mapDocumentshrinkDocument

在这个处理函数中,我们需要检查给定的部分是否是只包含文本部分的 SplitPart。第一个条件可以通过使用模式匹配,直接进行检查,而第二个条件,是在模式的 when 子句中指定的。我们写了一个工具函数 isText,来检查是否是 TextPart 部分,然后,在 List.forall 使用,检查所有部分是否都满足条件[1]。

接下来,我们使用 fold 把列表中的所有部分聚合成一个部分。我们已经知道,每个子部分都是TextPart,所以,写 lambda 函数聚合结果时[2],可以直接作为模式来使用。编译器不能验证其正确性,所以,会发出警告。当发现警告时,应该始终保持谨慎,但是在这里,我们可以放心地忽略它。在大型项目中,需要消除所有编译器警告,可能会用 match 结构重写代码,在不该到达的分支中调用 failwith 函数。聚合使用 TextContent 类型作为状态,初始值设置为没有文本内容的字符串,也不设置字体。在每一步,我们把当前部分现有的字符串连值连接起来,并使用当前的字体。我们没有用复杂的方式处理字体,所以,最终得到的字体是最后部分所使用的。

可以图在 7.5 中看到操作的最终结果。

图 7.5 原始和更新的文档;在新文档中,只包含纯文本的分栏部分,被合并成一个文本部分。

我们前面提到过,这个类似映射的操作是我们为处理文档提供的几个有用操作中的一个,在下一节,我们将看到另一个操作,把文档聚合成一个值。

时间: 2024-11-06 19:03:24

7.4.1 用映射操作进行更新的相关文章

大数据技术之_20_Elasticsearch学习_01_概述 + 快速入门 + Java API 操作 + 创建、删除索引 + 新建、搜索、更新删除文档 + 条件查询 + 映射操作

一 概述1.1 什么是搜索?1.2 如果用数据库做搜索会怎么样?1.3 什么是全文检索和 Lucene?1.4 什么是 Elasticsearch?1.5 Elasticsearch 的适用场景1.6 Elasticsearch 的特点1.7 Elasticsearch 的核心概念1.7.1 近实时1.7.2 Cluster(集群)1.7.3 Node(节点)1.7.4 Index(索引 --> 数据库)1.7.5 Type(类型 --> 表)1.7.6 Document(文档 -->

PostgreSQL连接python,postgresql在python 连接,创建表,创建表内容,插入操作,选择操作,更新操作,删除操作。

安装 PostgreSQL可以用Python psycopg2模块集成. sycopg2是Python编程语言的PostgreSQL数据库的适配器. 其程序代码少,速度快,稳定.不需要单独安装这个模块,因为它默认情况下被运往随着Python版本在2.5.x一起的.如果不把它安装在机器上,然后可以使用yum命令安装它,如下所示: $yum install python-psycopg2 要使用psycopg2的模块,首先必须创建一个Connection对象,它表示数据库然后再可以选择创建游标对象,

快学Scala 第五课 (构造映射,获取映射值,更新映射值,迭代映射,与Java互操作)

构造映射: val score = Map[String, Int]() val score1 = HashMap[String, Int]() val value1 = Map[String, Int]("aa" -> 1, "bb" -> 2) val value2 = Map[String, Int](("aa", 1), ("bb", 2)) 获取映射值: println(if(value2.contain

ioremap 函数映射操作已知的物理地址(寄存器、端口、IO)

NAME ioremap - map bus memory into CPU space SYNOPSIS void __iomem * ioremap (unsigned long offset, unsigned long size); ARGUMENTS offset bus address of the memory size size of the resource to map DESCRIPTION ioremap performs a platform specific sequ

java操作mongodb——更新数据

Java中可以通过updateOne,updateMany,replaceOne方法进行集合的文档更新.但是 _id 是不能更新的 updateOne只会更新一条数据,即使通过Filters.lt("age", 20)过滤出多条数据,也只会取出一条进行更新 更新操作符 名称 描述 $inc 增加一个指定值 $mul 乘以一个指定值 $rename  重命名 $setOnInsert  更新操作对现有的文档没有影响,而是新插入了一个文档,则在这新插入的文档中加上指定字段 $set 修改值

iOS特殊操作--持续更新

1. ios8,9开始支持icloud,影响相册相关操作: 3. 应用切到后台后,新消息提醒 是否在应用图标右上角作数字标记,有开关控制:   设置->通知->应用名称->"应用程序图标标记"选中此项,表示有数字标记,不选则没有: 4. 如何在数据网络下查看手机IP setp1.打开手机数据网络 setp2.打开手机浏览器--百度,在百度搜索框中 输入"IP地址" setp3. 查看搜索结果: 5. 连续输入两个空格,会成为一个句号 如果是中文输入

解决Hibernate4执行update操作,不更新数据的问题

后台封装java对象,使用hibernate4再带的update,执行不更新数据,不报错. 下面贴出解决方法: 失败的方法 hibernate自带update代码:(失效) Session session = this.getSessionFactory().getCurrentSession(); ession.update(obj); 成功的方法 使用hql语句执行: public void updateUser(String name, String tel,            Str

MySQL 操作(更新中……)

01 创建数据库 CREATE DATABASE `database_name`; 02 查看数据库 SHOW DATABASES; 03 选择数据库 USE `database_name`; 04 删除数据库 DROP DATABASE `database_name`; 05 表操作 0501 创建数据表 CREATE  TABLE `table_name` (`column01_name` `type_name` [...] , ...); *[NOT NULL | NULL][DEFAUL

简单的终端操作--for 更新上传文件

1:cd--进入项目目录(cd projiect/) 2:ls--查看项目目录 3:git branch--查看分支 4:git status--查看状态 5:git checkout staging--切换分支 6:mysql.server start--启动数据库 7:git pull origin staging--更新 8:rake db:migrate--数据库迁移 9:bundle--安装项目依赖包 10:rails s--启动项目 11:git flow feature start