找回密码
 注册
关于网站域名变更的通知
查看: 476|回复: 1
打印 上一主题 下一主题

#技术风云榜#MATLAB映射表数据结构

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2020-11-30 15:52 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
4 [6 D0 E9 }  y& K4 Y
目录:
( B9 U& n& W* X( ]# N# d: \9 G
$ L% {" l' v; }# J% vcontainers.Map简介. q$ e3 I6 c: [' F& Z3 m
数组,元胞和结构体的局限性$ y: E* E5 ]' u$ H5 H
什么是containers.Map
& L% z4 d/ Q% J0 ~9 I# fcontainers.Map的属性和成员方法
0 X7 p9 \& A) e0 Q) D+ Q! `/ [; mcontainers.Map的特点
, f4 v. o$ ]: f" Tcontainers.Map可以不断地扩张不需预分配
% S( B$ {0 n8 ~1 T  @% Y3 G/ Rcontainers.Map可以作为参数在函数内部直接修改+ ?, g* z* \4 ?1 U5 s/ ?
containers.Map可以增强程序的可读性
. U$ r" u6 ]# x% C1 V8 ocontainers.Map提供对数据的快速查找
. s, }4 B( G" e! W; ncontainers.Map的使用实例
2 ^3 f  v0 D1 B( c8 H+ m2 O用来盛放元素周期表
& n+ v0 Q2 D, F/ W) `1 Z  Z# O8 _用来实现快速地检索& i  {7 l' U  V5 o) D; H! ]
MATLAB常用基本数据类型有:整型,浮点型,字符型,函数句柄,元胞数组和结构体数组。除了这些基本数据类型,MATLAB还有很多其它的数据类型不为人熟悉,这些数据类型在编程中也非常有用。MATLAB高级数据类型系列旨在向大家介绍它们:比如 containers.Map,tables,enumeration和time series等等,它们为什么有用,用来解决什么问题,并且怎样在科学工程计算使用。本篇首先介绍 containers.Map 数据类型。# I( k6 B- t& m
containers.Map简介
$ b& x; n6 E" w: `# U( p9 oMATLAB中最具代表性的高级数据类型是containers.Map,我们可以把它叫做映射表。它和函数映射有些类似,比如函数映射的定义是:! _- o& t4 z/ C
F(x) = Y+ i8 z0 z2 a' o  E; X* X9 M
针对每一个X,都有一个唯一的Y与之对应,反之不一定。如图Fig.1所示。和函数映射相似,映射表用来形容键(Key)和键值(Key Value)之间的一一对应关系。每个Key都是独一无二的,且只能对一个Key Value。如图Fig.2所示。
1 D0 h8 e! N7 [5 l/ D) N8 t9 WFig.1 函数映射关系
  W/ c! C' J# WFig.2 containers.Map类的映射示意图
) V  O9 i3 A  {- P/ ^. B3 k% K数组,元胞和结构体的局限性
+ e" {; @  I0 q. `, k开始我们先介绍一下数组,元胞数组和结构体的局限性,为什么有的时候这些基本的数据类型无法满足程序的要求,换句话说,我们为什么需要 containers.Map 数据结构。假设要用MATLAB来记录电话号码簿中数据,比如表Table.1所示:
& w! J6 i- M0 t7 h& m: D# {7 jTable.1 电话号码簿/ X- l5 q% A# c/ c
2 z$ R1 }! M  Y" T5 _0 d4 T
姓名        电话号码; q  B! s. Z: ~8 R3 ?
Abby        5086470001
* Y+ K( r' K; c& E: u' ^1 g# FBob        5086470002( {/ U+ f# G1 |3 u+ _
Charlie        5086470003 先讨论数组,因为电话号码簿中既含有数字,又含有字符串,而数组中只能存放Double类型的数据,所以没有办法用数组直接记录电话号码薄中的内容。 再试试元胞数组,我们在第1行预先声明一个 3 X 3 的元胞数组,然后在2-4行按顺序填放号码簿的内容。
$ G2 C2 M$ b" L! B% 元胞数组初始化% A! t2 O- c7 }, J
addressBook    = cell(3,1);    % 预分配大小是MATLAB编程的好习惯 / j( x: c" @- ~3 {, ?0 @3 g& L
addressBook{1} = {'Abby',     '5086470001'};
7 S# J& A, `/ Q  n0 u4 zaddressBook{2} = {'Bob' ,     '5086470002'};* b7 x5 S( R) m3 A
addressBook{3} = {'Charlie',  '5086470003'};      7 k5 _0 T+ p5 X, ^
需要的时候,可以通过for循环访问其中的内容,比如:; i* [& ~% ]0 Q/ _9 A: \2 w1 Z3 Q
for iter = 1:length(addressBook)         % 元胞数组的遍历0 _! J' f* q6 T
   addressBook{iter}               % 通过Index访问元胞中的内容" J- m' W6 r% `; K; ~
end
' i% n  C; s7 g! Q* \但是按照顺序遍历电话号码簿没有什么实际用处,号码簿的主要功能应该是提供查找的功能才是。比如要想查询Charlie的电话号码,我们希望程序最好可以写成如下这样:
3 o$ L# C/ x6 {: Q! Q! v! LCharlieNum = addressBook{'Charlie'}     % 提示:这是错误的写法
+ i/ F1 \% Q6 o! d: h3 E+ o7 p* ~或者
: G& l  G/ ~: I# h2 i$ ~, VCharlieNum = addressBook.Charlie        % 提示:这是错误的写法
5 h* j. l" P3 {2 o; [, `0 q但是元胞数组的值只能通过Index去访问内容,不支持如上的访问方式。所以为了找到Charlie的电话号码,程序不得不遍历元胞中的所有内容,取出每一个元胞元素的第一列的字符串做比较,如果名字等于Charlie,则输出电话号码:
1 j, }6 r7 u, H7 D# m% 使用for循环查找- D# L* v2 Y1 V' E0 Z6 B) v
for iter = 1:length(addressBook)# X% y* Q3 \1 ~* W1 K/ N
   if strcmp(addressBook{iter}{1},'Charlie')
  p! u* e. ]! H6 ?% x# a        addressBook{iter}{2}              % 如找到则输出电话号码( k3 o! @1 S+ g6 ~. Z
      break;
