fortran 实现C++的vector.push_back() 或Python的List.append()
fortran90, 数组需要提前分配好大小,
但是如果事先不知道一个数组要分配多少内存,
遇到一个新元素加入到数组中,再遇到一个新元素又加入到数组中,
怎样实现呢?
我们知道C++标准库中的vector是动态数组,底层是数组,
但是却可以变戏法一样把新元素添加到数组尾部,比如 vec.push_back(x)
, 可以把x
添加到vec
的尾部,
这是怎么实现的呢?
实际上可以通过分配新数组,拷贝并且释放旧数组来实现,比如,
- 分配一个中间媒介,旧数组拷贝给媒介数组
- 释放旧数组,重新分配一个更大的数组
- 媒介数组拷贝给新数组
- 新元素添加到新数组的尾部
以上操作可以实现类似C++的vector.push_back() 或Python的List.append()的功能,但是要注意:
- 减少拷贝次数:如果每次添加新元素都涉及数组之间的拷贝,那么拷贝次数非常多,时间复杂度很高。为了降低时间复杂度,可以使用C++的vector的策略,即实际分配一个内存容量更大的空间,直到元素个数超过数组容量之后,再去分配新数组以及拷贝一番 (需要扩容时,可以让新数组容量为当前size的两倍)
- fortran中allocatable数组没法直接代入子程序或函数中allocate,需要用module 中的contain的方式
- 我目前想出的fortran中动态添加元素的最优雅的解决方案:在fortran中写一个vector类,类似面向对象,把添加元素等操作放入类方法中。但是fortran的自定义类Type只能包含类属性,不能包含类方法。有没有更接近类的做法呢?有!把类Type封装到Module中,Module再 contains 一下类相关的子程序或函数,这样类属性和类方法都有了!
实现上述思想的fortran代码如下所示(其中的append()
类似于C++中的vector.push_back() 或者Python中的 list.append()):
module vectorType
!!! basic idea: a smart array that can dynamically add a number to its back
!!! similar to vector<int> in C++
!!! similar to List in Python
!!!
!!! author: mohanxuan, https://blog.csdn.net/weixin_43414513
!!! license: Apache 2.0
type vector
integer :: size ! effective size of the array of this vector
integer :: capacity ! the storage memory capacity (>= size) of the array of this vector
integer, allocatable :: array(:)
end type vector
contains ! =========================================
subroutine vecInit(vec, size_)
!!! constructor or initialize function for Type vector
implicit none
type(vector) vec
integer :: size_
vec%size = size_
vec%capacity = max(size_, 1)
allocate(vec%array(vec%capacity))
end subroutine vecInit
subroutine vecDele(vec)
!!! destructor function for Type vector
implicit none
type(vector) vec
vec%size = 0
vec%capacity = 0
deallocate(vec%array)
end subroutine vecDele
subroutine append(vec, num)
!!! append a number to the back of this array
!!! similar to the function push_back() in C++ vector
!!! similar to the function append() in Python list
!!! author: mohanxuan, https://blog.csdn.net/weixin_43414513
implicit none
type(vector) vec, tmp
integer :: num
if (vec%size + 1 <= vec%capacity) then
vec%array(vec%size + 1) = num
vec%size = vec%size + 1
else
call vecInit(TMP, vec%size)
TMP%array = vec%array
call vecDele(vec)
call vecInit(vec, TMP%size * 2) ! twice the size of original vec
vec%size = TMP%size
vec%array(1:vec%size) = TMP%array(1:vec%size)
vec%array(vec%size + 1) = num
vec%size = vec%size + 1
call vecDele(TMP)
endif
end subroutine append
subroutine extend(v1, v2)
!!! merge vector v2 to vector v1
!!! similar to 'v1.extend(v2)' in Python
implicit none
type(vector) v1, v2, tmp
if (v1%size + v2%size <= v1%capacity) then
v1%array(v1%size + 1 : v1%size + v2%size)
& = v2%array(1:v2%size)
v1%size = v1%size + v2%size
else
call vecInit(TMP, v1%size)
TMP%array(1:v1%size) = v1%array(1:v1%size)
call vecDele(v1)
call vecInit(v1, TMP%size + v2%size)
v1%array(1:TMP%size) = TMP%array(1:TMP%size)
v1%array(TMP%size + 1 : TMP%size + v2%size)
& = v2%array(1:v2%size)
call vecDele(TMP)
endif
end subroutine extend
subroutine vecPrint(vec)
implicit none
type(vector) vec
print*, vec%array(1:vec%size)
end subroutine vecPrint
end module vectorType
program main 1
use vectorType
implicit none
integer :: i
type(vector) x
call vecInit(x, 0) ! initial x to be a 0 zise vector
do i = 1, 100
call append(x, i) ! push i to x's back
enddo
print*, "array of x ="
call vecPrint(x)
print*, "x%size =", x%size
print*, "x%capacity =", x%capacity
end