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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

9 N! i7 `/ A5 b0 j3 }' Z( F- f) C目录:! b( D! u( n$ f: Q+ J
* Y, r% V, u3 z" n9 G. q; N
containers.Map简介
; x6 ~# \! [1 N, I; W数组,元胞和结构体的局限性0 n+ Z1 W: O' O# `" q
什么是containers.Map
) I) U( S) F+ f- ^5 E# C0 R- }containers.Map的属性和成员方法
" k( ?8 Z7 Y9 icontainers.Map的特点- R/ k. X: V; I4 O
containers.Map可以不断地扩张不需预分配
6 K- R' ?* Q; o6 t/ d4 q4 Q) {4 xcontainers.Map可以作为参数在函数内部直接修改4 Y3 i7 p$ @1 J7 a7 W& k
containers.Map可以增强程序的可读性- y3 r3 q8 T- p4 B2 ^5 P6 F
containers.Map提供对数据的快速查找- ]4 t9 X% z0 d3 N
containers.Map的使用实例& y; Z$ E* ^5 K' Q% W6 s" D
用来盛放元素周期表! v. j1 }7 n% N% |* u
用来实现快速地检索  y# W7 n9 k; W; z6 R: B! n
MATLAB常用基本数据类型有:整型,浮点型,字符型,函数句柄,元胞数组和结构体数组。除了这些基本数据类型,MATLAB还有很多其它的数据类型不为人熟悉,这些数据类型在编程中也非常有用。MATLAB高级数据类型系列旨在向大家介绍它们:比如 containers.Map,tables,enumeration和time series等等,它们为什么有用,用来解决什么问题,并且怎样在科学工程计算使用。本篇首先介绍 containers.Map 数据类型。
* O: R- h' q1 p! U. m' pcontainers.Map简介
: V$ O, P0 Z. a$ @! K: |MATLAB中最具代表性的高级数据类型是containers.Map,我们可以把它叫做映射表。它和函数映射有些类似,比如函数映射的定义是:
: e, T+ Y& n2 f+ UF(x) = Y" B( p6 n: y: c3 @
针对每一个X,都有一个唯一的Y与之对应,反之不一定。如图Fig.1所示。和函数映射相似,映射表用来形容键(Key)和键值(Key Value)之间的一一对应关系。每个Key都是独一无二的,且只能对一个Key Value。如图Fig.2所示。
; h" I- e: g$ }& ?4 Y/ `- WFig.1 函数映射关系1 t( _( L4 O- Z8 F- U' A
Fig.2 containers.Map类的映射示意图
! a( [9 O: K& u$ P3 P# q: e% ]数组,元胞和结构体的局限性3 c! l' x. b* _- @; T5 o$ @+ Z
开始我们先介绍一下数组,元胞数组和结构体的局限性,为什么有的时候这些基本的数据类型无法满足程序的要求,换句话说,我们为什么需要 containers.Map 数据结构。假设要用MATLAB来记录电话号码簿中数据,比如表Table.1所示:
' |; c4 b! j7 S9 h. [! ]6 @Table.1 电话号码簿
2 I3 K4 W3 u% ]4 _% o6 W4 N6 r. K" ~* L
姓名        电话号码; i8 [' D% J) }  }5 |
Abby        5086470001
( Y# a* w* z5 l9 pBob        5086470002
. {) \( u- k( e, [) g+ x7 yCharlie        5086470003 先讨论数组,因为电话号码簿中既含有数字,又含有字符串,而数组中只能存放Double类型的数据,所以没有办法用数组直接记录电话号码薄中的内容。 再试试元胞数组,我们在第1行预先声明一个 3 X 3 的元胞数组,然后在2-4行按顺序填放号码簿的内容。' p! d2 t( M( v
% 元胞数组初始化
! Q' d" S% M7 |# zaddressBook    = cell(3,1);    % 预分配大小是MATLAB编程的好习惯
  n* N$ K" |1 D: J1 c, h7 ]+ e5 JaddressBook{1} = {'Abby',     '5086470001'};
6 t' z6 B7 z5 E7 VaddressBook{2} = {'Bob' ,     '5086470002'};
1 l# `8 g; d8 y5 B) GaddressBook{3} = {'Charlie',  '5086470003'};      
1 B& G7 V! G' p; ~; Z7 D需要的时候,可以通过for循环访问其中的内容,比如:1 M3 V! x% k- i  U6 v5 t
for iter = 1:length(addressBook)         % 元胞数组的遍历1 `3 k* r/ p0 X3 R6 K7 m4 k: U
   addressBook{iter}               % 通过Index访问元胞中的内容  ^, ~. M  k- ~- {
end4 B! Q9 N0 W0 n+ J! }
但是按照顺序遍历电话号码簿没有什么实际用处,号码簿的主要功能应该是提供查找的功能才是。比如要想查询Charlie的电话号码,我们希望程序最好可以写成如下这样:, r4 b' v/ f% Q8 y4 S2 L2 _
CharlieNum = addressBook{'Charlie'}     % 提示:这是错误的写法
8 ]; x! Z7 U+ z% V# h或者
# z. j* f1 U$ ~7 kCharlieNum = addressBook.Charlie        % 提示:这是错误的写法8 A4 v4 Z2 a- }6 ?% @
但是元胞数组的值只能通过Index去访问内容,不支持如上的访问方式。所以为了找到Charlie的电话号码,程序不得不遍历元胞中的所有内容,取出每一个元胞元素的第一列的字符串做比较,如果名字等于Charlie,则输出电话号码:. K, m1 P3 z' V! ~7 h& g6 ]
% 使用for循环查找
9 c4 T! M6 C8 q/ b# m# m: m1 G! n8 rfor iter = 1:length(addressBook)
5 r  d+ c/ D4 D1 w! R, [. d   if strcmp(addressBook{iter}{1},'Charlie')
2 ^5 j6 u1 I& O( c; \6 ]4 J# J        addressBook{iter}{2}              % 如找到则输出电话号码
! z9 M8 {& v' ~" P! \8 P- d/ Y      break;3 n- X5 N( }4 f8 i9 S  H5 ]& ?+ O7 b
   end
# S8 }- ]" Q$ b8 _end0 g# p  n+ F# m% u& \( ?
当然还有其他的方式来盛放电话号码簿,比如把电话和名字分别存到到两个元胞数组中去
& P6 Q* v- Z6 i% Mnames   = {'Abby','Bob','Charlie'};                 % 用元胞放号码9 r# d- h( A" M3 f) M. C& u
numbers = {'5086470001','5086470002','5086470001'}; % 用元胞放名字: }# E) E( g6 X% I
ind = find(strcmp(names,'Charlie'));  % strcmp接受向量的输入 返回Logical数组 & i4 E4 Y* i! t1 m
                                      % find紧接着找出逻辑数组中非零元素的Index
* z/ ?' X- M3 }9 j) e$ P! e0 N- }numbers{ind}  7 f/ c& `( H7 {! x
其中第3行strcmp接受元胞作为输入,在其中寻找Charlie,find函数将返回Charlie所在的位置,这样的方式比使用for循环要快,但无一例外的是,两种方法都要从头开始遍历一个数组,终究来说是慢的。 除查找性能慢外,使用元胞盛放电话号码簿类型的数据还有其它缺点:
" H0 p0 v6 ^: A4 A无法方便的验证重复数据。电话号码簿要求每一个人的名字都是独一无二的,所以在数据录入的时候要防止姓名的重复,但是我们没有其它办法知道某名字是否已经被使用过了,除非在每次输入的时候都对整个元胞里的内容做遍历比较。
) k1 b' |' Y, ]" F无法方便地添加内容。如果电话号码簿中的记录需要不断地增长,但是我们没有办法在一开始就估计出其大概的数量,于是无法有效的预先分配内存,所以添加数据时,一旦超过预先分配的量,MATLAB就要重新分配内存了。
( K: \* p7 _2 ]* P  @' t- w% k) Z( Z无法方便地删除内容。如果我们要从元胞中去掉某一记录,可以找到该记录,并把该位置的元胞内容置空,但这并不会自动减小元胞数组的长度,如果 这样的删减操作多了,元胞中会留下很多没有利用的空余位置。! S; t& w: U3 R& s& `
不方便作为函数的参数,具体原因见struct的局限性." C1 m# F& A! O. F" N$ j& Y7 K
最后我们再尝试一下用结构体盛放电话号码簿数据类型,struct的赋值很简单,比如可以直接赋值:* t4 f9 z; H$ ?  b+ M( F
% 赋值方法14 y7 S* g- Z: D6 }$ B5 l( D
addressBook.Abby   = '5086470001';
% F, u: h: i9 c  k2 raddressBook.Bob  = '5086470002';( }  x9 z3 ?+ h( w% B1 A! U1 b; n
addressBook.Charlie  = '5086470003';0 l9 u( e3 n# h# k8 t" E: X9 b
或者:$ G, H) z  @: g  }4 N4 i: `
% 赋值方法2& B6 m7 ~9 ^) E. R/ O
addressBook = struct('Abby','5086470001','Bob','5086470002','Charlie','5086470003')
) j5 M- W& u7 Q7 q  |# G方法1和方法2是等价的。 struct数据类型的查找很方便,比如要查询Charlie的电话号码,直接访问struct中的同名的field即可以了。' s5 D' J7 f( K+ F
num = addressBook.Charlie  . |0 L" [, k/ k& U* Q; ~
如果要查询的人名是一个变量, 我们可以使用getfield函数:
9 U/ F- h9 X9 c4 c& d) Lnum = getfield(addressBook,name)  % 其中name是变量   8 }; r. e" ~- @4 j
struct盛放电话号码簿类型的数据,查询起来确实比元胞进步了不少,但还是有些不方便的地方。 因为struct的field的名字要求必须是以字母开头,这是一个很大的局限,并不是所有的类似电话号码簿的结构都可以用人名做索引,比如账户号码簿,股票代码等等,他们通常是用数字开头的,比如图Table.2中的数据:4 g2 J9 b- ^- x; T- \$ l
Table.2 深证股票代码
& f: o/ [8 v% `) K" o$ z" o; _( w
( m) S0 p( ?* J" P* D: k* V3 D8 s7 v7 M股票代码        股票名称' x- C3 q7 p( J8 {
000001        深发展
3 C: x0 p3 K7 B2 R000002        万科; i3 A5 `9 e4 @
000004        ST国农 如果我们要求通过股票的代码来查找股票的具体信息,struct无法简单的直接做到。 使用struct的另一个不方便之处在于,当把struct当做函数参数,并且在函数内部需要对该struct做一定的修改时,为了要把修改后的结果返回,我们需要对原来的struct做完全的拷贝,显然如果struct中的数据很多的话,这样做是低效的。比如下面的函数中,addressBook被当做函数的参数传入,在函数中添加了一个新的field, 为了返回更新后的结构,函数内部必须重新构造一个新的struct,也就是s返回给该函数的调用者。, Y/ [/ X7 `, m1 E
% 把struct当做函数的参数    $ p' {9 m- o% m, c8 Z2 [- `
function s = modifystruct(s)
# [- @7 W$ Y) e; Q    s.Dave =  '5086470004';( w: H1 R. s4 k& r$ u
end( `+ L% V  r/ Y4 g5 K1 w3 q' Z
用containers.Map来记录电话号码簿
# ^7 b. j1 k! ^上面一节我们介绍了数组,元胞数组和结构体在模拟电话号码簿这种数据结构时的局限性,这节我们来看怎么用 containers.Map 来盛放电话号码簿中的内容:
  f9 n3 Z9 I( I4 A6 O' w  U; QaddressMap = containers.Map;             % 首先声明一个映射表对象变量
# x  I; u0 g/ ~" z4 x$ t+ G; yaddressMap('Abby')    = '5086470001';+ L: ?1 u. F' e( [
addressMap('Bob')     = '5086470002';
. w6 y* X# O+ {) s- l/ NaddressMap('Charlie') = '5086470003';  
4 L7 t9 Z4 R0 \6 H% c1 ]5 t1 I第一行我们声明一个containers.Map的变量(其实是containers.Map类的对象)叫做addressMap,2,3,4行通过提供Key Key Value的方式来给对象赋值,其中单引号内部的值叫做键(Key),等式右边的叫做键值(Key Value)。通过这个结构,我们在MATLAB内存中,建立起了如图Fig.3的映射关系数据结构。
1 J: a8 z8 i# V! d5 e7 aFig.3 电话号码簿映射表5 f! |( i: }9 z) J$ P2 U0 M
Fig.4 Map类的UML 查找addressMap对象中的内容极其方便,比如查找我们想知道Charlie的电话号码,只需:7 K% m) G6 k* }2 w
% 查找  
1 C* d2 n6 i: x. F5 p* Enum  = addressMap('Charlie')  ! ^9 o5 ~- w9 ~
如果我们要修改Charlie的电话号码,只需 :
$ J. N- h' w  q5 {, a; V1 U/ s% 赋值
2 g: Q% S. _7 M7 H# Y- |- h( PaddressMap('Charlie') = newNum;  $ {, g, E! t+ Z9 v* E! X, z" C
什么是containers.Map
4 F! z- L% E- y% n, Q( hcontainers.Map是一个MATLAB的内置类(类是面向对象编程中的一个基本概念,在这里可以把类暂时理解成一种数据类型),所谓内置,就是MATLAB内部实现的,通常这种类的性能更加的优秀。containers.Map其中containers是Package的名称,Map是该Package中的一个类,Map是它的类名。用UML(Unified Modeling Language)的语法把该类表示出来,它的属性包括Count, KeyType,ValueType。它的常用方法包括keys,values,isKey,remove。如图Fig.4所示。8 |: s9 r- `0 @
containers.Map的属性和成员方法
3 U# }; r1 I: z* _2 [这节我们介绍containers.Map的属性和成员方法,假设我们这样初始化一个containers.Map对象:" E; v* n7 k5 b1 R% Q
% 初始化一个Map对象
& P! L' u: N4 e/ ^2 N1 V, `0 JaddressMap = containers.Map;  O7 Q% x" Y$ m
addressMap('Abby')    = '5086470001';
3 K3 T. ~" ?5 k) A0 ^% V/ QaddressMap('Bob')     = '5086470002';
7 y; G3 ^" {5 V6 U0 @& |! K8 F5 yaddressMap('Charlie') = '5086470003';
) O8 s$ ?/ h& y6 ^6 O在命令行中键入该对象的名称,MATLAB会显示该对象属性的基本信息:) _/ M4 s8 \4 W( n+ E3 U) L
>> addressMap: ]) J1 l; i! r- u- }
addressMap =
( J4 Z$ ^( _$ \* A$ n; W  Map with properties:
: i4 u7 B2 G7 _$ u0 }        Count: 3          % MAP 中映射对的数目
: `9 d/ y8 e) _0 l2 g2 W9 P      KeyType: char       % MAP 中键的类型
' e' E1 K- |2 H    ValueType: any        % MAP 中键值的类型   9 N, I: H: d% Y4 l/ @
其中Count 表示Map对象中映射对的数目。按照规定,键的类型一定要是字符类型,不能是其它数据类型,而键值的类型可以是MATLAB中的任意类型:数组,元胞,结构体,MATLAB对象,甚至JAVA对象等等。因为键值的类型可以是任何MATLAB类型,所以 containers.Map 是MATLAB中极其灵活的数据类型。 成员方法keys 用来返回对象中所有的键:
; q+ v- J6 b9 _2 l' R>> addressMap.keys- d5 c- E1 O8 @
ans =) c2 G0 g' j+ ^# v* w8 D. B
    'Charlie'    'Abby'    'Bob'  1 q, U$ W! ~; a( t7 o
成员方法values用来返回对象中的所有键值
+ m$ b6 V* Y6 _: e' v8 c>> addressMap.values
, R# a& e! _8 ~, ]4 h0 Fans =
! g! d6 p* C4 ~8 T% m5 [' \    '5086470003'    '5086470001'    '5086470002'  + q, }" }) J" o3 q! i* c
remove用来移除对象中的一个键-键值对,经过下面的操作之后,该对象中的Count的值变成了2。
9 h" ~$ S* |: P1 l! d>> addressMap.remove('Charlie'), u  O9 X3 ]9 K4 @9 w
ans = - w7 @0 y- n) T5 Y$ C
  Map with properties:- @* C& q+ C$ Q# s3 L( b
        Count: 2          % 映射对数目减少) _$ h6 y: {, u
      KeyType: char' ]) U! d" \2 o2 K: U9 Z
    ValueType: any2 n* r* r5 ?! u$ P, d# C
  , I' c! }* L0 I' Z4 x) Q
isKey成员方法用来判断一个map对象中是否已经含有一个键值,比如:4 W) [* e" o4 f. ~  Z" |8 f
>> addressMap.isKey('Abby')  {' Q7 e: P2 P# A2 N& x
ans =
. N+ [7 L/ Z& u, B     1) d' l( p% ]# z2 u+ z+ B  p
>> addressMap.isKey('abby')    % 键是大小写敏感的
5 ]' N4 Q' i" V& W. Vans =" L; H  p2 d# Q
     0  * k7 k, v9 b; c8 X9 J  a
isKey的功能是查询,类似之前提到的find和strcmp函数合起来使用的例子。
' O6 ~( ^( M  c4 [1 K- tcontainers.Map的特点
: \  O" y" Z5 A  Y6 Ncontainers.Map可以不断地扩张不需预分配3 v" W# b8 \# g2 W, L* `
使用数组和元胞数组作为数据容器,通常要预先分配容器的大小。否则每次扩充容器,MATLAB都会重新分配内存,并且拷贝原来数组中的数据到新分配的内存中去。映射表是一个可以灵活扩充的容器,并且不需要预分配,每次往里面添加内容不会引起MATLAB重新分配内存。 我们可以通过提供两个元胞来构造映射表对象,比如下面我们构造一个机票簿数据结构:1 }4 f. h% I8 E% }( E6 U% F( Q
% 映射表的初始化方法1
' g, v  f3 E8 t" E: hticketMap = containers.Map( {'2R175', 'B7398', 'A479GY'}, ...
7 K2 p2 T/ C, p& ^9 S, k                            {'Abby', 'Bob, 'Charlie'});
5 T) x/ k# g2 f1 o  3 l3 ?, g  n7 c4 M
也可以在程序的一开始,先声明一个对象:
  C' m; y) G" }/ }% 映射表的初始化方法2  , m, z# F1 r1 s$ d4 K
>> ticketMap = containers.Map
' |1 {4 R2 K0 Y0 {然后在计算的过程中慢慢的向表中插入数据:7 b/ u: z! m+ S2 X" d5 [- ]
% 映射表的初始化方法2  : M/ i9 U, J! v8 x$ U1 G
>> ticketMap['2R175'] = 'Abby';
# E! o1 L0 r. B" M& T# U; K...
' m8 m. e- ?9 p: D& e>> ticketMap['A479GY'] = 'Charlie;  
  ~% |# W3 L3 qcontainers.Map可以作为参数在函数内部直接修改; C# l4 N- |) \( k6 S5 w$ @5 r
因为containers.Map是handle类(handle类的介绍参见《MATLAB面向对象编程-从入门到设计模式》第三章:MATLAB的句柄类和实值类),我们还可以方便的将这个对象传给任何MATLAB的函数,并且在函数内部直接修改对象内部的数据,并且不用把它当做返回值输出,比如:
& a, Z* U: _! |0 |4 t9 s. U>> modifyMap(ticketMap);  
) G: D' |( ?& a# r7 D1 T" S* JmodifyMap函数可以写成:$ G3 |% p: m9 l# \7 b8 B
function modifyMap(ticketMap)   % 该函数没有返回值5 O/ d' l3 n8 I) G
   .....) [8 h! G0 z- E* s1 R3 c2 a
   ticketMap(NewKey) = newID 8 t5 I; S0 _) Z
end  * T0 z+ U7 O% {) t
注意这里没有把修改的ticketMap当做返回值,在函数中我们直接修改了Map中的数据,这是MATLAB面向对象语言中handle类的特性。
/ C4 E2 s+ u. Y0 p# ~& Ucontainers.Map可以增强程序的可读性- N4 e$ Q' d% v& W8 I- Q2 f) A7 T
映射表内部可以存储各种类型的数据,并且给它们起一些有意义的名字。具体的例子见元素周期表的例子。 访问和修改这些数据的时候,可以直接使用这些有意义的名字,从而增加程序的可读性。而如果用元胞数组存储这些数据,在访问和修改这些数据的时候, 需要使用整数的Index,程序可读性不够好。" t0 ]  A+ T5 ?/ F# \$ y3 r$ z
containers.Map提供对数据的快速查找
) k  ?5 L$ ~9 t7 k9 g& k7 k; Q映射表查找的复杂度是常数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都相同。
- v! K. f$ d1 d: F7 D7 x5 ua = 1:1000000;& g/ }& I+ Y5 f. S/ }; F
m = containers.Map(1:1000000,ones(1,1000000));  
" K2 u7 X3 V$ R5 d数组数据结构,使用find函数依次查找从1到(10^7)的整数,在笔者的机器上,耗时28.331901秒. }& [  y1 n, a% d1 B" _+ J, I
% array的查找- x# g* y" N; D/ e$ p
tic
  Y, Q( i7 U3 s  F0 D7 mfor i=1:100000, ! `- d( S& |+ x1 }7 |$ l
    find(b==i);
0 j) W% D% b3 c; S! mend;
2 C; C* r9 e) _$ Mtoc  ) c, \7 B/ E" b8 g# J2 i& Y
% command line结果" @, M- \, S! {5 s2 g4 C. G

4 |3 X( ~8 F  I1 C8 o
% R2 g0 ~6 U/ M5 c1 l
: n; x; \7 D4 r' H2 T" K; I
+ b; M- z' {$ t  A& W5 JElapsed time is 28.331901 seconds.  
( B2 t, t) r# Z- g3 E4 D5 K- \containers.Map数据结构,使用键值1到(10^7)进行访问, 在笔者的机器上,耗时只需1.323007秒, 结论是:如果有大量的数据,并且要频繁的进行查找操作,可以考虑使用containers.Map数据结构。(读者可以试试减少容器中元素的个数,观察一下什么情况下,数组的查找会更快一些。)
$ U$ D* v7 C+ g* M' q/ J; ~% u% 映射表的查找
- _% n4 S4 q+ jtic
! w+ g3 U& X, e9 Qfor i=1:100000, 7 h) w8 {& c3 b
    m(i);
1 S/ H( m+ Y$ M' ]" ~end; , `0 Z4 J  j7 \
toc* Q3 x( N( y# [3 `& M* m1 w) \% d* ?
% command line结果      , @& C( X9 m, b( G7 |$ |, j

0 h2 ^9 N- X$ q" _* I) r' X0 ]
. A. K# h4 M$ H$ j' i7 v' v
! ]8 K$ M+ F8 g- p6 b7 ^* w( X% f
Elapsed time is 1.323007 seconds.
7 i$ d7 K2 G1 R, W6 u谈到在MATLAB中使用高级数据结构,另一种常见的做法是直接使用Java和Python对象,这里顺便比较一下在MATLAB中使用Java的哈希表的效率。再笔者的机器上,耗时3.072889秒.) l5 j" z& R8 m- Z* S6 P* n6 F
% JAVA哈希表的查找
4 w, O* b1 X4 [: D" B1 qs = java.util.HashSet();8 C. W- M# ^! ~( |# V
for i=1:100000, s.add(i); end   2 h5 W3 [2 W4 H) q$ j
tic;
+ Z( E  o* g. R- a5 ~for i=1:100000,
, X- k5 @" j) k# s; D    s.contains(i); / }% w, v/ L  i* d" _
end; 8 A: g' Y( m8 Y
toc
! ?7 k7 `8 L  K. V5 y0 t! x( V5 v% command line结果      
" i* x1 c' u3 j+ H0 e, w/ v
* Z  q0 Y5 S" x
7 A# D, X- ?% z9 _4 f0 t3 \( m& d
: R  }+ `* [+ F
: n6 L3 W& ~8 A. j
# ]3 v6 b) a# V- V9 B. @) ^, V
Elapsed time is 3.072889 seconds.4 m9 E( W& {5 q* g/ j( p" \; K$ G8 m
containers.Map的使用实例! M. Y  K/ [* N
用来盛放元素周期表  D- c; _5 O! P3 u
工程计算中可以使用这种键-键值的例子有很多。比如物理化学计算中可以用映射表来盛放元素周期表中的内容:; d9 M# _$ j8 ], Y6 S
>> ptable = containers.Map;, I) `% h& D: p/ I
>> ptable('H')  = 1 ;
  L- j9 E3 V1 }  h* q>> ptable('He') = 2 ;  # t, @1 \1 O, s/ O- a
其中键是原子符号,而键值是原子量。因为键值的类型不限,所以键值本身还可以是对象,比如Atom类对象,其中包涵更多有用的内容:
6 `8 u& S, N. f$ Z- b/ `' ?% 类文件Atom.m! c, W1 Q6 G$ C$ k2 L5 [( U' h
classdef Atom < handle
+ ^( n5 J" [+ ~0 r+ {  properties+ z- Q" P" W$ _' A8 `0 f
      atomicNumber; t6 p( C9 x/ }3 g8 E* x( w/ b
      fullName
! @- i4 t' Z& Y+ r1 S5 k+ \  k' c  end: ~) Q/ R8 R1 D
  methods
0 k; Q, j' m8 `. b+ X; `    function obj = Atom(num,name)
8 k- L& h! l$ V6 T/ x0 i5 q% N  F       obj.atomicNumber = num ;
0 x" L  T1 r- j3 f2 w) K; F* r1 K       obj.fullName = name
5 ?* A( A$ q7 p( W( p4 x    end
% M$ D3 ~5 p6 B$ S0 y' F  end0 s; Z* ^# t) c: a
end  
( \" u7 J% O, O\end{Verbatim} 于是:1 O- R" c) J3 r# |) z  t+ L# `" {
% 键值可以是Atom对象                  
; _- e4 W) J, n9 C% }" M>> ptable('H') = Atom(1,'hydrogen');1 {- S( D+ z0 F8 K! `
>> ptable('H') = Atom(2,'helium');  
3 _7 Y0 j1 g/ d) @9 V用来实现快速地检索" J- @9 n6 J/ T! h. o- {. D( }" R
这个问题来自ilovematlab一个网友的提问:& f# w4 B( h2 U# _5 v
大家好,最近遇到一个问题,要构建20000次以上的三元素向量,且保证每次不重复构建,该向量元素值在2000―3000之间不定数,目前采用的方法是建立一历史档案矩阵A,构建一个存储一行,A大小行数持续增加。每次在构建出一新的向量时对该向量与历史档案矩阵每行进行对比,最终确定是否构建过,若没构建过,重新构建。算法显示该检查程序运算时间在执行20000次以上时,会超过200S的运行时间。想力争减小该时间,求方法。 多谢!2 N! z, k( a* I7 ?- v$ ?
这位网友的问题不在于如何构建这些向量,而是如何保证每次不重复的构建。他一开始想出的解决方法是用矩阵A来存储历史档案,每建立一个新的向量,和该历史档案已有的内容做比较,如果在档案中没有存档,则构建。这样设计的问题是,如他所述,当A中存储的向量变多之后,每次检查该向量是否之前被创建过的时间加长。 这是一个典型的可以用映射表来解决的问题,把线性的复杂度转成常数的复杂度。他可以给每个向量设计一个独立无二的ID,比如直接转数字成id : num2str(vector)0 x, |% b; h/ @  g1 C
% 构建
$ n* E! l  C  C7 k0 Rmydictionary = containers.Map# A4 L$ i6 i& I9 q; f

5 g/ q/ M6 x$ x$ t2 {. `/ u/ L% 插入数据1 W, B: |7 {% H  E
id = num2str(vector) ; % 比如vector = [1 2 3];  `/ X. Z6 b, p
mydictionary('some_id') = vector ;  
  M' O0 k9 x; l3 O; W+ {6 R! H0 u  G之后的检索也是通过vector得到独一无二的ID,然后通过isKey来判断该键值是否已经被加入到MAP中去了。4 K6 |' Q* e3 T6 _+ u( T' v& p
some_otherID = some_other_vector ;
: C5 |( A, P) A5 B; G' Q4 D% 验证是否已经构建给过
5 E0 B% K4 n8 E; H* _) D9 xif mydictinary.isKey('some_otherID')  v2 ^& i1 v$ t2 V3 Z( H- q
      % 做要做的工作
$ f# ~7 l& g$ O4 J- pend  
  • 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-11-1 06:38 , Processed in 0.140625 second(s), 23 queries , Gzip On.

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

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

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