1 ]+ N+ {# p) S2 \0 X: m/ V   end2 C; j" f7 _* x  y: V# a+ b7 ~
end
- B, s4 C# R% l* X0 I) d当然还有其他的方式来盛放电话号码簿,比如把电话和名字分别存到到两个元胞数组中去' s7 u4 i; ~9 r' |9 U( s
names   = {'Abby','Bob','Charlie'};                 % 用元胞放号码6 B1 B2 L- t, P7 D
numbers = {'5086470001','5086470002','5086470001'}; % 用元胞放名字
5 f% N' T8 n. q0 \ind = find(strcmp(names,'Charlie'));  % strcmp接受向量的输入 返回Logical数组 ( L9 h$ E( b3 }8 R2 e
                                      % find紧接着找出逻辑数组中非零元素的Index
  Y4 _3 K+ s# |& g5 |numbers{ind}  ' B8 u2 q6 ^" P# a/ b9 J
其中第3行strcmp接受元胞作为输入,在其中寻找Charlie,find函数将返回Charlie所在的位置,这样的方式比使用for循环要快,但无一例外的是,两种方法都要从头开始遍历一个数组,终究来说是慢的。 除查找性能慢外,使用元胞盛放电话号码簿类型的数据还有其它缺点:/ T5 {$ _0 y3 ]
无法方便的验证重复数据。电话号码簿要求每一个人的名字都是独一无二的,所以在数据录入的时候要防止姓名的重复,但是我们没有其它办法知道某名字是否已经被使用过了,除非在每次输入的时候都对整个元胞里的内容做遍历比较。0 D; L1 v1 ~- m4 A* E
无法方便地添加内容。如果电话号码簿中的记录需要不断地增长,但是我们没有办法在一开始就估计出其大概的数量,于是无法有效的预先分配内存,所以添加数据时,一旦超过预先分配的量,MATLAB就要重新分配内存了。( P% c) x7 L* N$ j% V( l
无法方便地删除内容。如果我们要从元胞中去掉某一记录,可以找到该记录,并把该位置的元胞内容置空,但这并不会自动减小元胞数组的长度,如果 这样的删减操作多了,元胞中会留下很多没有利用的空余位置。
9 }. k8 j* b  ^$ _0 u% A/ f% |- d" Y% `不方便作为函数的参数,具体原因见struct的局限性.6 w% @& j. w& O* r+ S: l- p
最后我们再尝试一下用结构体盛放电话号码簿数据类型,struct的赋值很简单,比如可以直接赋值:6 ~6 l0 U' W6 A. {, K0 F7 ~
% 赋值方法1
7 c# _  l$ N8 i. W' ~  CaddressBook.Abby   = '5086470001';: @6 M8 V( V+ B7 s
addressBook.Bob  = '5086470002';
$ Q. R7 M- o, zaddressBook.Charlie  = '5086470003';1 f( r, h1 G  N; v6 H. V5 p) `$ P
或者:1 N  D% S, }) w# A  w! V0 L
% 赋值方法23 F% e8 _, f% M9 A/ a6 B
addressBook = struct('Abby','5086470001','Bob','5086470002','Charlie','5086470003')
) n3 }8 D( `) }! n9 O方法1和方法2是等价的。 struct数据类型的查找很方便,比如要查询Charlie的电话号码,直接访问struct中的同名的field即可以了。
) p$ G2 r1 f8 O1 F% O+ M7 Vnum = addressBook.Charlie  3 K: h. X* i. g0 Z8 g
如果要查询的人名是一个变量, 我们可以使用getfield函数:. N# i) A% C$ B& n: h- l& Z9 L
num = getfield(addressBook,name)  % 其中name是变量   
  d" ~5 |: m6 m8 Gstruct盛放电话号码簿类型的数据,查询起来确实比元胞进步了不少,但还是有些不方便的地方。 因为struct的field的名字要求必须是以字母开头,这是一个很大的局限,并不是所有的类似电话号码簿的结构都可以用人名做索引,比如账户号码簿,股票代码等等,他们通常是用数字开头的,比如图Table.2中的数据:
