编程中的正交性思考

本文探讨了如何通过位运算优化计算多块磁盘文件数量的函数,引入了DISK_ALL参数和位运算的正交性概念。当磁盘数量增加时,利用二进制编码和位运算,可以实现一次性计算多个磁盘的文件总数。同时,还介绍了如何通过编码函数恢复不同数值之间的正交性,以应对特定的数值冲突问题。这种方法在软件设计和测试中也有广泛应用,如接口测试的正交性分析。

背景描述

我有2块磁盘,并要求设计一个函数 cal() 用于计算一块磁盘中文件的数量

我们很容易写出

#define DISK1 1
#define DISK2 2
int cal(int disk)
{
	int size = 0;
	if (disk == DISK1) {
		size = ...; // 计算磁盘1大小
		return size;
	}
	if (disk == DISK2) {
		size = ...; // 计算磁盘2大小
		return size;
	}
}

这样用户如果需要统计两块磁盘总文件数量,就可以这样写

int size1 = cal(DISK1); // 计算 磁盘1的文件数量
int size2 = cal(DISK2); // 计算 磁盘2的文件数量
int size_sum = size1 + size2;

简化一点就是

int size_sum = cal(DISK1) + cal(DISK2);

如果有三块磁盘呢?

int size_sum = cal(DISK1) + cal(DISK2) + cal(DISK3);

四块呢???

多合一

有什么办法能将多次调用改成一次即可呢?答案是提供一个DISK_ALL参数

#define DISK1 1
#define DISK2 2
#define DISK3 3
#define DISK_ALL 4
int cal(int disk)
{
	int size = 0;
	if (disk == DISK1 || disk == DISK_ALL) {
		size += ...; // 计算磁盘1大小
	}
	if (disk == DISK2 || disk == DISK_ALL) {
		size += ...; // 计算磁盘2大小
	}
	if (disk == DISK3 || disk == DISK_ALL) {
		size += ...; // 计算磁盘3大小
	}
	return size;
}

这样,用户如果想统计所有磁盘的文件数量,就可以写出

int size_sum = cal(DISK_ALL);

如何选择性的统计呢?

加入了DISK_ALL参数后,我们可以很方便的统计所有磁盘文件数量的和了

一个新的问题是,我们如何只统计其中几块磁盘的文件数量呢?
例如,我想求1盘、3盘的数量,那么我只能写成

int size = cal(DISK1) + cal(DISK3);

代码如何优化才能让调用再变成一次呢?
答案是引入bit map的思想

#define DISK1 1
#define DISK2 2
#define DISK3 4
#define DISK_ALL 7
int cal(int disk)
{
	int size = 0;
	if (disk | DISK1) {
		size += ...; // 计算磁盘1大小
	}
	if (disk | DISK2) {
		size += ...; // 计算磁盘2大小
	}
	if (disk | DISK3) {
		size += ...; // 计算磁盘3大小
	}
	return size;
}

这时用户就可以写成

int size = cal(DISK1 | DISK3); // 统计1、3号磁盘文件数量
int size = cal(DISK1 | DISK2 | DISK3) // 统计所有磁盘文件数量
int size = cal(DISK_ALL) // 统计所有磁盘文件数量的等价写法

这里利用了二进制位运算的性质
我们预设了

DISK1 = 1 = 001
DISK2 = 2 = 010
DISK3 = 4 = 100
DISK_ALL = 7 = 111

当我们传入disk = DISK1 | DISK3时,我们实际传入了disk = 001 | 100 == 101
所以代码中三个if判断的条件结果分别是:

disk | DISK1 = 101 | 001 = 001 = ‘true’
disk | DISK2 = 101 | 010 = 000 = ‘false’
disk | DISK3 = 101 | 100 = 100 = ‘true’

就会走入计算1、3盘文件数量的分支

But Why

为什么这样可行呢

这其实利用了正交性我们设定的几个不同的 DISKx 的值,在二进制表示上,是彼此正交的

所以可以通过简单的或者操作来对DISKx进行组合

所以可以返回看下上面的代码。将 | 操作换成 +操作一样等价

一个面试题

如果我非要定义

#define DISK1 1
#define DISK2 2
#define DISK3 3

能否仍然满足之前的需求呢?

显然。123 在二进制表达层级上,已经不是彼此正交的了

所以cal(DISK1 | DISK2) 并不等价于cal(DISK1) + cal(DISK2)

如何恢复他们的正交性呢?

编码

123 不具备二进制表示的正交性
但是2<sup>1</sup>2<sup>2</sup>2<sup>3</sup>,在二进制表示层面上是正交的

定义编码函数f(x) = 2<sup>x</sup>

f(DISK1 + DISK2) == f(DISK1) + f(DISK2)

定义可以改写成

#define DISK1 f(1)
#define DISK2 f(2)
#define DISK3 f(3)
#define DISK_ALL f(4)

其实这与之前是等价的,只不过换了一种思考角度

正交性在其他领域的作用

测试

在测试某一对象(以接口为例),我们会对每一个入参进行等价类划分,对划分出来的测试点,进行正价分析。通常被用于构造测试输入数据。

例如一个接口f(int a, int b),接受两个参数ab,设计说明a的合法取值范围为0~100,b的合法取值范围为0~10
则可以得到以下正交表

ab的取值-10199100101
-1f(-1, -1)f(0,-1)f(1,-1)f(99,-1)f(100,-1)f(101,-1)
0f(-1,0)
1f(-1,1)
9f(-1,9)
10f(-1,10)
11f(-1,11)

则可以完成对接口f的基本覆盖

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值