6.2.1 使用函数处理元组

6.2.1 使用函数处理元组

 

    在第 3 章,我们用元组来表示城市和人口。当我们想要增加人口时,不得不写点东西,像这样:

 

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 的实现。

 

Listing 6.4 Higher-order functions for working with tuples (F# Interactive)

 

> let mapFirst f (a, b) = (f(a), b)
   let mapSecond f (a, b) = (a, f(b))
;;
val mapFirst : ('a -> 'b) -> 'a * 'c -> 'b * 'c
val mapSecond : ('a -> 'b) -> 'c * 'a -> 'c * 'b

 

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

 

映射操作

 

    我使用术语映射(map),我们刚才讨论的函数的名称。映射(也称为投影)是一项常见的操作,并正如你将要看到的,它可以与许多数据类型一起使用。一般情况下,它取一个函数作为参数值,并应用此函数一个,或有时多个值,存储在这个数据类型中。结果被包在具有相同结构的数据类型中,并返回作为这个映射操作的结果。结构并未改变,因为,我们所指定的操作不会告诉我们用这个组合值来做什么。它只指定用这个值的组件做什么,不知道其他,投影必须保留原始结构。现在,这个描述可能不完全清楚,因为,这很大程度上取决于你获得的直觉,在本章后面更类似的操作之后。

 

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

image

 

图 6.1 mapFirst 函数取一个函数作为第一个参数值,并将其应用到元组的第一个元素,元组作为第二个参数值传递。

 

    让我们看看这个签名能告诉我们有关这个函数的什么内容。首先,它是一个泛型函数,有三个类型参数,由 F# 编译器自动命名。它取一个函数作为第一个参数,元组包含类型为 'a 和 ' c 的值,作为第二参数值。这个签名告诉我们,返回的元组由类型为 'b 和 'c 的值组成。

    因为这个函数不具有任何安全的方式,来处理 ' c 类型的值,它可能是只复制第二个元素。接下来的问题,是我们怎样才能在结果中,获得 ' b 类型的值。我们有一个 ' a 类型的值(元组的第一个元素),和一个函数,它可以将一个 'a 类型的值转换成 ' b 类型的值,所以,最明显的解释是,mapFirst 适用这个函数到元组的第一个元素。

    现在,我们已经实现了 mapFirst 和 mapSecond 函数,让我们开始使用它们。清单 6.5 显示了F# Interactive 会话演示如何能用来处理元组。

 

Listing 6.5 Working with tuples (F# Interactive)

 

> let oldPrague = ("Prague", 1188000);;
val prague : string * int

> mapSecond (fun n -> n + 13195) oldPrague;;
val it : string * int = ("Prague", 1201195)

> oldPrague |> mapSecond ((+) 13195);;
val it : string * int = ("Prague", 1201195)

 

    该示例演示了两种方来法写相同的操作,使用 mapSecond 函数。第一种情况,我们直接调用这个函数,给它 lambda 函数作为第一个参数值,原始的元组作为第二个参数。如果你看看由 F# Interactive 打印的结果元组,可以看到这个函数应用到元组的第二个元素,正是我们想要的。

    在第二个版本中,我们使用两个强大的技术。我们使用了偏函数应用(在前一章中介绍的)来创建一个函数,为第二个元素加上  13195。不用显式写 lambda 函数,我们写了 (+) 13195。如果在括号中使用运算符,它的行为像普通函数,这意味着,我们可以通过写 (+) 10 5 来加两个数。如果我们使用偏函数应用,并只给它有一个参数值,我们就获得了一个 int –> int 类型的函数,加这个数到任意给定的参数值,类型与 mapSecond 函数预期的兼容。类型是 'a -> 'b,在这种情况下, int 替换为 'a 和' b。

    由于有了流,我们可以写原始的元组,然后是应用的这个函数。这使代码更具可读性,首先,描述我们要去操作什么;然后,我们打算用它做什么——就像在 C# 中,操作是通常的目标形式,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、付费专栏及课程。

余额充值