4 {1 [7 b8 o6 [- cTable.2 深证股票代码" [" _2 Y. c0 i. O

- [$ k  C$ T' t+ h) d3 ]股票代码        股票名称: F! j/ ?; m: P! u7 B, ^% s. l
000001        深发展
# \8 f1 N8 C% I" V000002        万科1 ^( E, f& }; B: h
000004        ST国农 如果我们要求通过股票的代码来查找股票的具体信息,struct无法简单的直接做到。 使用struct的另一个不方便之处在于,当把struct当做函数参数,并且在函数内部需要对该struct做一定的修改时,为了要把修改后的结果返回,我们需要对原来的struct做完全的拷贝,显然如果struct中的数据很多的话,这样做是低效的。比如下面的函数中,addressBook被当做函数的参数传入,在函数中添加了一个新的field, 为了返回更新后的结构,函数内部必须重新构造一个新的struct,也就是s返回给该函数的调用者。  W' j: k* {! e# H( l) _# B- R
% 把struct当做函数的参数   
$ t( I8 ^$ V& g+ X4 e4 Ofunction s = modifystruct(s)
# ]0 F) `! W* y& [# a$ U& g    s.Dave =  '5086470004';7 y; R* P$ Z5 ?3 I
end% z- u1 Z: {$ K  L/ w  P5 ^- i
用containers.Map来记录电话号码簿
- m& b- e% z% r0 t2 X: p& s上面一节我们介绍了数组,元胞数组和结构体在模拟电话号码簿这种数据结构时的局限性,这节我们来看怎么用 containers.Map 来盛放电话号码簿中的内容:
- W& H" x! ~0 w; E7 i1 vaddressMap = containers.Map;             % 首先声明一个映射表对象变量
" |8 d5 l& x0 O* c" I5 EaddressMap('Abby')    = '5086470001';
  B, L/ B5 g+ JaddressMap('Bob')     = '5086470002';
) G1 W. t# n3 _; x$ G4 x' zaddressMap('Charlie') = '5086470003';  
/ b. ]0 \1 C7 d第一行我们声明一个containers.Map的变量(其实是containers.Map类的对象)叫做addressMap,2,3,4行通过提供Key Key Value的方式来给对象赋值,其中单引号内部的值叫做键(Key),等式右边的叫做键值(Key Value)。通过这个结构,我们在MATLAB内存中,建立起了如图Fig.3的映射关系数据结构。+ A% `$ |5 P8 w$ c2 o
Fig.3 电话号码簿映射表
* ?/ l9 K( @* ?Fig.4 Map类的UML 查找addressMap对象中的内容极其方便,比如查找我们想知道Charlie的电话号码,只需:
8 `1 |! `! l/ ^) _  f% 查找  
( s. B! j' @: t5 Ynum  = addressMap('Charlie')  
+ \$ L2 t0 Z' @7 W8 z; K6 w如果我们要修改Charlie的电话号码,只需 :+ d! t4 N* Z  V* s
% 赋值7 y' B+ G4 G5 P8 t: I) A2 o3 A
addressMap('Charlie') = newNum;  
+ Y* R6 L1 W! M0 S# K6 L1 F4 Y9 {什么是containers.Map
4 U9 J- y. E5 I- G2 T9 i0 ncontainers.Map是一个MATLAB的内置类(类是面向对象编程中的一个基本概念,在这里可以把类暂时理解成一种数据类型),所谓内置,就是MATLAB内部实现的,通常这种类的性能更加的优秀。containers.Map其中containers是Package的名称,Map是该Package中的一个类,Map是它的类名。用UML(Unified Modeling Language)的语法把该类表示出来,它的属性包括Count, KeyType,ValueType。它的常用方法包括keys,values,isKey,remove。如图Fig.4所示。
1 t# ~8 f( `/ S2 R6 v/ lcontainers.Map的属性和成员方法1 b7 t9 ^( N$ f5 L$ a# f
这节我们介绍containers.Map的属性和成员方法,假设我们这样初始化一个containers.Map对象:" x6 |- U4 M& x+ k6 I
% 初始化一个Map对象' F+ c, \4 G- I  E' r
addressMap = containers.Map;% i6 j. G4 Q. }) U+ Q
addressMap('Abby')    = '5086470001';: ^( x$ s! w1 i% z
addressMap('Bob')     = '5086470002';
6 U# a, V; O8 c: `0 g6 f! @addressMap('Charlie') = '5086470003';
+ n% n, `9 p" R$ I4 h在命令行中键入该对象的名称,MATLAB会显示该对象属性的基本信息:
( K1 Q2 ?' n/ s+ N+ s" {2 r% N>> addressMap
: X; ?9 m; u) [3 O. _; GaddressMap =9 G- ?4 v& ]4 {
  Map with properties:+ |9 [- A8 x0 R  Q
        Count: 3          % MAP 中映射对的数目# U# z: z; c) |# E. o$ T
      KeyType: char       % MAP 中键的类型
: t3 P4 o5 S8 Z% V    ValueType: any        % MAP 中键值的类型   3 G! b$ P! V: i, [
其中Count 表示Map对象中映射对的数目。按照规定,键的类型一定要是字符类型,不能是其它数据类型,而键值的类型可以是MATLAB中的任意类型:数组,元胞,结构体,MATLAB对象,甚至JAVA对象等等。因为键值的类型可以是任何MATLAB类型,所以 containers.Map 是MATLAB中极其灵活的数据类型。 成员方法keys 用来返回对象中所有的键:
  ^4 P0 `% P% W6 ]+ w9 _( I>> addressMap.keys5 `- K/ r( ~) W) N% J! I" E+ l
ans =0 `# j& o) |/ a8 f' _8 F
    'Charlie'    'Abby'    'Bob'  
% X1 t  O" d4 X$ H4 M8 q2 T成员方法values用来返回对象中的所有键值
$ Z) ~( S5 J) j/ F8 t9 M' I( H/ e>> addressMap.values+ K/ e9 v6 ]- b
ans =
. S7 ~+ U  s- \" v- T    '5086470003'    '5086470001'    '5086470002'  
/ V( X# S6 D4 N; tremove用来移除对象中的一个键-键值对,经过下面的操作之后,该对象中的Count的值变成了2。0 i" ]7 r: W' H5 @& n
>> addressMap.remove('Charlie'), v$ X% i+ @" W8 `' R5 I& m# `/ P- N
ans =
% t4 u4 t' W% B' l" g  Map with properties:
4 [: j, n1 x7 [1 b0 W        Count: 2          % 映射对数目减少+ ]& t0 I& I) S' h" R
      KeyType: char
$ Q% }2 [2 W2 v3 n; R) j* m0 x- l    ValueType: any# F, q. O! I2 o
  3 I1 c2 O8 e; ~1 r; s
isKey成员方法用来判断一个map对象中是否已经含有一个键值,比如:- x1 A4 \6 A# P. \2 B
>> addressMap.isKey('Abby')
; B1 h3 w6 R& x2 u3 Bans =
; k& w6 T: F( {) a     1
8 y0 M+ s- p& a6 [# n2 Q>> addressMap.isKey('abby')    % 键是大小写敏感的  d& M; C) X6 X
ans =* J' @7 z: C  w! N! n8 j* D
     0    N) D4 s: `! R3 c$ b* }- m
isKey的功能是查询,类似之前提到的find和strcmp函数合起来使用的例子。" t. b+ z0 f) F: |
containers.Map的特点3 N  r9 ?4 k7 p: z& T
containers.Map可以不断地扩张不需预分配
+ A" H! I% n$ n& l7 E使用数组和元胞数组作为数据容器,通常要预先分配容器的大小。否则每次扩充容器,MATLAB都会重新分配内存,并且拷贝原来数组中的数据到新分配的内存中去。映射表是一个可以灵活扩充的容器,并且不需要预分配,每次往里面添加内容不会引起MATLAB重新分配内存。 我们可以通过提供两个元胞来构造映射表对象,比如下面我们构造一个机票簿数据结构:
. ^/ G8 p  C' g% 映射表的初始化方法15 u7 d/ O) i5 Y  g
ticketMap = containers.Map( {'2R175', 'B7398', 'A479GY'}, ...
' x  ^8 H9 ~! |                            {'Abby', 'Bob, 'Charlie'});
4 P% U& a( ?( }+ N2 B- V* Y  
0 \! f. ~# Z- M6 P" ~也可以在程序的一开始,先声明一个对象:9 Q% ^7 t0 y4 b' F5 b/ @  T9 C
% 映射表的初始化方法2  2 x% C1 d- r2 o! n4 @: ~
>> ticketMap = containers.Map
! t4 ^1 P9 M: i  {7 I- x* [然后在计算的过程中慢慢的向表中插入数据:
" [# V: t$ b9 d- v4 k% 映射表的初始化方法2  ' Z6 A) C$ ~& I+ E$ m- w2 A7 ~
>> ticketMap['2R175'] = 'Abby';# V2 K+ O+ {2 ^6 j- J
...
. j+ k3 h$ G1 [0 t' U8 M>> ticketMap['A479GY'] = 'Charlie;  
; _  E" N6 P, z0 b* o) _containers.Map可以作为参数在函数内部直接修改
5 h' G% P" W, y4 a9 M因为containers.Map是handle类(handle类的介绍参见《MATLAB面向对象编程-从入门到设计模式》第三章:MATLAB的句柄类和实值类),我们还可以方便的将这个对象传给任何MATLAB的函数,并且在函数内部直接修改对象内部的数据,并且不用把它当做返回值输出,比如:
1 Z7 X8 g) F' T2 v8 Q>> modifyMap(ticketMap);  
3 a. ?7 ]2 H% O6 R) qmodifyMap函数可以写成:
5 N: _0 c* A, E3 lfunction modifyMap(ticketMap)   % 该函数没有返回值* }/ M) g3 u4 f/ `/ p) p) Y- T
   ...../ M' |% J& L+ {' c( {
   ticketMap(NewKey) = newID
" B8 @/ E8 z6 j) p" g8 Y" j. D( f8 _) uend  - b( N9 n* {) {# {/ Q+ E  o
注意这里没有把修改的ticketMap当做返回值,在函数中我们直接修改了Map中的数据,这是MATLAB面向对象语言中handle类的特性。* i9 W1 d8 F" T1 ?# V% }) G
containers.Map可以增强程序的可读性. v/ u8 K0 v6 O! q& X: w; I
映射表内部可以存储各种类型的数据,并且给它们起一些有意义的名字。具体的例子见元素周期表的例子。 访问和修改这些数据的时候,可以直接使用这些有意义的名字,从而增加程序的可读性。而如果用元胞数组存储这些数据,在访问和修改这些数据的时候, 需要使用整数的Index,程序可读性不够好。0 P- j! n- w, p7 @. \) {' {) D  {
containers.Map提供对数据的快速查找
3 i& x. A+ ~! \5 T( f6 D映射表查找的复杂度是常数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都相同。5 Q3 d, {1 w* H5 @6 ]' x  h5 [1 }9 U
a = 1:1000000;( e% N" k; ?1 B$ B
m = containers.Map(1:1000000,ones(1,1000000));  
5 P1 c7 D6 }/ n1 ?: C数组数据结构,使用find函数依次查找从1到(10^7)的整数,在笔者的机器上,耗时28.331901秒3 J0 n, t" C3 _  ?) b
% array的查找
% a# n. \* N. t3 _* d: N- }2 f  ftic
$ c5 v, O- t* q: o6 rfor i=1:100000,
+ [1 N0 p' Z' O    find(b==i);1 i4 z# u0 W$ }! a
end;
) P! o/ h# I* Y. Etoc  6 i/ s* b! x" g/ L/ |
% command line结果
! T/ N0 L6 m# o9 p: l% D. O/ m2 K& L# s2 k) x

% J/ R7 {5 A# Y% g2 F
8 k  a  }  _( d/ m6 G" `
/ d- z5 V2 l" }Elapsed time is 28.331901 seconds.  
; {5 b  L( h! scontainers.Map数据结构,使用键值1到(10^7)进行访问, 在笔者的机器上,耗时只需1.323007秒, 结论是:如果有大量的数据,并且要频繁的进行查找操作,可以考虑使用containers.Map数据结构。(读者可以试试减少容器中元素的个数,观察一下什么情况下,数组的查找会更快一些。)% O' V8 e4 V& R! I9 ?; p& r* e4 I
% 映射表的查找  d" Y5 F2 K3 H/ {
tic
2 n- \/ J  _" _4 P5 n# c0 ^2 hfor i=1:100000,
7 _9 @) O, v' |: |    m(i);
; l4 ]: @$ A* _end; $ Y' l& S- x* z0 B! @+ v
toc6 R9 L" S7 [9 N0 k. `
% command line结果      
  I, r" ?$ f6 y6 a; W7 Y$ l8 m
  }, q; N! \7 M+ k8 |8 L7 `2 `1 y' D5 m4 P7 Z
5 ]0 g5 o  R0 X. d- m( ?: ]' s: |( w
6 y# U6 ^4 y2 @: ~5 C, f$ ~7 Y- \
Elapsed time is 1.323007 seconds.
; ^+ l0 @, s6 w! D3 y& |3 S谈到在MATLAB中使用高级数据结构,另一种常见的做法是直接使用Java和Python对象,这里顺便比较一下在MATLAB中使用Java的哈希表的效率。再笔者的机器上,耗时3.072889秒.3 @) v5 i# }. y( A4 j  h
% JAVA哈希表的查找
* V$ w- T' P/ ~0 t" S6 i" Is = java.util.HashSet();5 G  K3 Y9 x! L) J$ T5 d4 @, c
for i=1:100000, s.add(i); end   . H( X* Y" Q: R, a
tic;
& {2 w( k7 k" ]1 |% R8 {& Jfor i=1:100000, # I2 V% \7 Z+ d0 ~( [$ k  Z
    s.contains(i);
2 X. l- u3 F3 t3 ?4 z6 M" Gend;
: h% \' ^! |4 xtoc
, n, u: }7 z9 D; \4 ^8 y% command line结果      
1 L4 S$ y: X5 h3 v6 I" _9 C) b$ e2 v5 c0 \+ a1 m

3 D- c; r$ ~2 w' J1 U( M% C) ?3 Q: q) e& P( f
1 N1 D' U% Q, P

$ v0 r4 O/ X8 }  Z
8 U8 J9 W- W$ X* e; G( C1 r+ _Elapsed time is 3.072889 seconds.
! H2 _! w- ~' E8 l! A  M5 ]containers.Map的使用实例8 I8 ?9 F6 ?7 E7 A
用来盛放元素周期表
8 y' Z- g1 x( k# G工程计算中可以使用这种键-键值的例子有很多。比如物理化学计算中可以用映射表来盛放元素周期表中的内容:. y3 w# q& I; X) g. Y0 K
>> ptable = containers.Map;& U( Z9 o+ |4 F+ }
>> ptable('H')  = 1 ;
" W& z3 O/ `% @% p>> ptable('He') = 2 ;  
$ y9 `0 y- `; p8 G; k9 N4 ~# y4 l0 y0 w8 C其中键是原子符号,而键值是原子量。因为键值的类型不限,所以键值本身还可以是对象,比如Atom类对象,其中包涵更多有用的内容:; w6 y( \: p  C" }
% 类文件Atom.m, c$ u2 @3 o. V; C: P
classdef Atom < handle
8 N- u: T* {0 e  {  A2 j  properties; y3 w+ L/ {2 Y2 X2 u/ a
      atomicNumber
$ D7 J+ g5 ~  f      fullName
$ i5 C' V. C- g3 f3 M  end7 e. s) P" j8 X
  methods
! a0 s) e  |- C( b" A    function obj = Atom(num,name)
! e/ d1 A: z+ p+ ?7 t  f3 B       obj.atomicNumber = num ;7 p3 t! `# K( e: s' b  B* X, R  j- C
       obj.fullName = name- h& w6 m7 }2 x4 I7 A
    end
8 V& }" @0 d# N/ o$ w+ L; L  end
* u0 b( ~; k' E0 Lend  1 j8 O, \6 U( N6 k2 q% e# {
\end{Verbatim} 于是:
( _6 w% A0 \0 s, M& g6 M, Z% 键值可以是Atom对象                   " C( N6 n' A3 x- P2 Z
>> ptable('H') = Atom(1,'hydrogen');
3 M0 ?. v6 ^  @! {- n>> ptable('H') = Atom(2,'helium');  
& B8 F) W; D$ {/ L  P% z0 ~用来实现快速地检索; b, H) Z  O5 i! }: ^
这个问题来自ilovematlab一个网友的提问:5 Y. Z3 {; M; i$ [4 _5 G/ W# l
大家好,最近遇到一个问题,要构建20000次以上的三元素向量,且保证每次不重复构建,该向量元素值在2000―3000之间不定数,目前采用的方法是建立一历史档案矩阵A,构建一个存储一行,A大小行数持续增加。每次在构建出一新的向量时对该向量与历史档案矩阵每行进行对比,最终确定是否构建过,若没构建过,重新构建。算法显示该检查程序运算时间在执行20000次以上时,会超过200S的运行时间。想力争减小该时间,求方法。 多谢!
* k- G+ y& c" M$ h这位网友的问题不在于如何构建这些向量,而是如何保证每次不重复的构建。他一开始想出的解决方法是用矩阵A来存储历史档案,每建立一个新的向量,和该历史档案已有的内容做比较,如果在档案中没有存档,则构建。这样设计的问题是,如他所述,当A中存储的向量变多之后,每次检查该向量是否之前被创建过的时间加长。 这是一个典型的可以用映射表来解决的问题,把线性的复杂度转成常数的复杂度。他可以给每个向量设计一个独立无二的ID,比如直接转数字成id : num2str(vector)
1 J; ~# R9 X  j% 构建
9 W' i# H) f, I) G/ q  Smydictionary = containers.Map
7 ^7 E4 w  h8 _! F% Z6 W; N
) u' ]* G% g% e% n$ v% 插入数据# Q! i3 {0 F8 P0 p7 N2 G6 ^5 I
id = num2str(vector) ; % 比如vector = [1 2 3];0 k. l/ X: v" L& |" H
mydictionary('some_id') = vector ;  9 r) P& I2 @) x/ T% n8 H
之后的检索也是通过vector得到独一无二的ID,然后通过isKey来判断该键值是否已经被加入到MAP中去了。
& b0 o, K( e) b8 ^2 _% ~some_otherID = some_other_vector ;
4 n% x3 P0 d+ k# c7 {7 y' ^% 验证是否已经构建给过
; v* ^4 `4 G- L5 S8 mif mydictinary.isKey('some_otherID')
, V' N5 g+ V: N. x      % 做要做的工作" r0 ^$ x* ~. i& U- L
end  
  • TA的每日心情

    2019-11-29 15:37
  • 签到天数: 1 天

    [LV.1]初来乍到

    2#
    发表于 2020-11-30 16:47 | 只看该作者
    MATLAB映射表数据结构
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    关闭

    推荐内容上一条 /1 下一条

    EDA365公众号

    关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

    GMT+8, 2025-7-31 08:33 , Processed in 0.140625 second(s), 23 queries , Gzip On.

    深圳市墨知创新科技有限公司

    地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

    快速回复 返回顶部 返回列表