EDA365电子论坛网
标题:
#技术风云榜#MATLAB映射表数据结构
[打印本页]
作者:
ededewa
时间:
2020-11-30 15:52
标题:
#技术风云榜#MATLAB映射表数据结构
8 x" o1 D4 k6 |1 v3 G0 Z
目录:
f! p2 d0 w( i& n" Z
0 m b0 u# _7 P3 L% P
containers.Map简介
1 Q' Q# c; k( }3 O# N
数组,元胞和结构体的局限性
8 a6 C# H7 C* R1 V( q( E0 Z1 J
什么是containers.Map
" C* }! I! X$ G- H7 X9 g
containers.Map的属性和成员方法
( }, T0 ~. Y; @
containers.Map的特点
0 {* B$ i" H' y+ }, m+ |
containers.Map可以不断地扩张不需预分配
: F: I, T5 @; p1 r
containers.Map可以作为参数在函数内部直接修改
1 V# ?' W% }' F8 q4 p0 z
containers.Map可以增强程序的可读性
) f4 `- V% T: @8 o$ i3 `
containers.Map提供对数据的快速查找
4 K8 |% Z. r) }) U
containers.Map的使用实例
& m& E0 \# p5 P" h* J
用来盛放元素周期表
* q$ r3 H9 q) _: b& _2 S) m% B) `
用来实现快速地检索
6 A+ m0 `3 H/ N* E5 s" x3 p
MATLAB常用基本数据类型有:整型,浮点型,字符型,函数句柄,元胞数组和结构体数组。除了这些基本数据类型,MATLAB还有很多其它的数据类型不为人熟悉,这些数据类型在编程中也非常有用。MATLAB高级数据类型系列旨在向大家介绍它们:比如 containers.Map,tables,enumeration和time series等等,它们为什么有用,用来解决什么问题,并且怎样在科学工程计算使用。本篇首先介绍 containers.Map 数据类型。
( k3 B6 b! L4 {( `
containers.Map简介
2 o# U1 X' ^# |
MATLAB中最具代表性的高级数据类型是containers.Map,我们可以把它叫做映射表。它和函数映射有些类似,比如函数映射的定义是:
$ J/ `% \* N" [. O: c
F(x) = Y
5 C& G }7 `# {+ w4 y- H: g
针对每一个X,都有一个唯一的Y与之对应,反之不一定。如图Fig.1所示。和函数映射相似,映射表用来形容键(Key)和键值(Key Value)之间的一一对应关系。每个Key都是独一无二的,且只能对一个Key Value。如图Fig.2所示。
8 b8 d/ o U. k4 K
Fig.1 函数映射关系
9 q- s5 I9 n& C5 [8 N0 U
Fig.2 containers.Map类的映射示意图
: x: R/ ^, V1 {; v' W
数组,元胞和结构体的局限性
0 I; p+ Z* p% y/ Q" g. D Q; |/ `; a
开始我们先介绍一下数组,元胞数组和结构体的局限性,为什么有的时候这些基本的数据类型无法满足程序的要求,换句话说,我们为什么需要 containers.Map 数据结构。假设要用MATLAB来记录电话号码簿中数据,比如表Table.1所示:
2 a7 c5 h# {5 R! c" g+ M
Table.1 电话号码簿
' [$ h+ {% s/ Y! d+ \8 J
7 n4 F: d+ s S% q- Z
姓名 电话号码
0 ^7 G; c( _; b6 q2 U8 |7 x
Abby 5086470001
B' L# q4 r% e) ?2 l3 s
Bob 5086470002
) ~' T% Z' U; x: l' |9 p# w v
Charlie 5086470003 先讨论数组,因为电话号码簿中既含有数字,又含有字符串,而数组中只能存放Double类型的数据,所以没有办法用数组直接记录电话号码薄中的内容。 再试试元胞数组,我们在第1行预先声明一个 3 X 3 的元胞数组,然后在2-4行按顺序填放号码簿的内容。
% x4 h4 n: F2 j) [4 j
% 元胞数组初始化
, L; }: b4 Y1 k6 @- G- }- \
addressBook = cell(3,1); % 预分配大小是MATLAB编程的好习惯
7 T0 ]1 r- y" h/ v/ ~* L( B
addressBook{1} = {'Abby', '5086470001'};
i# g# G ]/ d% @4 U6 A" |
addressBook{2} = {'Bob' , '5086470002'};
, X. x% F! Q, R; t. h, ?7 ?/ Z
addressBook{3} = {'Charlie', '5086470003'};
+ l2 H% b( q" B* |+ p8 o2 |* w
需要的时候,可以通过for循环访问其中的内容,比如:
& R0 s% [% `7 x- b% T* ^# ^: D* R# Y
for iter = 1:length(addressBook) % 元胞数组的遍历
4 n% m! f! B: R0 A) m& T( [
addressBook{iter} % 通过Index访问元胞中的内容
2 I$ d" o' m, q. b/ h8 G( n& s
end
) j8 H( Q& r$ P6 `% _8 D0 s' G1 L
但是按照顺序遍历电话号码簿没有什么实际用处,号码簿的主要功能应该是提供查找的功能才是。比如要想查询Charlie的电话号码,我们希望程序最好可以写成如下这样:
c0 W' f+ }0 w% H2 d6 b! S1 [& e. a
CharlieNum = addressBook{'Charlie'} % 提示:这是错误的写法
, B6 [) `5 g3 L/ b
或者
. `6 z8 r4 a0 I9 y* e
CharlieNum = addressBook.Charlie % 提示:这是错误的写法
9 ~, j# U7 n; U& T/ h
但是元胞数组的值只能通过Index去访问内容,不支持如上的访问方式。所以为了找到Charlie的电话号码,程序不得不遍历元胞中的所有内容,取出每一个元胞元素的第一列的字符串做比较,如果名字等于Charlie,则输出电话号码:
; k6 |/ O2 J9 ?, ^4 o' o! c
% 使用for循环查找
& U, J' D R8 S- @
for iter = 1:length(addressBook)
8 C; v8 Q/ F/ p) R' O, x
if strcmp(addressBook{iter}{1},'Charlie')
+ v d# b' Z* R# ?; T1 H4 N
addressBook{iter}{2} % 如找到则输出电话号码
8 Y9 Z5 b) M4 g7 y' C
break;
" }1 E: k7 e" ]3 [; i
end
6 F: F9 o: }! |" V7 g; ?% @. {
end
9 \9 F3 G+ [0 i* y* a6 `$ h, I# E
当然还有其他的方式来盛放电话号码簿,比如把电话和名字分别存到到两个元胞数组中去
, ~* t S0 A# [+ A8 H
names = {'Abby','Bob','Charlie'}; % 用元胞放号码
0 J) }; P: W! ^
numbers = {'5086470001','5086470002','5086470001'}; % 用元胞放名字
3 F2 n* _4 c/ c. B! O
ind = find(strcmp(names,'Charlie')); % strcmp接受向量的输入 返回Logical数组
- L' I3 d1 o* b1 _
% find紧接着找出逻辑数组中非零元素的Index
' D4 `& [. v3 Z+ Q9 m2 j6 ?9 T
numbers{ind}
Y1 K/ ~3 b \8 M* ~
其中第3行strcmp接受元胞作为输入,在其中寻找Charlie,find函数将返回Charlie所在的位置,这样的方式比使用for循环要快,但无一例外的是,两种方法都要从头开始遍历一个数组,终究来说是慢的。 除查找性能慢外,使用元胞盛放电话号码簿类型的数据还有其它缺点:
: D% y0 i* A# L$ h |0 }
无法方便的验证重复数据。电话号码簿要求每一个人的名字都是独一无二的,所以在数据录入的时候要防止姓名的重复,但是我们没有其它办法知道某名字是否已经被使用过了,除非在每次输入的时候都对整个元胞里的内容做遍历比较。
/ B' K. m+ m, P, j( {% {9 k
无法方便地添加内容。如果电话号码簿中的记录需要不断地增长,但是我们没有办法在一开始就估计出其大概的数量,于是无法有效的预先分配内存,所以添加数据时,一旦超过预先分配的量,MATLAB就要重新分配内存了。
) C- u/ ^1 t2 M
无法方便地删除内容。如果我们要从元胞中去掉某一记录,可以找到该记录,并把该位置的元胞内容置空,但这并不会自动减小元胞数组的长度,如果 这样的删减操作多了,元胞中会留下很多没有利用的空余位置。
S" u; H) g* a: l6 e! \
不方便作为函数的参数,具体原因见struct的局限性.
5 ]3 F+ E0 S" h1 N) r: b4 z/ I1 j
最后我们再尝试一下用结构体盛放电话号码簿数据类型,struct的赋值很简单,比如可以直接赋值:
6 `2 |' M7 _9 [& r2 w
% 赋值方法1
" d' R0 |: G0 {+ V4 B
addressBook.Abby = '5086470001';
! B* A# n# @9 O+ O
addressBook.Bob = '5086470002';
, ^/ i* Q* S5 }* s3 \
addressBook.Charlie = '5086470003';
/ P0 X/ J0 w2 X# X3 O6 A
或者:
% S% y# D1 l, j E7 f R
% 赋值方法2
, i# o7 [, {# L3 ~. V
addressBook = struct('Abby','5086470001','Bob','5086470002','Charlie','5086470003')
$ l/ ?0 M* V5 r$ J9 A
方法1和方法2是等价的。 struct数据类型的查找很方便,比如要查询Charlie的电话号码,直接访问struct中的同名的field即可以了。
2 z$ i7 I W4 t
num = addressBook.Charlie
% \% k: X0 H5 Z* ~$ q! j
如果要查询的人名是一个变量, 我们可以使用getfield函数:
- V+ m* J% }( R
num = getfield(addressBook,name) % 其中name是变量
0 a7 P" Y) c! K: \6 |
struct盛放电话号码簿类型的数据,查询起来确实比元胞进步了不少,但还是有些不方便的地方。 因为struct的field的名字要求必须是以字母开头,这是一个很大的局限,并不是所有的类似电话号码簿的结构都可以用人名做索引,比如账户号码簿,股票代码等等,他们通常是用数字开头的,比如图Table.2中的数据:
3 h4 O( @. a% v; N- O
Table.2 深证股票代码
* X1 M |9 p: i
8 W9 _2 @; R' ^9 F f/ J" Z
股票代码 股票名称
- g7 J9 i6 e( l; i
000001 深发展
2 s5 |, K2 a8 I# b
000002 万科
* g" {0 }1 s8 u6 H
000004 ST国农 如果我们要求通过股票的代码来查找股票的具体信息,struct无法简单的直接做到。 使用struct的另一个不方便之处在于,当把struct当做函数参数,并且在函数内部需要对该struct做一定的修改时,为了要把修改后的结果返回,我们需要对原来的struct做完全的拷贝,显然如果struct中的数据很多的话,这样做是低效的。比如下面的函数中,addressBook被当做函数的参数传入,在函数中添加了一个新的field, 为了返回更新后的结构,函数内部必须重新构造一个新的struct,也就是s返回给该函数的调用者。
' G7 o+ p9 [! b2 V. `! V+ R
% 把struct当做函数的参数
: F* }* C# |# L+ l/ V/ x
function s = modifystruct(s)
0 ~( \, i/ L7 y, ^; u
s.Dave = '5086470004';
) Z$ ^7 P' e; E1 c. v
end
' l- o% ]; K- U5 w
用containers.Map来记录电话号码簿
. a. b% b+ D% o: h# ?0 w
上面一节我们介绍了数组,元胞数组和结构体在模拟电话号码簿这种数据结构时的局限性,这节我们来看怎么用 containers.Map 来盛放电话号码簿中的内容:
3 ^7 H& j4 y4 l1 y @
addressMap = containers.Map; % 首先声明一个映射表对象变量
- d& x0 g' J- V3 K
addressMap('Abby') = '5086470001';
+ J7 a# T- I# F/ y
addressMap('Bob') = '5086470002';
$ C* r! B9 I3 I- H) N4 D& {
addressMap('Charlie') = '5086470003';
1 w" s5 D, ], V8 p
第一行我们声明一个containers.Map的变量(其实是containers.Map类的对象)叫做addressMap,2,3,4行通过提供Key Key Value的方式来给对象赋值,其中单引号内部的值叫做键(Key),等式右边的叫做键值(Key Value)。通过这个结构,我们在MATLAB内存中,建立起了如图Fig.3的映射关系数据结构。
( c$ \( L4 ^1 R. F3 V8 a
Fig.3 电话号码簿映射表
8 |- d' d* `; n7 b$ k0 t
Fig.4 Map类的UML 查找addressMap对象中的内容极其方便,比如查找我们想知道Charlie的电话号码,只需:
) f; l- Z2 X7 {# Q# v
% 查找
. ?4 i6 m! s. F8 J0 i( A2 p9 G4 a
num = addressMap('Charlie')
8 e: ]% s% j t* p
如果我们要修改Charlie的电话号码,只需 :
: p, E, O. T; b- l
% 赋值
+ Z: L i7 U2 {: h; d+ i$ L% R
addressMap('Charlie') = newNum;
% D; ]6 l( g- j8 y% C# i
什么是containers.Map
* U j( x+ @! R6 `8 z3 X3 D: J
containers.Map是一个MATLAB的内置类(类是面向对象编程中的一个基本概念,在这里可以把类暂时理解成一种数据类型),所谓内置,就是MATLAB内部实现的,通常这种类的性能更加的优秀。containers.Map其中containers是Package的名称,Map是该Package中的一个类,Map是它的类名。用UML(Unified Modeling Language)的语法把该类表示出来,它的属性包括Count, KeyType,ValueType。它的常用方法包括keys,values,isKey,remove。如图Fig.4所示。
6 k5 f2 C3 C, @7 d
containers.Map的属性和成员方法
% i g+ u0 Z; t2 \+ v6 w+ G4 T
这节我们介绍containers.Map的属性和成员方法,假设我们这样初始化一个containers.Map对象:
3 Y- g, n+ ^6 i
% 初始化一个Map对象
% j4 T, i5 I- }" Q. G7 L3 m
addressMap = containers.Map;
; X9 z) Q) r* I2 Q
addressMap('Abby') = '5086470001';
" t) u* f, ]; n9 n! x7 q
addressMap('Bob') = '5086470002';
- b1 J+ d4 G8 I* {
addressMap('Charlie') = '5086470003';
( I& S( g( c/ N0 r0 r
在命令行中键入该对象的名称,MATLAB会显示该对象属性的基本信息:
( P" h! Y# a( Y" U9 m6 r- q
>> addressMap
2 a' \! u) R1 D5 o* V, b7 c$ L
addressMap =
: j2 z% V/ Q' t9 [
Map with properties:
2 J; y$ q& L1 P$ d& _* Z$ [2 D6 _
Count: 3 % MAP 中映射对的数目
4 @6 ^4 `' w; n1 B% R- K9 _
KeyType: char % MAP 中键的类型
) s# M/ W- z, e! N7 d2 W
ValueType: any % MAP 中键值的类型
8 m1 y. D- d2 Q8 s: y
其中Count 表示Map对象中映射对的数目。按照规定,键的类型一定要是字符类型,不能是其它数据类型,而键值的类型可以是MATLAB中的任意类型:数组,元胞,结构体,MATLAB对象,甚至JAVA对象等等。因为键值的类型可以是任何MATLAB类型,所以 containers.Map 是MATLAB中极其灵活的数据类型。 成员方法keys 用来返回对象中所有的键:
8 {! H* U3 m( l
>> addressMap.keys
( s: M' W7 u; n: Y8 _# X0 C- ?
ans =
+ |6 l( L: q" A$ l' Z! i: `( K
'Charlie' 'Abby' 'Bob'
% b( @6 F* v. k ]: M4 _# W9 Z
成员方法values用来返回对象中的所有键值
7 f; I" Z) N/ Q: ]; h1 T5 Q( |
>> addressMap.values
. d' o. z9 d4 b) t( ]
ans =
* o; i' x7 l- c5 ~- f/ e/ C
'5086470003' '5086470001' '5086470002'
: `: l1 E$ [" M+ {
remove用来移除对象中的一个键-键值对,经过下面的操作之后,该对象中的Count的值变成了2。
& Q2 ]; Z$ \$ H4 F
>> addressMap.remove('Charlie')
/ K) ?3 \6 p3 w1 x) q6 N( e
ans =
4 q1 l, m4 V( G) A
Map with properties:
. i. w2 ]& K2 C4 m" W. X
Count: 2 % 映射对数目减少
- z j {5 q6 Q
KeyType: char
, Q% o9 [; k7 I! Z7 I0 o
ValueType: any
( W& a; a& s B
+ G8 i& H2 v @. Q# X+ e& X; r1 V
isKey成员方法用来判断一个map对象中是否已经含有一个键值,比如:
& H' ?$ N( Q) c+ ~; U3 a& U" K
>> addressMap.isKey('Abby')
I2 P9 v9 e2 r* L; t( b+ B
ans =
0 u& j4 r. Y5 v5 T& X: f
1
& R# M# R5 ~6 n- Y, G
>> addressMap.isKey('abby') % 键是大小写敏感的
* a8 \" T. g' Y( c8 w# y8 J
ans =
/ Y8 H% ?' W" z. m D
0
' F9 G0 x1 e( O# q( D
isKey的功能是查询,类似之前提到的find和strcmp函数合起来使用的例子。
; X; l: O' y9 Y
containers.Map的特点
! c+ F* X( r/ o p
containers.Map可以不断地扩张不需预分配
$ \3 V- N0 f; b6 B
使用数组和元胞数组作为数据容器,通常要预先分配容器的大小。否则每次扩充容器,MATLAB都会重新分配内存,并且拷贝原来数组中的数据到新分配的内存中去。映射表是一个可以灵活扩充的容器,并且不需要预分配,每次往里面添加内容不会引起MATLAB重新分配内存。 我们可以通过提供两个元胞来构造映射表对象,比如下面我们构造一个机票簿数据结构:
& G5 J& j' m8 ~8 v* A
% 映射表的初始化方法1
3 x5 O0 ?7 ^5 W0 U- k: S T) l
ticketMap = containers.Map( {'2R175', 'B7398', 'A479GY'}, ...
) N2 `! {# @0 s/ E; [
{'Abby', 'Bob, 'Charlie'});
: S: f+ D; \6 d1 |' A9 {* C" d$ d) ^
/ v& i/ e9 C" s& J
也可以在程序的一开始,先声明一个对象:
# b/ @6 w/ g" C: w
% 映射表的初始化方法2
8 |" c0 E( m; ]+ b. W7 h
>> ticketMap = containers.Map
) l& R8 |: }& j8 e6 q+ T4 ]
然后在计算的过程中慢慢的向表中插入数据:
2 C X: b& F8 t0 b# i* ^
% 映射表的初始化方法2
+ T, S1 Z! v+ j2 o
>> ticketMap['2R175'] = 'Abby';
- ^4 v, ^9 o) E6 H
...
( m C$ M0 u' ?: O5 ^" A" F5 |* M
>> ticketMap['A479GY'] = 'Charlie;
: t: @( M9 |2 Y/ ^
containers.Map可以作为参数在函数内部直接修改
6 s6 h2 y/ W+ i3 |
因为containers.Map是handle类(handle类的介绍参见《MATLAB面向对象编程-从入门到设计模式》第三章:MATLAB的句柄类和实值类),我们还可以方便的将这个对象传给任何MATLAB的函数,并且在函数内部直接修改对象内部的数据,并且不用把它当做返回值输出,比如:
, g+ Z. W! u" D7 R; D6 G
>> modifyMap(ticketMap);
2 o2 B4 Z2 [# N5 q5 c1 h3 m: o
modifyMap函数可以写成:
e, v4 P# I4 @; B: r0 L
function modifyMap(ticketMap) % 该函数没有返回值
8 r% Y- p" x2 |9 H& D
.....
# J- I) j9 ^) T% Q$ e; q
ticketMap(NewKey) = newID
$ d; N+ z( |: k/ W6 C
end
8 J" Z* |" k: G
注意这里没有把修改的ticketMap当做返回值,在函数中我们直接修改了Map中的数据,这是MATLAB面向对象语言中handle类的特性。
& w% s( F& d J) e8 r4 {
containers.Map可以增强程序的可读性
2 U% T9 j) Y2 v
映射表内部可以存储各种类型的数据,并且给它们起一些有意义的名字。具体的例子见元素周期表的例子。 访问和修改这些数据的时候,可以直接使用这些有意义的名字,从而增加程序的可读性。而如果用元胞数组存储这些数据,在访问和修改这些数据的时候, 需要使用整数的Index,程序可读性不够好。
. p: E: k+ N1 J/ n! K
containers.Map提供对数据的快速查找
5 s, p& o7 f% m3 C9 t
映射表查找的复杂度是常数O(C)的,而传统的数组和元胞数组查找的复杂度是线性O(N)的。如果不熟悉算法中复杂度的概念,可以这样理解:如果使用数组或者元胞来存储数据,当我们要在该数据结构中搜索一个值时,只能做线性搜索,平均下来,搜索的时间和该数据结构中的元素的数目N成正比,即元胞和数组中的元素越多,找到一个值所用的时间就越长,这叫做线性的复杂度 O(N)。而映射表采取的底层实现是哈希表(Hash Map),搜索时间是常数C,理论上来说搜索速度和集合中元素的个数没有关系。所以当容器中元素数量众多的时候,要想查找得快,可以使用containers.Map结构。具体例子见快速查找的例子。 下面通过对假想的数据集合进行查找,来比较一下数组和container.Map的性能。我们第一行先构造一个含有1000000(10^7)个整数的数组(这是数组中元素的个数很多,如果只有少量元素,线性查找的效率会高一些),按顺序填充从1到(10^7)的整数;作为比较,第二行再构造一个containers.Map对象,往内部填充从1到(10^7)的整数,Key和KeyValue都相同。
0 D. p' I, G1 V2 c$ O, b2 n* F
a = 1:1000000;
' p0 O$ Z0 ~6 K2 H5 K$ Z/ b
m = containers.Map(1:1000000,ones(1,1000000));
5 D1 w0 z1 c' K8 z U: g* s$ [
数组数据结构,使用find函数依次查找从1到(10^7)的整数,在笔者的机器上,耗时28.331901秒
; a1 x" T B+ \, d( E
% array的查找
* z6 m) U, u4 N" |3 I6 Y- A
tic
% @, O5 J2 V7 v3 y$ _/ M: z' J" y
for i=1:100000,
5 B( v% t' P8 w. o! w7 d
find(b==i);
- X. l& y% `2 R$ `) q
end;
3 R( e# K G: A3 ]4 k f
toc
6 b5 A" E* k1 v% _
% command line结果
7 l: }/ u1 I0 f( J
. n6 R0 d! z# i
2 y C* I. P) e" y+ s( W' y
6 Y+ D* y; I, B; w+ A' C1 {/ S
/ d% y5 a. f' |5 `! q& T
Elapsed time is 28.331901 seconds.
6 W( A% S5 o1 F" H
containers.Map数据结构,使用键值1到(10^7)进行访问, 在笔者的机器上,耗时只需1.323007秒, 结论是:如果有大量的数据,并且要频繁的进行查找操作,可以考虑使用containers.Map数据结构。(读者可以试试减少容器中元素的个数,观察一下什么情况下,数组的查找会更快一些。)
9 X& Q8 b: ~+ i, C
% 映射表的查找
3 Y# v* r' f# f% j, K
tic
7 o, P# l) [' a
for i=1:100000,
) n2 Z' @! a) d* M: U4 u
m(i);
" x. g; t( U/ H) ~8 t: C9 f
end;
' s& |) `8 ^' k# O8 ]5 C
toc
9 F2 w6 w4 y3 _
% command line结果
8 x4 c) W! C. v |% H
4 B ^& m' P2 P9 @% D7 ?9 |
- j! k: I0 ^8 H. q' o0 L% M
2 V6 [0 V4 f9 R5 c
# E& s0 O+ w( P2 v' K7 L1 R
Elapsed time is 1.323007 seconds.
) q" n7 I2 i5 O* ?; }
谈到在MATLAB中使用高级数据结构,另一种常见的做法是直接使用Java和Python对象,这里顺便比较一下在MATLAB中使用Java的哈希表的效率。再笔者的机器上,耗时3.072889秒.
+ U% f, p' m7 h- G3 |6 E
% JAVA哈希表的查找
/ [ ~' ?, J- k. l
s = java.util.HashSet();
9 b# E8 [) c/ s4 |* U( y& `* H
for i=1:100000, s.add(i); end
# q5 o/ ~! q$ e6 J) ?. E, |
tic;
2 C4 A/ ]4 \9 L8 Q9 G
for i=1:100000,
% Q% l/ Q5 t2 O U( x" M
s.contains(i);
& C2 O% h1 f( b
end;
6 d; K- \! S, P6 w: `+ w) ?
toc
$ Y. x2 _. I# Y. c9 A7 f' a% K! K8 ]
% command line结果
( y4 O- R; I1 \0 m& H2 r# A% q
* a2 v' z' k: ` i: L' l; a
+ G. N6 O4 w- T1 ~9 l# I
: J) @. V S7 E7 U* Q( d' \/ g
" v( W/ l( h6 c
9 L5 ^, Z) c( B4 k' z, i( l4 X
; o6 y# T- u2 X3 U
Elapsed time is 3.072889 seconds.
4 t# Z+ S* t# s* w
containers.Map的使用实例
: o% ?2 e. H0 @: C, P
用来盛放元素周期表
9 {( ]7 ]- Y5 |4 b
工程计算中可以使用这种键-键值的例子有很多。比如物理化学计算中可以用映射表来盛放元素周期表中的内容:
! F5 Q5 A) f2 o- d4 c
>> ptable = containers.Map;
: D$ F3 e) w7 d) K
>> ptable('H') = 1 ;
1 j) l, {: W6 `* ~+ I
>> ptable('He') = 2 ;
3 u% P6 c+ ?" L' t
其中键是原子符号,而键值是原子量。因为键值的类型不限,所以键值本身还可以是对象,比如Atom类对象,其中包涵更多有用的内容:
2 R% ?! X+ p; a# ` u
% 类文件Atom.m
" ?# V4 A: i: G, X, K
classdef Atom < handle
8 Q$ V4 c F" l1 T5 ]3 j7 B0 Z" q
properties
1 H/ D. v8 w& e* N" l8 e
atomicNumber
4 ^) ?4 ]5 u* @! d3 m! D: V, n
fullName
/ ]( P" D* E4 ^9 L$ `7 V' b9 T ?
end
U) z; N7 L' w* R) q
methods
# d- y) W, I4 R0 y$ r6 F
function obj = Atom(num,name)
3 `( Z5 h1 B) x, A
obj.atomicNumber = num ;
$ {$ ^2 p8 W$ n7 b: c
obj.fullName = name
$ N: q! H: |1 U4 a9 V7 e2 N
end
9 l4 |9 O6 N2 Z: G$ g6 M
end
7 c' s$ z9 j- ?! a3 y% C
end
! S$ m0 ?/ X" Q i4 ]+ w+ x
\end{Verbatim} 于是:
4 ?3 P) e7 V3 ?
% 键值可以是Atom对象
9 Y+ k( r' P5 y
>> ptable('H') = Atom(1,'hydrogen');
0 O$ o: m) K+ J6 |1 K4 D9 K, m' q
>> ptable('H') = Atom(2,'helium');
; c7 a. |' g) B3 F1 `; O) i
用来实现快速地检索
7 d8 B- s" N- T, D
这个问题来自ilovematlab一个网友的提问:
( s4 \0 D" C5 L9 b
大家好,最近遇到一个问题,要构建20000次以上的三元素向量,且保证每次不重复构建,该向量元素值在2000―3000之间不定数,目前采用的方法是建立一历史档案矩阵A,构建一个存储一行,A大小行数持续增加。每次在构建出一新的向量时对该向量与历史档案矩阵每行进行对比,最终确定是否构建过,若没构建过,重新构建。算法显示该检查程序运算时间在执行20000次以上时,会超过200S的运行时间。想力争减小该时间,求方法。 多谢!
0 B* w& u4 O/ Y* T
这位网友的问题不在于如何构建这些向量,而是如何保证每次不重复的构建。他一开始想出的解决方法是用矩阵A来存储历史档案,每建立一个新的向量,和该历史档案已有的内容做比较,如果在档案中没有存档,则构建。这样设计的问题是,如他所述,当A中存储的向量变多之后,每次检查该向量是否之前被创建过的时间加长。 这是一个典型的可以用映射表来解决的问题,把线性的复杂度转成常数的复杂度。他可以给每个向量设计一个独立无二的ID,比如直接转数字成id : num2str(vector)
: w/ _2 W3 X- T b
% 构建
, o/ ~# Y0 ^. h; r
mydictionary = containers.Map
' u+ \$ Q# z/ S; b. M. ]6 i1 T7 c2 N: V
8 }% H, X( `$ ?3 v4 F
% 插入数据
G" W5 T2 P+ w
id = num2str(vector) ; % 比如vector = [1 2 3];
! n, Z) [; y9 k$ \
mydictionary('some_id') = vector ;
2 e" O @ O5 ?0 r
之后的检索也是通过vector得到独一无二的ID,然后通过isKey来判断该键值是否已经被加入到MAP中去了。
$ Q7 {5 e4 w0 t$ d
some_otherID = some_other_vector ;
3 }6 K1 C5 u ~8 l" k
% 验证是否已经构建给过
4 H2 s$ ~! m1 s% m$ n8 o- E" J* C
if mydictinary.isKey('some_otherID')
/ {7 I+ B# }2 w0 u$ [6 E
% 做要做的工作
5 k" u% T3 @3 L5 E/ T! F; W; Q
end
作者:
yin123
时间:
2020-11-30 16:47
MATLAB映射表数据结构
欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/)
Powered by Discuz! X3.2