|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
2 Y# Q" J$ d% R% D( Q# {目录:
, m C( ^' |, Z! G0 x8 N9 R
( ?' O, L) d1 K3 T9 ^; `! scontainers.Map简介( B8 C: f" k5 A3 E* E" ^' e
数组,元胞和结构体的局限性% G6 @: O8 V+ ^% O( |) s6 s
什么是containers.Map
/ O0 A/ ]% d8 k7 M/ lcontainers.Map的属性和成员方法3 E5 w$ g H2 ^: G( X
containers.Map的特点
1 m: f3 ` ^1 ^* W1 O! F. j: W! Mcontainers.Map可以不断地扩张不需预分配. ^, \- F) h3 Y9 H) u) N y
containers.Map可以作为参数在函数内部直接修改
4 y7 V9 f8 V- n) ?, Xcontainers.Map可以增强程序的可读性
' \! m. t! D, Lcontainers.Map提供对数据的快速查找. W! R( v8 T# v, u$ D* V# j
containers.Map的使用实例: d# P: ]5 |, l# m
用来盛放元素周期表
6 X; `$ L: | Y6 _* E4 M用来实现快速地检索# c+ o- L: U- e; B0 n
MATLAB常用基本数据类型有:整型,浮点型,字符型,函数句柄,元胞数组和结构体数组。除了这些基本数据类型,MATLAB还有很多其它的数据类型不为人熟悉,这些数据类型在编程中也非常有用。MATLAB高级数据类型系列旨在向大家介绍它们:比如 containers.Map,tables,enumeration和time series等等,它们为什么有用,用来解决什么问题,并且怎样在科学工程计算使用。本篇首先介绍 containers.Map 数据类型。3 Q" k; _: V" |4 ^) k
containers.Map简介$ i$ z" E" v u1 q
MATLAB中最具代表性的高级数据类型是containers.Map,我们可以把它叫做映射表。它和函数映射有些类似,比如函数映射的定义是:
" l$ B, h1 q! v+ ]+ lF(x) = Y; C/ j* ^- ?- W6 H8 S3 M+ o
针对每一个X,都有一个唯一的Y与之对应,反之不一定。如图Fig.1所示。和函数映射相似,映射表用来形容键(Key)和键值(Key Value)之间的一一对应关系。每个Key都是独一无二的,且只能对一个Key Value。如图Fig.2所示。
6 N. ] r6 X+ qFig.1 函数映射关系
6 Y a+ _3 @6 @9 Z$ t) H# `Fig.2 containers.Map类的映射示意图+ [6 u2 T0 P O* ]- h
数组,元胞和结构体的局限性. k7 G% G; f# O* m
开始我们先介绍一下数组,元胞数组和结构体的局限性,为什么有的时候这些基本的数据类型无法满足程序的要求,换句话说,我们为什么需要 containers.Map 数据结构。假设要用MATLAB来记录电话号码簿中数据,比如表Table.1所示:) P3 S$ g) E1 h. Z7 ]! n# q6 f% N `7 p
Table.1 电话号码簿$ d o) z2 }7 }. I/ a
5 C N( d) ^1 t1 m$ l+ X姓名 电话号码
1 o7 y$ ?8 F$ {0 d) P/ u: uAbby 5086470001
& \8 }, n Z7 e N, y1 }/ ?( FBob 5086470002/ c% f( Q6 q/ N* R7 g5 E
Charlie 5086470003 先讨论数组,因为电话号码簿中既含有数字,又含有字符串,而数组中只能存放Double类型的数据,所以没有办法用数组直接记录电话号码薄中的内容。 再试试元胞数组,我们在第1行预先声明一个 3 X 3 的元胞数组,然后在2-4行按顺序填放号码簿的内容。
1 V) s" G! p3 U O( n- w% 元胞数组初始化
6 ]& ^+ S5 ~! S2 A# g6 d0 | uaddressBook = cell(3,1); % 预分配大小是MATLAB编程的好习惯 . V$ k! R Z3 m) {0 S6 Z" x
addressBook{1} = {'Abby', '5086470001'};& F4 g9 x8 N( D- O" D1 O" N
addressBook{2} = {'Bob' , '5086470002'};& X+ Z. d4 Y% c$ h* J. @
addressBook{3} = {'Charlie', '5086470003'};
# T2 C+ S8 s0 D需要的时候,可以通过for循环访问其中的内容,比如:7 H3 i; O1 W0 O7 @+ Y2 g: |+ {4 r
for iter = 1:length(addressBook) % 元胞数组的遍历
" V0 T7 Q& Q+ _/ O) d addressBook{iter} % 通过Index访问元胞中的内容3 c8 }1 X& T0 k$ o# ]3 O9 @- q
end# s, g/ Z) z4 E" j( w3 ~
但是按照顺序遍历电话号码簿没有什么实际用处,号码簿的主要功能应该是提供查找的功能才是。比如要想查询Charlie的电话号码,我们希望程序最好可以写成如下这样:: N9 p8 n& w0 T- j) b" Q
CharlieNum = addressBook{'Charlie'} % 提示:这是错误的写法
: y/ B8 H1 s* f: E0 V; b或者; q% t2 [7 N# m1 W
CharlieNum = addressBook.Charlie % 提示:这是错误的写法$ H8 [3 O6 P8 E/ u* f
但是元胞数组的值只能通过Index去访问内容,不支持如上的访问方式。所以为了找到Charlie的电话号码,程序不得不遍历元胞中的所有内容,取出每一个元胞元素的第一列的字符串做比较,如果名字等于Charlie,则输出电话号码:9 p8 i0 z$ j$ Y7 x
% 使用for循环查找/ V3 c1 W& ^3 s; b8 i$ U
for iter = 1:length(addressBook)
9 q6 c8 V* n+ _" x if strcmp(addressBook{iter}{1},'Charlie')
3 L4 m( }4 E! K3 B addressBook{iter}{2} % 如找到则输出电话号码
7 `2 ?- S( K. M" U9 y break;
' Q# d2 B+ f" Z+ [) k% i( e end
% i) `8 a5 {! b$ r9 T! P" qend
2 y: }# {! j! h% z7 l9 h, n当然还有其他的方式来盛放电话号码簿,比如把电话和名字分别存到到两个元胞数组中去! b6 Y, H, v. g& ~( U2 T# H5 n. d
names = {'Abby','Bob','Charlie'}; % 用元胞放号码7 e$ L( U u! `
numbers = {'5086470001','5086470002','5086470001'}; % 用元胞放名字5 k N: f5 h E& Z9 U0 V- ?
ind = find(strcmp(names,'Charlie')); % strcmp接受向量的输入 返回Logical数组 - q$ `" D- ?5 h, V9 |9 @
% find紧接着找出逻辑数组中非零元素的Index
% j4 ? f W# j; Inumbers{ind}
+ o% `. L% p4 U# T* D7 ^0 ]其中第3行strcmp接受元胞作为输入,在其中寻找Charlie,find函数将返回Charlie所在的位置,这样的方式比使用for循环要快,但无一例外的是,两种方法都要从头开始遍历一个数组,终究来说是慢的。 除查找性能慢外,使用元胞盛放电话号码簿类型的数据还有其它缺点:
8 m& O9 S; O2 s3 B- x无法方便的验证重复数据。电话号码簿要求每一个人的名字都是独一无二的,所以在数据录入的时候要防止姓名的重复,但是我们没有其它办法知道某名字是否已经被使用过了,除非在每次输入的时候都对整个元胞里的内容做遍历比较。 Q2 i3 Z' z* Q3 Y
无法方便地添加内容。如果电话号码簿中的记录需要不断地增长,但是我们没有办法在一开始就估计出其大概的数量,于是无法有效的预先分配内存,所以添加数据时,一旦超过预先分配的量,MATLAB就要重新分配内存了。
+ i* W: h) _; Z2 k" e8 Q无法方便地删除内容。如果我们要从元胞中去掉某一记录,可以找到该记录,并把该位置的元胞内容置空,但这并不会自动减小元胞数组的长度,如果 这样的删减操作多了,元胞中会留下很多没有利用的空余位置。
7 ~5 h1 w: r3 g. Y& p- ~不方便作为函数的参数,具体原因见struct的局限性.$ V, B% k# k, l
最后我们再尝试一下用结构体盛放电话号码簿数据类型,struct的赋值很简单,比如可以直接赋值:
0 _4 L+ f7 S2 a6 {/ C% 赋值方法1) q; p( H% W$ Y# ~2 \9 H% t
addressBook.Abby = '5086470001';) L" g: k( D+ {3 h, W8 R) O
addressBook.Bob = '5086470002';
- p5 P" j7 w1 c: L* F* MaddressBook.Charlie = '5086470003';
$ v9 m- `: Z, v6 u; d; X5 X或者: e# a8 W {7 q8 d3 `
% 赋值方法2
% B) ^4 [* B! H. VaddressBook = struct('Abby','5086470001','Bob','5086470002','Charlie','5086470003')
/ ~/ T: H) [/ N% b' w方法1和方法2是等价的。 struct数据类型的查找很方便,比如要查询Charlie的电话号码,直接访问struct中的同名的field即可以了。; W7 S4 N) E# {: g+ |( A
num = addressBook.Charlie _) k5 p' q8 |* E( C
如果要查询的人名是一个变量, 我们可以使用getfield函数:/ E' \5 p. E- p4 U5 e
num = getfield(addressBook,name) % 其中name是变量 ) |- L& Y5 B; H
struct盛放电话号码簿类型的数据,查询起来确实比元胞进步了不少,但还是有些不方便的地方。 因为struct的field的名字要求必须是以字母开头,这是一个很大的局限,并不是所有的类似电话号码簿的结构都可以用人名做索引,比如账户号码簿,股票代码等等,他们通常是用数字开头的,比如图Table.2中的数据:: }3 k* q+ l4 U, s8 Y0 w5 K7 N8 `
Table.2 深证股票代码
/ V. V# }- e- ^3 j% e$ i1 F/ J, r3 @# F3 l" z, L
股票代码 股票名称0 @3 m* m# @0 q+ x7 f% g1 |8 p$ F
000001 深发展
# d+ I. A3 C* @+ h/ i; G% e" y4 k000002 万科
9 s d9 E# m9 f1 e000004 ST国农 如果我们要求通过股票的代码来查找股票的具体信息,struct无法简单的直接做到。 使用struct的另一个不方便之处在于,当把struct当做函数参数,并且在函数内部需要对该struct做一定的修改时,为了要把修改后的结果返回,我们需要对原来的struct做完全的拷贝,显然如果struct中的数据很多的话,这样做是低效的。比如下面的函数中,addressBook被当做函数的参数传入,在函数中添加了一个新的field, 为了返回更新后的结构,函数内部必须重新构造一个新的struct,也就是s返回给该函数的调用者。
- I# Z8 V/ a1 V0 A3 X% 把struct当做函数的参数
1 P$ z$ Z$ D! @5 f5 t5 k9 zfunction s = modifystruct(s)+ q$ E' V: J/ q6 T" J
s.Dave = '5086470004';
) w$ y$ k; w- b8 N: mend
2 ]/ A3 ]- W2 f2 c8 ~4 ?用containers.Map来记录电话号码簿, y% @) T( S3 T' d: X
上面一节我们介绍了数组,元胞数组和结构体在模拟电话号码簿这种数据结构时的局限性,这节我们来看怎么用 containers.Map 来盛放电话号码簿中的内容:1 h4 i$ M7 ~) H+ R! a. C
addressMap = containers.Map; % 首先声明一个映射表对象变量
3 x2 R2 Z: \% r+ UaddressMap('Abby') = '5086470001';
1 r, k8 \" i7 k# n: C0 ^+ x1 B, caddressMap('Bob') = '5086470002';
8 N( B2 y* o$ ]4 x" [" J$ TaddressMap('Charlie') = '5086470003';
$ J# s4 P7 l. t5 O; d8 b' G第一行我们声明一个containers.Map的变量(其实是containers.Map类的对象)叫做addressMap,2,3,4行通过提供Key Key Value的方式来给对象赋值,其中单引号内部的值叫做键(Key),等式右边的叫做键值(Key Value)。通过这个结构,我们在MATLAB内存中,建立起了如图Fig.3的映射关系数据结构。
0 y- p; a1 j9 q# C& X, cFig.3 电话号码簿映射表- q3 V+ c1 G# u! u$ K
Fig.4 Map类的UML 查找addressMap对象中的内容极其方便,比如查找我们想知道Charlie的电话号码,只需:
1 t% @9 G N7 q% 查找
2 E" s; o& L, P7 W$ ?num = addressMap('Charlie') ) R5 }& F2 `/ R% g. Y5 e# A
如果我们要修改Charlie的电话号码,只需 :
9 B U9 B9 F# Y4 X2 @# P* d. ?% 赋值
+ [. Y+ t. P! g: L Y1 X& uaddressMap('Charlie') = newNum;
& w" b0 m( i5 P3 B什么是containers.Map
1 }" c6 x9 t1 ~" fcontainers.Map是一个MATLAB的内置类(类是面向对象编程中的一个基本概念,在这里可以把类暂时理解成一种数据类型),所谓内置,就是MATLAB内部实现的,通常这种类的性能更加的优秀。containers.Map其中containers是Package的名称,Map是该Package中的一个类,Map是它的类名。用UML(Unified Modeling Language)的语法把该类表示出来,它的属性包括Count, KeyType,ValueType。它的常用方法包括keys,values,isKey,remove。如图Fig.4所示。6 N: c* T3 T& N( v% S. ~0 ^0 x
containers.Map的属性和成员方法
0 h5 ]& m9 S4 s8 J这节我们介绍containers.Map的属性和成员方法,假设我们这样初始化一个containers.Map对象:
$ v( O, o: K3 M# G% 初始化一个Map对象( N" j% z) Y* ^& y! X) O' ~, l7 {4 Y
addressMap = containers.Map;5 T1 A# G( ^: p8 o& D9 u
addressMap('Abby') = '5086470001';
: Y: Q1 F) N& d9 e1 \0 n( QaddressMap('Bob') = '5086470002';7 ^" F7 ?9 x* l! A
addressMap('Charlie') = '5086470003';
, o! e$ I2 ^/ a: B: k" _在命令行中键入该对象的名称,MATLAB会显示该对象属性的基本信息:- k; A3 x8 k/ F0 u
>> addressMap* \9 w# k( |; ~! u# g+ n+ m
addressMap =
" k6 X1 x9 o z; z4 R$ J7 ~ Map with properties:2 ^. h3 v" v) A" B( o( n3 |
Count: 3 % MAP 中映射对的数目1 F; \* ^( A/ X3 T U2 \
KeyType: char % MAP 中键的类型 ' g7 s& g* e+ s0 ?3 E/ ^9 ]; A
ValueType: any % MAP 中键值的类型 0 T5 c, A0 o9 r/ U5 m$ ~
其中Count 表示Map对象中映射对的数目。按照规定,键的类型一定要是字符类型,不能是其它数据类型,而键值的类型可以是MATLAB中的任意类型:数组,元胞,结构体,MATLAB对象,甚至JAVA对象等等。因为键值的类型可以是任何MATLAB类型,所以 containers.Map 是MATLAB中极其灵活的数据类型。 成员方法keys 用来返回对象中所有的键:0 h6 J+ F$ P H7 b ?5 V# T3 R' X
>> addressMap.keys
, a( U( R9 p' x0 oans =
8 h6 u N8 E8 X& I5 p9 X5 H 'Charlie' 'Abby' 'Bob' . x- |8 s. E4 P4 C2 Z
成员方法values用来返回对象中的所有键值) c- H; L7 c$ N' G2 X9 W) o
>> addressMap.values4 q4 N+ t9 H% |# R- w1 T# d: n
ans =
# V) u1 U2 D' e% B! d. D '5086470003' '5086470001' '5086470002' " E2 @, w% [8 [5 G& ^3 p2 `5 ~
remove用来移除对象中的一个键-键值对,经过下面的操作之后,该对象中的Count的值变成了2。6 x$ Y1 C/ f% o- k: d! I( D
>> addressMap.remove('Charlie'). L$ n. ^3 @) r' N5 J/ l6 m8 c
ans = , _$ V; _- \1 V4 j/ n# @4 C
Map with properties:
1 t$ ?3 L" W ?4 P8 w Count: 2 % 映射对数目减少8 |* T! z+ g% d) z
KeyType: char
6 Y- {" H) D- Y# Q& u0 s ValueType: any
( K( v9 f6 m: ^7 Y$ e$ G
& T, ?1 x7 c8 j* F) u# t- B( PisKey成员方法用来判断一个map对象中是否已经含有一个键值,比如:
7 Z V7 [% C6 N' p' e. U r>> addressMap.isKey('Abby') N2 a1 T% z1 d6 N1 ?
ans =5 z i7 Q: x% T
1
2 z% g8 U2 u6 p( o6 i: d' s$ n>> addressMap.isKey('abby') % 键是大小写敏感的5 D3 \) E+ g1 S7 u4 v- H: E
ans =4 _0 a& A9 G" K* s+ m2 E
0
7 J. k. {% H/ LisKey的功能是查询,类似之前提到的find和strcmp函数合起来使用的例子。
4 D: ^+ E. ]- i8 x, ^containers.Map的特点3 y2 ?! o/ r/ \5 G2 x
containers.Map可以不断地扩张不需预分配
: K" n* K) `2 o- ?! M B使用数组和元胞数组作为数据容器,通常要预先分配容器的大小。否则每次扩充容器,MATLAB都会重新分配内存,并且拷贝原来数组中的数据到新分配的内存中去。映射表是一个可以灵活扩充的容器,并且不需要预分配,每次往里面添加内容不会引起MATLAB重新分配内存。 我们可以通过提供两个元胞来构造映射表对象,比如下面我们构造一个机票簿数据结构: a3 [9 p ]) B
% 映射表的初始化方法1
. I+ M" k/ ^# W+ z+ R7 ZticketMap = containers.Map( {'2R175', 'B7398', 'A479GY'}, ...
0 @5 J4 a. S4 R. u7 c {'Abby', 'Bob, 'Charlie'});
/ l/ @/ ]# X+ j! `- N % s/ X; A% {6 V
也可以在程序的一开始,先声明一个对象:
2 |2 K- E# |9 g# P& y0 r1 h% 映射表的初始化方法2 4 G S; A- E# z+ ~" K2 q+ L, ]& N; X
>> ticketMap = containers.Map
, F8 T' I9 w0 h3 ~然后在计算的过程中慢慢的向表中插入数据:! j+ N7 I/ Z7 a [2 }
% 映射表的初始化方法2
6 w4 q$ f- ]+ X2 G' X>> ticketMap['2R175'] = 'Abby';
9 _# r+ d# d+ A' F! B6 W...
. b' Q, N/ s8 I. ]( A) v+ e( b4 _9 w>> ticketMap['A479GY'] = 'Charlie; # q3 {- h+ C1 p8 R A, {
containers.Map可以作为参数在函数内部直接修改
8 j+ o9 D" s4 g& z" w" C; T因为containers.Map是handle类(handle类的介绍参见《MATLAB面向对象编程-从入门到设计模式》第三章:MATLAB的句柄类和实值类),我们还可以方便的将这个对象传给任何MATLAB的函数,并且在函数内部直接修改对象内部的数据,并且不用把它当做返回值输出,比如:5 W3 l5 {) `& @: a, B6 a, F( {
>> modifyMap(ticketMap); " {' R, `! v0 M" r4 g
modifyMap函数可以写成:( W S+ \8 \# R p
function modifyMap(ticketMap) % 该函数没有返回值7 E# P4 c$ \9 K1 L1 B. G9 _
.....
! Z& E* h9 m2 l, q( O' r$ g" \; m! j ticketMap(NewKey) = newID
" j. S. p) c, [: ^end
N8 b# e4 C3 ^8 w. d1 ]注意这里没有把修改的ticketMap当做返回值,在函数中我们直接修改了Map中的数据,这是MATLAB面向对象语言中handle类的特性。
* u! {, P) |6 z8 r. u& F. i1 h6 scontainers.Map可以增强程序的可读性: d. z5 M1 p9 M2 b
映射表内部可以存储各种类型的数据,并且给它们起一些有意义的名字。具体的例子见元素周期表的例子。 访问和修改这些数据的时候,可以直接使用这些有意义的名字,从而增加程序的可读性。而如果用元胞数组存储这些数据,在访问和修改这些数据的时候, 需要使用整数的Index,程序可读性不够好。
$ _& H( I" c1 r# _9 U! n5 vcontainers.Map提供对数据的快速查找
) ^3 B! `2 [7 p7 P+ k! [映射表查找的复杂度是常数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都相同。' r* s h& M6 q' |% q
a = 1:1000000;
; H4 v( L+ r" K/ A" \/ i" Z- r/ L2 \m = containers.Map(1:1000000,ones(1,1000000)); ) L! p0 B: t9 j1 M5 d' d2 ^
数组数据结构,使用find函数依次查找从1到(10^7)的整数,在笔者的机器上,耗时28.331901秒
; s6 U" y+ r* t3 D2 V/ T; p! g% array的查找; |& G5 q' |" ^6 ?" L
tic0 S: w& S+ y7 W& \, ^* }4 T
for i=1:100000,
\5 z8 ]8 \, L6 S7 C4 T" ] find(b==i);
3 l4 H: {! I9 z* @+ wend; {8 X% Y" _8 G" n4 m& O9 x
toc
) }' \' }, Z( s1 q' a8 f- j% command line结果
- a" {8 ]: j& u( m8 z
% s- k3 J; A. {
3 r1 j- P" Q: {* ^7 D- O, b: Z
2 k+ y- g2 Y2 z: e4 q' A0 {3 X& r1 W# j n
Elapsed time is 28.331901 seconds. 8 S( z0 R, x1 A$ ]# N4 D! U' z
containers.Map数据结构,使用键值1到(10^7)进行访问, 在笔者的机器上,耗时只需1.323007秒, 结论是:如果有大量的数据,并且要频繁的进行查找操作,可以考虑使用containers.Map数据结构。(读者可以试试减少容器中元素的个数,观察一下什么情况下,数组的查找会更快一些。)4 m! s" A' @9 K+ Q; g
% 映射表的查找
* ^; y- Z T5 o( K6 `$ n7 htic
0 I5 h3 [0 C( a2 x0 ~- k/ Cfor i=1:100000, ! S4 M- b e \( O1 q
m(i);
3 T& L9 B& e; i6 x. U, `end;
8 r; F" y! b$ z# p' x$ W6 gtoc
5 E5 U" _" r8 @5 j3 s' l, m5 m7 o% command line结果 8 ?; J( g4 N( r/ C8 K; l
' Y# U8 D' ]3 [) ~; ]
- T! H/ Z. v* `. r5 s6 {# r2 O4 D7 W0 s8 L4 [$ o7 v! h
! _2 w5 ^1 h/ l5 z; [( f) F
Elapsed time is 1.323007 seconds.# P# |) T J0 d
谈到在MATLAB中使用高级数据结构,另一种常见的做法是直接使用Java和Python对象,这里顺便比较一下在MATLAB中使用Java的哈希表的效率。再笔者的机器上,耗时3.072889秒.
& `, ]8 d! [' P1 f! d# m& o% JAVA哈希表的查找
- @% M5 Z& g' K" X4 U5 u& Cs = java.util.HashSet();
2 w5 `) F1 k; o8 Z; Vfor i=1:100000, s.add(i); end
$ K* I l {$ O0 n$ P, Dtic;
2 r. }% ` \& \% Q) E! o$ N0 Afor i=1:100000, 1 w, A4 q! ]8 C0 |8 s" A+ m% w- D3 D
s.contains(i);
) D p P3 v# e0 ?4 Y, V% U& [end; 4 W' ]3 }. q1 Q" B
toc
! {4 k. I+ O3 h+ e Q. h( W% command line结果
. K4 ^+ u# y5 q1 |% c- m7 n9 @7 I5 i0 a- U( f# W8 Q) Q8 S
6 s+ t& H3 O4 h; E
3 h( ]# Q: p, q6 M t' ~5 I! o/ A
+ E. D& t/ Z- k# a0 I
2 E/ a" E# d' ^/ g6 _9 Y0 m/ D% S' Y d) z0 L) B1 y/ j
Elapsed time is 3.072889 seconds.
|3 g( Z) w2 f$ e5 e: b0 Lcontainers.Map的使用实例
7 J2 ^3 F( ^/ W7 g, l用来盛放元素周期表3 w5 e* p* L" g" C, q" m, D
工程计算中可以使用这种键-键值的例子有很多。比如物理化学计算中可以用映射表来盛放元素周期表中的内容:( C; g" b( `! j; Z0 i
>> ptable = containers.Map;
# M1 h& n$ l: \>> ptable('H') = 1 ;- f, {+ U0 J/ M0 \
>> ptable('He') = 2 ;
4 A, _5 L4 A( V/ H* K- i! V其中键是原子符号,而键值是原子量。因为键值的类型不限,所以键值本身还可以是对象,比如Atom类对象,其中包涵更多有用的内容:7 n M s( P. I2 d% U4 [- x8 n
% 类文件Atom.m
6 u/ i; I+ C8 f/ Z8 Uclassdef Atom < handle
2 H. Y$ {% B3 Q" S& n& v properties
6 P. w; r5 n2 k X" M' o- { atomicNumber j( S' @) k2 S6 B
fullName
5 ?& {6 e# ]3 u6 X# L end
9 |( X6 u) x! w* Z- ]1 ^ methods
/ C: m0 a0 p K3 M function obj = Atom(num,name)
: ^- H8 Y$ O: K/ `% R/ C obj.atomicNumber = num ;$ x% X- U/ C* C# f& f! N- `
obj.fullName = name
7 l4 R$ F- P9 X& X5 ]9 T/ E- q8 J8 a7 d end
4 J) U, x$ d: L# O- G, u5 p end" X( i2 { d% Z
end
* v! |, h [, X# a9 R6 c# r. }6 {\end{Verbatim} 于是: C9 \& p- C3 l& ~ P; U/ f
% 键值可以是Atom对象
# N+ Z5 G/ P6 c0 x>> ptable('H') = Atom(1,'hydrogen');
- y# P) _; a* A2 ~0 R>> ptable('H') = Atom(2,'helium');
5 e/ Q ]' [! P' E用来实现快速地检索
, A4 [$ y- I9 W5 Y. g1 B这个问题来自ilovematlab一个网友的提问:
, e3 h+ f: Z+ l大家好,最近遇到一个问题,要构建20000次以上的三元素向量,且保证每次不重复构建,该向量元素值在2000―3000之间不定数,目前采用的方法是建立一历史档案矩阵A,构建一个存储一行,A大小行数持续增加。每次在构建出一新的向量时对该向量与历史档案矩阵每行进行对比,最终确定是否构建过,若没构建过,重新构建。算法显示该检查程序运算时间在执行20000次以上时,会超过200S的运行时间。想力争减小该时间,求方法。 多谢!
0 S- m9 k3 [: ]9 k( G这位网友的问题不在于如何构建这些向量,而是如何保证每次不重复的构建。他一开始想出的解决方法是用矩阵A来存储历史档案,每建立一个新的向量,和该历史档案已有的内容做比较,如果在档案中没有存档,则构建。这样设计的问题是,如他所述,当A中存储的向量变多之后,每次检查该向量是否之前被创建过的时间加长。 这是一个典型的可以用映射表来解决的问题,把线性的复杂度转成常数的复杂度。他可以给每个向量设计一个独立无二的ID,比如直接转数字成id : num2str(vector)
3 b# Q8 l3 H, }& B2 k' r. Z) a8 e4 B% 构建
% {% M, r5 t7 T: b0 V2 r fmydictionary = containers.Map
! h' B3 S: X |6 o, b) U2 ]' [: L7 r) G" J( q% q/ h' a T( x! r
% 插入数据+ F( ^% m. ~! V3 \* M
id = num2str(vector) ; % 比如vector = [1 2 3];, {; p8 l6 d1 O6 P0 q2 ?
mydictionary('some_id') = vector ; 5 \0 P$ E5 {+ F; V! N5 F' z
之后的检索也是通过vector得到独一无二的ID,然后通过isKey来判断该键值是否已经被加入到MAP中去了。6 v% z3 ~6 z/ T, O( E( V2 r/ g
some_otherID = some_other_vector ;
% k; _8 A# `, B# D! H* @% 验证是否已经构建给过
8 {7 Q( @) Q( _' s- dif mydictinary.isKey('some_otherID'): h& b2 {) e1 P7 J/ y, A2 D
% 做要做的工作
8 f' G C6 C% k% l! |; ~& X5 Bend |
|