6.2.1 使用函数处理元组

本文介绍了如何使用F#中的mapFirst和mapSecond函数处理元组,简化对元组元素的操作过程。通过具体示例展示了这些函数的应用场景及其实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

6.2.1 使用函数处理元组

 

在第三章,我们用元组来表示城市和人口。当我们想增加人口时,写的代码像这样:

 

let (name, population) = oldPrague

let newPrague = (name, population + 13195)

 

这很清晰,但有点罗唆。第一行分解元组,第二行对第二个元素进行计算,然后,生成新的元组。理论上,我们可能说,对分解的第二个元素执行计算,然后重建元组。首先,我们看一下能够用F# 和 C# 写的代码,然后,实现完成所有工作的方法。这就是我们的目标:

 

let newPrague = oldPrague |> mapSecond((+) 13195)   //F#

var newPrague = oldPrague.MapSecond(n =>n + 13195); //C#

 

这个版本移除了重建元组所有额外的代码,描述了核心思想,即,我们要把元组中第二个元素中加上某个数。对第二个元素上执行计算的思想,是通过使用 F# 中的 mapSecond 函数表达的。清单 6.4 显示了这个函数和类似的 mapFirst 函数的实现。

 

清单 6.4 处理元组的高阶函数 (F# Interactive)

> let mapFirst f (a, b) = (f(a), b)    [1]

let mapSecond f (a, b) = (a, f(b))   [2]

;;

val mapFirst : ('a -> 'b) -> 'a * 'c-> 'b * 'c    | [3]

val mapSecond : ('a -> 'b) -> 'c * 'a-> 'c * 'b  |

 

清单 6.4 实现两个函数:一个是对元组的第一个元素执行操作[1],另一个是作用于第二个元素[2]。这两个函数的实现都很简单:使用在参数列表中的模式匹配,分解给定的元组,然后,对其中的一个元素调用函数;最后,返回的新元组,是由函数调用的结果与其他元素的原始值组成。虽然,函数体看上去不难,但是,推导出的类型签名[3]看起来却相当复杂,尤其是当你第一次看到时。很快我们会再回来。

 

映射操作

 

在刚才讨论的函数名中,我们使用了术语映射(map)。映射(也称为投影)是一个常见操作,我们将会看到,它可以用于许多数据类型。一般情况下,它取函数作为参数值,把这个函数应用到一个、有时可能是多个值上,这些值是保存在数据类型中的;结果被包在具有相同结构的数据类型中,并且,也作为映射操作的返回结果。结构没有改变,是因为我们所描述的操作不是针对组合值的,只而是描述了[组合]值的各个组件应该做什么,不需要知道其他内容,因此,映射必然保持原来的结构。现在,这个描述可能不完全清楚,因为,这很大程度上取决于你获得的直观感受,在本章后面有更类似的操作。

 

这些函数的签名,对于了解它们在做什么是有用的。图 6.1 分解了 mapFirst 的签名,并显示每个部分的意思。

图 6.1 mapFirst 函数的第一个参数为函数,第二个参数为元组,把这个函数应用到元组中的第一个元素。

 

我们来看看签名能提供函数的哪些信息。首先,函数是泛型的,有三个类型参数,由 F# 编译器自动命名;它取函数作为第一个参数,包含类型为 'a 和 ' c值的元组,作为第二参数值。从签名可知,返回元组由类型为 'b 和 'c 的值组成。

因为这个函数没有任何安全的方式,来处理 ' c 类型的值,直接就复制了第二个元素。接下来的问题是,怎样才能在结果中得到 ' b 类型的值。已有 ' a 类型的值(元组中的第一个元素),和能够把 'a 类型值转换成 ' b 类型值的函数,因此,最明显的答案是,mapFirst 把函数应用到元组的第一个元素。

我们已经实现了 mapFirst 和 mapSecond 函数,现在,就可以使用了。清单 6.5 显示的F# Interactive 会话演示了如何用来处理元组。

 

清单 6.5 处理元组 (F# Interactive)

> let oldPrague = ("Prague",1188000);;

val prague : string * int

 

> mapSecond (fun n -> n + 13195)oldPrague;;  [1]

val it : string * int =("Prague", 1201195)

 

> oldPrague |> mapSecond ((+)13195);;    [2]

val it : string * int =("Prague", 1201195)

 

示例演示了两种方法,使用 mapSecond 函数,写的是同样的操作。第一种方法是直接调用函数[1],把 lambda 函数作为第一个参数值,原始元组作为第二个参数。从 F# Interactive 输出的结果元组,可以看到,函数应用到了元组的第二个元素,正是我们想要的。

在第二个版本中[2],我们使用了两种强大的技术。我们使用了散函数应用(在前一章中介绍的)来创建函数,为第二个元素加上 13195。我们没有显式写 lambda 函数,而是直接写成 (+) 13195。如果在括号中使用运算符,它的行为就像普通函数,因此,(+) 10 5 就能实现两个数的加法。如果使用散函数应用,只给它提供一个参数值,我们就获得了 int –> int 类型的函数,把这个数与任意给定的参数值相加,而这个函数的类型与 mapSecond 函数预期的一致。在这里,类型 'a -> 'b 中的'a 和'b 都替换为 int。

由于有管道,我们可以先写原始元组,然后再写应用函数。这样,代码更具可读性,首先,描述我们要操作的对象,然后,再描述做什么,就像在 C# 中,操作通常的形式是target.MethodToCall()。使用管道的另一个原因是,mapSecond的第一个参数是函数,第二个参数是元组,而没有其他的方式。

这一节,我们首先讨论 F#,因为推导高阶函数的类型签名和使用管道,在 F# 中非常自然;当然,在 C# 中,也可以使用同样的概念,我们将在下一节实现。

### 图像处理中的哈夫曼编码 #### 6.2.1 哈夫曼编码在图像压缩中的具体实现方法 哈夫曼编码作为一种高效的无损压缩算法,在图像处理领域广泛应用。对于彩色图像,通常会先将其转换为适合压缩的颜色空间模型(如YCbCr),再分别对各个通道的数据进行哈夫曼编码。 为了有效地应用于图像数据,哈夫曼编码需经过以下几个重要阶段: - **预处理**:将原始图像分割成多个子块,并针对每个颜色分量执行离散余弦变换(DCT),从而减少相邻像素间的冗余度[^4]。 - **量化**:对DCT系数应用量化表,进一步降低数值范围内的细节差异,这一步骤虽然引入了一定量的信息损失,但对于视觉效果影响较小。 - **构建哈夫曼树**:依据量化后的DCT系数频次分布情况建立对应的哈夫曼树,确保高频出现的符号对应较短位串表示;此过程中涉及到了字符频率统计环节[^1]。 - **生成编码映射表**:根据所建好的哈夫曼树创建唯一的前缀码字典,用于指导后续的实际编解码工作。 - **编码过程**:利用上述准备好的映射关系替换原图中各位置上的亮度及色差值,形成紧凑表达形式以便于传输或长期保存[^3]。 - **文件格式设计**:考虑到后期恢复需求,还需精心规划额外元数据记录方式——比如采用特定标志符标记不同部分边界、保留重建所需参数等措施,使得接收端能准确解析接收到的内容并重构初始画面质量。 ```python import numpy as np from collections import Counter, deque class Node: def __init__(self, char=None, freq=0, left=None, right=None): self.char = char self.freq = freq self.left = left self.right = right def build_huffman_tree(frequencies): nodes = [Node(char=c, freq=f) for c,f in frequencies.items()] while len(nodes)>1: nodes.sort(key=lambda node:node.freq) new_node = Node( left=nodes.pop(0), right=nodes.pop(0), freq=(nodes[-1].freq + nodes[-2].freq)) nodes.append(new_node) return nodes[0] # 构造测试用例模拟图像数据流 image_data_stream = "A"*5+"B"*9+"C"*12+"D"*13+"E"*16+"F"*45 frequency_dict = dict(Counter(image_data_stream)) root_of_huffman_tree = build_huffman_tree(frequency_dict) def generate_codes(node, prefix="", codebook={}): if not (node.left or node.right): codebook[node.char]=prefix else: generate_codes(node.left , prefix+'0',codebook) generate_codes(node.right, prefix+'1',codebook) encoded_symbols = {} generate_codes(root_of_huffman_tree, "", encoded_symbols ) print("Generated Huffman Codes:", encoded_symbols ) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值