避免使用for循环
在程序设计思想中,循环是一个很有力的工具。在循环中,计算机很轻松地重复执行相同的操作。循环是汇编之上的编程中最重要的概念之一。Matlab的循环有两个语言构造,一个是for循环,另一个是while循环。在Matlab中,for循环是最常用的循环结构。
然而,for循环在Matlab中是非常慢的。这是因为Matlab是一种解释性语言,而不是一种编译性语言。比如,立志于在科学计算领域开辟一“新”道路的Julia语言,其宣传的最重要特点之一就是for循环的速度非常快。
作为经常需要处理大量数据的科学计算工具,Matlab的for循环速度慢是一个很大的问题。因此,我们应该尽量避免使用for循环。
这里有一个说法可以特别地在这里提到,就是所谓的过早优化。过早优化是一种不好的编程习惯。在程序设计的初期,我们应该首先考虑代码的可读性和可维护性。然后,我们应该考虑代码的性能。然而在Matlab中,避免使用for循环并不意味着我们应该在一开始就考虑性能问题。通过采用下面我们提到的方式,编写的Matlab代码的可读性和可维护性也是非常强的。
1. For循环的基本知识
1.1 语法
在Matlab中,for循环的构造如下:
for i = 1:n
% do something
end
这个构造同样也可以写成单行的形式:
for i = 1:n, statements, end
如果我们要对一个向量v
进行操作,可以使用如下的for循环:
for i = 1:length(v)
% do something
end
上面,1:n
和1:length(v)
是for循环的索引。实际上,在Matlab中,:
操作符会直接产生一个数组(Matlab最基本的数据结构),并且这个操作符还能够设定步长。比如,1:2:10
会产生一个从1到10的数组,步长为2。
for语言构造采用 =
来迭代一个数组。所以,前一个例子里面,可以直接写成:
for vi = v
% do something
end
1.2 break和continue
在For循环中,要避免改变索引变量的值来试图改变循环过程,for循环会直接覆盖对这个值的改变。这一点跟很多别的程序设计语言并不相同。
for i = 1:10
i = 5; % 这个操作是无效的
disp(i);
end
要中途退出一个循环,可以使用关键词break。要跳过当前循环的剩余部分,可以使用关键词continue。
运行一下上面的代码,会打印10个5。
1.3 循环二维数组
另外,我们都知道Matalb的2维数组(矩阵)是列先的,所以在for循环中,对矩阵的操作,应该是按列进行的。比如:
A = [1 2 3; 4 5 6; 7 8 9];
for Ai = A
disp(Ai);
end
上面的代码会打印出矩阵A的每一列。
Q: 如果上面的A是一个列向量,会怎么样?循环几次?
A: 循环一次。因为列向量是一个列,所以循环一次。
Q: 如果A是一个行向量呢?
A: 行向量有多少个元素,就循环多少次。
这里还挺有意思的,Matlab的for循环是按列进行的,所以对于一个矩阵,我们可以使用for循环来对每一列进行操作。这是一个很有用的特性。而一维数组,列向量和行向量,是一个维数为1的矩阵,所以对于这些数据,for循环的行为是具有一致性的。
2. 避免使用for循环
2.1 算法矩阵化
Matlab是一种矩阵化的语言。这意味着,Matlab的很多操作都是对整个矩阵进行的。与C、Java等语言不同,Matlab的很多操作都是对整个矩阵进行的,Matlab提供了大量针对矩阵进行计算的操作符和操作函数。这些操作符和函数都是高度优化过的,所以在Matlab中,我们应该尽量使用矩阵化的操作。这一点应该是Matlab的使用者在设计算法之初就应该考虑的。
把算法的步骤推导为矩阵的形式,可以为Matlab来实现提供很大的帮助。比如,我们要计算一个向量的平方和,可以使用for循环:
v = [1 2 3 4 5];
sumv = 0;
for vi = v
sumv = sumv + vi^2;
end
这里需要注意,如果v是列向量,那么只会循环一次,得到奇怪的结果。(可以自己试试,并思考为什么)
我们可以使用矩阵化的操作:
v = [1 2 3 4 5];
sumv = v * v';
这里用到了行矩阵x列矩阵的乘法。
A m × n × B n × p = C m × p A 1 × n × B n × 1 = c \begin{split} & A_{m \times n} \times B_{n \times p} = C_{m \times p} \\ & A_{1 \times n} \times B_{n \times 1} = c \end{split} Am×n×Bn×p=Cm×pA1×n×Bn×1=c
2.2 计算向量化
Matlab还提供了另外一个非常强大的工具,那就是向量化。向量化就是把操作符应用到一个矩阵的每一个元素上。比如,计算向量平方和,可以用向量化的方法写成:
v = [1 2 3 4 5];
sumv = sum(v.^2);
这里用到一个向量化操作的函数sum
和一个向量化算符.^
。前者是对矩阵的每一个元素求和,后者是对矩阵的每一个元素进行平方。
Matlab提供了大量的向量化操作符和函数,这些函数和操作符都是高度优化过的
3. 数组的逻辑索引与arrayfun函数
Matlab还提供了一些数组的逻辑操作,这些操作也是高度优化过的。比如,我们要找出一个向量中所有大于5的元素:
v = [1 2 3 4 5 6 7 8 9];
vgt5 = v(v > 5);
这里用到了一个逻辑操作符>
,这个操作符会产生一个逻辑数组,然后我们可以用这个逻辑数组来索引原数组。
通过逻辑数组索引,还能够实现很多高级的操作。比如,我们要把一个向量中所有的偶数都变成0:
v = [1 2 3 4 5 6 7 8 9];
v(v % 2 == 0) = 0;
这里用到了一个逻辑操作符==
,这个操作符会产生一个逻辑数组,然后我们可以用这个逻辑数组来索引原数组。
可以通过比较复杂的函数来产生逻辑数组,然后用逻辑数组来索引原数组。这样,我们就可以把很多循环操作转化为矩阵化的操作。特别是,用逻辑判断的函数加上arrayfun函数,可以实现很多循环操作。
v = [1 2 3 4 5 6 7 8 9];
predicate = @(x) x > 5 && x < 8;
index = arrayfun(predicate, v);
v(index) = 0;
通过这样的办法,可以写出非常易读的代码,并且也可能会更快。
4. 总结
在Matlab中,for循环是非常慢的。我们应该尽量避免使用for循环。Matlab提供了大量的矩阵化操作符和函数,向量化操作符和函数,逻辑操作符和函数,这些操作符和函数都是高度优化过的。我们应该尽量使用这些操作符和函数,来代替for循环。
- 使用矩阵化的操作,从设计和推导算法的时候就采用矩阵来表示;
- 使用向量化的操作,对矩阵的每一个元素进行操作;
- 使用逻辑索引操作矩阵的部分元素;
- 可以考虑使用arrayfun函数来代替for循环。