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

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

[复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x

  x" j5 J! Q  Y/ n4 J" O+ W目录:
6 J3 G" B" X9 k
5 W7 i+ k; [+ E( j$ @containers.Map简介
8 \* B7 P! v# }9 v. ~数组,元胞和结构体的局限性: u3 Q  k; P+ ]; s% [8 g$ ?% |) l
什么是containers.Map
# h" @. L# ^  z6 D3 ?% Pcontainers.Map的属性和成员方法
* B; z: }/ D0 k, ]# W6 R* Econtainers.Map的特点
) A& A7 U( F/ ^6 C7 U5 }containers.Map可以不断地扩张不需预分配- I; {9 ~" J& Q
containers.Map可以作为参数在函数内部直接修改
( m/ |* A3 i  Jcontainers.Map可以增强程序的可读性- |5 ]7 T) |; {  W) y/ c8 M6 f
containers.Map提供对数据的快速查找
4 v! w/ c0 n6 _% S& L( ocontainers.Map的使用实例9 p  A4 R# x$ g
用来盛放元素周期表
' Z5 T0 [* N. Q- N用来实现快速地检索
5 E6 ~7 ?6 J+ _1 G! T$ F2 FMATLAB常用基本数据类型有:整型,浮点型,字符型,函数句柄,元胞数组和结构体数组。除了这些基本数据类型,MATLAB还有很多其它的数据类型不为人熟悉,这些数据类型在编程中也非常有用。MATLAB高级数据类型系列旨在向大家介绍它们:比如 containers.Map,tables,enumeration和time series等等,它们为什么有用,用来解决什么问题,并且怎样在科学工程计算使用。本篇首先介绍 containers.Map 数据类型。& e' {1 K1 G5 R" F5 b5 M) S0 P
containers.Map简介4 `5 L  J2 ^+ {* ]+ N) r4 r8 U
MATLAB中最具代表性的高级数据类型是containers.Map,我们可以把它叫做映射表。它和函数映射有些类似,比如函数映射的定义是:5 Y3 c( K! Z$ M2 ?0 a, F0 n
F(x) = Y
7 ]+ |0 L# ^0 E* G) J针对每一个X,都有一个唯一的Y与之对应,反之不一定。如图Fig.1所示。和函数映射相似,映射表用来形容键(Key)和键值(Key Value)之间的一一对应关系。每个Key都是独一无二的,且只能对一个Key Value。如图Fig.2所示。
) N) v# t( x4 e+ W+ NFig.1 函数映射关系
8 l0 v/ n7 @0 m5 YFig.2 containers.Map类的映射示意图' f# I! G3 `) o1 E
数组,元胞和结构体的局限性* o( w% @- b* M8 I
开始我们先介绍一下数组,元胞数组和结构体的局限性,为什么有的时候这些基本的数据类型无法满足程序的要求,换句话说,我们为什么需要 containers.Map 数据结构。假设要用MATLAB来记录电话号码簿中数据,比如表Table.1所示:! A% O) L+ c6 d; |. P; W
Table.1 电话号码簿
9 Z, ~% @$ q: O; ~; e0 s( V' {5 H" {9 W; J- u% V& v
姓名        电话号码
6 G* M: R  M8 b, S# vAbby        50864700018 j& @6 ]  m& x; g
Bob        5086470002
; ?* Z; w3 c9 v+ I0 SCharlie        5086470003 先讨论数组,因为电话号码簿中既含有数字,又含有字符串,而数组中只能存放Double类型的数据,所以没有办法用数组直接记录电话号码薄中的内容。 再试试元胞数组,我们在第1行预先声明一个 3 X 3 的元胞数组,然后在2-4行按顺序填放号码簿的内容。5 F* ]+ X  z. S3 E" I
% 元胞数组初始化
" d1 l. S! m- N/ FaddressBook    = cell(3,1);    % 预分配大小是MATLAB编程的好习惯 + S$ ^- B, N1 W( t: \2 [% d
addressBook{1} = {'Abby',     '5086470001'};
) G! v- v/ i" L( {3 U, ?addressBook{2} = {'Bob' ,     '5086470002'};" b" G% A2 l' {3 g
addressBook{3} = {'Charlie',  '5086470003'};      8 p$ [2 c3 P3 R  K# `
需要的时候,可以通过for循环访问其中的内容,比如:2 @/ R1 p3 p# [8 S
for iter = 1:length(addressBook)         % 元胞数组的遍历
6 i( E! J8 y  J2 w# M4 ]   addressBook{iter}               % 通过Index访问元胞中的内容
/ P5 O/ r$ t. G' R' d5 L' Bend9 k- {7 ^# }' j3 D
但是按照顺序遍历电话号码簿没有什么实际用处,号码簿的主要功能应该是提供查找的功能才是。比如要想查询Charlie的电话号码,我们希望程序最好可以写成如下这样:
) i% N( o* i7 h6 q& S" E- Y7 ?CharlieNum = addressBook{'Charlie'}     % 提示:这是错误的写法2 g$ a2 l2 a1 g( Y
或者' P# G: m4 M' }. s  r
CharlieNum = addressBook.Charlie        % 提示:这是错误的写法# Q- V" f7 ]. s' q! V# A; x
但是元胞数组的值只能通过Index去访问内容,不支持如上的访问方式。所以为了找到Charlie的电话号码,程序不得不遍历元胞中的所有内容,取出每一个元胞元素的第一列的字符串做比较,如果名字等于Charlie,则输出电话号码:+ m" u; B9 p2 l% }( j
% 使用for循环查找. G. h: V$ o' X1 T
for iter = 1:length(addressBook)9 Z; Y. R. c( w% W
   if strcmp(addressBook{iter}{1},'Charlie'), o$ s( l. q3 h& u# s+ q
        addressBook{iter}{2}              % 如找到则输出电话号码
5 |7 O, g  ~" T) W/ k      break;
# F# E" n  I, }" Z   end  l; ]; d0 k+ X7 \
end
: ^( t/ d+ G6 F1 ^当然还有其他的方式来盛放电话号码簿,比如把电话和名字分别存到到两个元胞数组中去- I+ y5 j- r$ L
names   = {'Abby','Bob','Charlie'};                 % 用元胞放号码4 k7 L$ h* k2 H# ]+ q6 l
numbers = {'5086470001','5086470002','5086470001'}; % 用元胞放名字4 p1 v! g; M0 n, V- {8 i& q# }
ind = find(strcmp(names,'Charlie'));  % strcmp接受向量的输入 返回Logical数组
* ~/ w5 x; o  y8 E                                      % find紧接着找出逻辑数组中非零元素的Index
( ]7 }# p  [+ x5 M4 dnumbers{ind}  
+ R+ ^" h' w! k4 J其中第3行strcmp接受元胞作为输入,在其中寻找Charlie,find函数将返回Charlie所在的位置,这样的方式比使用for循环要快,但无一例外的是,两种方法都要从头开始遍历一个数组,终究来说是慢的。 除查找性能慢外,使用元胞盛放电话号码簿类型的数据还有其它缺点:3 x0 G$ t5 L: R
无法方便的验证重复数据。电话号码簿要求每一个人的名字都是独一无二的,所以在数据录入的时候要防止姓名的重复,但是我们没有其它办法知道某名字是否已经被使用过了,除非在每次输入的时候都对整个元胞里的内容做遍历比较。; {( J) f9 `- m- }
无法方便地添加内容。如果电话号码簿中的记录需要不断地增长,但是我们没有办法在一开始就估计出其大概的数量,于是无法有效的预先分配内存,所以添加数据时,一旦超过预先分配的量,MATLAB就要重新分配内存了。
  }) s* U3 N0 I无法方便地删除内容。如果我们要从元胞中去掉某一记录,可以找到该记录,并把该位置的元胞内容置空,但这并不会自动减小元胞数组的长度,如果 这样的删减操作多了,元胞中会留下很多没有利用的空余位置。8 b  n, r9 F' f) o2 H0 W
不方便作为函数的参数,具体原因见struct的局限性.
5 P( f' ~8 J5 M  ?最后我们再尝试一下用结构体盛放电话号码簿数据类型,struct的赋值很简单,比如可以直接赋值:. v7 z4 ]. L* R/ r4 ~2 C
% 赋值方法1( Y9 X8 \( C& Y- o
addressBook.Abby   = '5086470001';
! k2 U/ I0 [8 G' f1 w  j- {8 kaddressBook.Bob  = '5086470002';* Z+ I5 C( j; d- @7 v
addressBook.Charlie  = '5086470003';
% }- o# b  ]' n2 m' r2 L或者:
# T% A7 ]9 {" w- Z* P& x% 赋值方法2& U( @; A, ?, X/ Q! C4 R2 e
addressBook = struct('Abby','5086470001','Bob','5086470002','Charlie','5086470003')
4 t5 h' z% n  t" ^+ G' R方法1和方法2是等价的。 struct数据类型的查找很方便,比如要查询Charlie的电话号码,直接访问struct中的同名的field即可以了。6 Y0 X7 N& l1 j0 {# [2 h, d& E, F& R
num = addressBook.Charlie  9 |! o- h' ?1 F) k9 v
如果要查询的人名是一个变量, 我们可以使用getfield函数:0 Z! i/ J: Y+ P: ^
num = getfield(addressBook,name)  % 其中name是变量   ! W6 |+ l% l) z* g! l) X. E% h; X
struct盛放电话号码簿类型的数据,查询起来确实比元胞进步了不少,但还是有些不方便的地方。 因为struct的field的名字要求必须是以字母开头,这是一个很大的局限,并不是所有的类似电话号码簿的结构都可以用人名做索引,比如账户号码簿,股票代码等等,他们通常是用数字开头的,比如图Table.2中的数据:: {. a  C5 b) ?' n# M
Table.2 深证股票代码
+ k$ c  Q9 {3 W+ c2 _# r5 N. J: J9 Z9 ^' U+ Q7 B! D; |5 a- {+ A/ J$ E
股票代码        股票名称
+ b: i2 `* V  A000001        深发展$ Y1 F, R2 _! U$ [8 ?8 Y# m+ z5 E2 {
000002        万科
5 z( Z- K4 T: g# V: D000004        ST国农 如果我们要求通过股票的代码来查找股票的具体信息,struct无法简单的直接做到。 使用struct的另一个不方便之处在于,当把struct当做函数参数,并且在函数内部需要对该struct做一定的修改时,为了要把修改后的结果返回,我们需要对原来的struct做完全的拷贝,显然如果struct中的数据很多的话,这样做是低效的。比如下面的函数中,addressBook被当做函数的参数传入,在函数中添加了一个新的field, 为了返回更新后的结构,函数内部必须重新构造一个新的struct,也就是s返回给该函数的调用者。
5 m- K: q! v) E8 ]8 n: v% 把struct当做函数的参数   
# [) z" C# j# @/ s3 s* `function s = modifystruct(s)
- E0 `3 x/ ]* q6 O# X; Q    s.Dave =  '5086470004';
( f; b9 X2 S7 {3 Jend3 ?# s" {7 A+ ^+ k
用containers.Map来记录电话号码簿4 u- |7 e- Z# _, n/ v" b
上面一节我们介绍了数组,元胞数组和结构体在模拟电话号码簿这种数据结构时的局限性,这节我们来看怎么用 containers.Map 来盛放电话号码簿中的内容:
5 p  Z" L* d3 EaddressMap = containers.Map;             % 首先声明一个映射表对象变量
! b# R- Q* @% }, V8 WaddressMap('Abby')    = '5086470001';5 d9 {$ Y1 \; E, s
addressMap('Bob')     = '5086470002';
& P  r' L1 G7 l( O1 t: c0 BaddressMap('Charlie') = '5086470003';  
8 u" l  C. c8 a! f; ^第一行我们声明一个containers.Map的变量(其实是containers.Map类的对象)叫做addressMap,2,3,4行通过提供Key Key Value的方式来给对象赋值,其中单引号内部的值叫做键(Key),等式右边的叫做键值(Key Value)。通过这个结构,我们在MATLAB内存中,建立起了如图Fig.3的映射关系数据结构。
2 [4 ?' Z! }0 V& ~" ?Fig.3 电话号码簿映射表
& ~! |3 p/ {" e3 U5 H! J2 u8 {- SFig.4 Map类的UML 查找addressMap对象中的内容极其方便,比如查找我们想知道Charlie的电话号码,只需:
: h: l5 j9 T" ]3 Z" s& r) Y% 查找  7 ]* R1 ~( p4 B* m5 y6 a; I
num  = addressMap('Charlie')  
7 {1 N& n: a' n3 W% q如果我们要修改Charlie的电话号码,只需 :: d; G% r  ?: ]0 w! C
% 赋值
' n7 ^4 E+ `9 n: V8 ~addressMap('Charlie') = newNum;  
1 P+ J# X  [' z6 b% g4 y什么是containers.Map0 x; z7 E6 }5 @( F3 @* z7 X
containers.Map是一个MATLAB的内置类(类是面向对象编程中的一个基本概念,在这里可以把类暂时理解成一种数据类型),所谓内置,就是MATLAB内部实现的,通常这种类的性能更加的优秀。containers.Map其中containers是Package的名称,Map是该Package中的一个类,Map是它的类名。用UML(Unified Modeling Language)的语法把该类表示出来,它的属性包括Count, KeyType,ValueType。它的常用方法包括keys,values,isKey,remove。如图Fig.4所示。
3 W/ }8 u, L$ ~0 ccontainers.Map的属性和成员方法
! k8 ^0 q/ g8 v/ p. O这节我们介绍containers.Map的属性和成员方法,假设我们这样初始化一个containers.Map对象:$ D" r' P1 [8 o0 }
% 初始化一个Map对象
2 r+ e: R2 X9 y: |& o5 JaddressMap = containers.Map;
% }5 \% c+ S, O: R( NaddressMap('Abby')    = '5086470001';& z( q+ R* U) w) l4 m9 E: C" T% o
addressMap('Bob')     = '5086470002';2 W5 E3 A  O/ r$ {7 I
addressMap('Charlie') = '5086470003';7 {  W2 Y* _& L; Q) [
在命令行中键入该对象的名称,MATLAB会显示该对象属性的基本信息:
% W! T  T  {' E0 R; V& [# \. Y>> addressMap+ o" M' S( o, E6 J+ j
addressMap =9 l1 N* n0 a! e6 f" m: K: M) R) c% v4 e
  Map with properties:
- h6 K, `8 O" f/ }7 c/ I        Count: 3          % MAP 中映射对的数目
8 a$ Z$ c$ `5 A/ y      KeyType: char       % MAP 中键的类型 4 i7 q4 e  v6 f) e/ _5 J
    ValueType: any        % MAP 中键值的类型   
$ c" R1 R/ |9 x其中Count 表示Map对象中映射对的数目。按照规定,键的类型一定要是字符类型,不能是其它数据类型,而键值的类型可以是MATLAB中的任意类型:数组,元胞,结构体,MATLAB对象,甚至JAVA对象等等。因为键值的类型可以是任何MATLAB类型,所以 containers.Map 是MATLAB中极其灵活的数据类型。 成员方法keys 用来返回对象中所有的键:0 Q- C$ U: h6 G; W) S4 N8 u- Y3 c
>> addressMap.keys* G; P, W2 `6 `( Q' N9 e4 x) y
ans =9 k) Y# ~. ?6 Y1 ^- k3 `
    'Charlie'    'Abby'    'Bob'  
2 G7 g& R/ K( S  t, E成员方法values用来返回对象中的所有键值/ r8 U3 I. j( I
>> addressMap.values8 T) B, S0 k+ Z3 v1 ]4 H
ans =
+ h6 n  ~  P& C    '5086470003'    '5086470001'    '5086470002'  
+ L. `% E6 \5 `* o9 mremove用来移除对象中的一个键-键值对,经过下面的操作之后,该对象中的Count的值变成了2。
0 {, I1 @3 U/ x% Z1 z" Y/ _3 c( o>> addressMap.remove('Charlie')$ |. ?3 w( d8 o5 l4 z5 `  R
ans = 9 j3 Z) [' G: s
  Map with properties:
" `# T+ z" y. A        Count: 2          % 映射对数目减少0 L* j9 q  F* G: t) i
      KeyType: char. J- H0 i3 \) w
    ValueType: any7 V, B. c9 e) N0 [0 }  M4 `4 w
  % [9 D8 v5 S2 F( D5 N
isKey成员方法用来判断一个map对象中是否已经含有一个键值,比如:9 S. d  V" j# N% M
>> addressMap.isKey('Abby')4 [2 ~" b% J$ V6 P
ans =
1 G' O7 o" a; E) r7 q6 |     1$ g) z: @+ s& a
>> addressMap.isKey('abby')    % 键是大小写敏感的) K# _) A, q4 g5 S  v
ans =
* Z/ [4 p7 V1 V     0  
! m1 w. Q7 j) _- k4 LisKey的功能是查询,类似之前提到的find和strcmp函数合起来使用的例子。
) v' ~, f; M6 L# _$ N' |0 v0 r1 Icontainers.Map的特点# L: q9 |" @! |% w* z3 }5 X: E
containers.Map可以不断地扩张不需预分配
2 c3 A2 Y% n- r3 {' z1 I4 ]4 @9 C使用数组和元胞数组作为数据容器,通常要预先分配容器的大小。否则每次扩充容器,MATLAB都会重新分配内存,并且拷贝原来数组中的数据到新分配的内存中去。映射表是一个可以灵活扩充的容器,并且不需要预分配,每次往里面添加内容不会引起MATLAB重新分配内存。 我们可以通过提供两个元胞来构造映射表对象,比如下面我们构造一个机票簿数据结构:
. N+ {% T7 f0 m: V% 映射表的初始化方法1
/ y+ v+ F9 u+ QticketMap = containers.Map( {'2R175', 'B7398', 'A479GY'}, ...; m: |: f5 R0 V5 J
                            {'Abby', 'Bob, 'Charlie'});2 o7 Q" `  w2 l( ~
  5 w( |/ Q5 o6 L% e5 k: N. f3 Y3 X
也可以在程序的一开始,先声明一个对象:
$ y' j% {& k; q/ [! t% 映射表的初始化方法2  0 M- Z' a1 Q/ k  J( g/ Z2 m# |- A% F
>> ticketMap = containers.Map ( H1 A% e9 M1 V" q
然后在计算的过程中慢慢的向表中插入数据:$ M; c# M# t9 F' F: @6 u
% 映射表的初始化方法2  - Y% T( F3 H) J/ r; \' c" H0 a
>> ticketMap['2R175'] = 'Abby';7 w$ c; _( K7 V; N0 ~
...
" x+ l2 U( [  d/ |* D4 D>> ticketMap['A479GY'] = 'Charlie;  : b5 Y$ s+ ^1 G0 G4 y& l8 w3 v6 P4 _
containers.Map可以作为参数在函数内部直接修改
! b! W6 {, Q. \) `因为containers.Map是handle类(handle类的介绍参见《MATLAB面向对象编程-从入门到设计模式》第三章:MATLAB的句柄类和实值类),我们还可以方便的将这个对象传给任何MATLAB的函数,并且在函数内部直接修改对象内部的数据,并且不用把它当做返回值输出,比如:0 n) e- P' o- h% }6 ~
>> modifyMap(ticketMap);  0 p8 T, `4 g9 ~5 U0 t; n
modifyMap函数可以写成:
8 F5 w; Y; j3 E4 @5 ]function modifyMap(ticketMap)   % 该函数没有返回值) T7 }  z- [! X8 C+ L3 b4 z0 @
   .....
  X, S6 W  ^3 |2 ^. ]' e3 t   ticketMap(NewKey) = newID
/ O% L, v4 k3 Y) z* ~end  ! t3 ]. S2 N" q" Y# u. W
注意这里没有把修改的ticketMap当做返回值,在函数中我们直接修改了Map中的数据,这是MATLAB面向对象语言中handle类的特性。" \- U8 m' W4 R0 J
containers.Map可以增强程序的可读性! N1 [- U. R( U/ Z9 R8 q
映射表内部可以存储各种类型的数据,并且给它们起一些有意义的名字。具体的例子见元素周期表的例子。 访问和修改这些数据的时候,可以直接使用这些有意义的名字,从而增加程序的可读性。而如果用元胞数组存储这些数据,在访问和修改这些数据的时候, 需要使用整数的Index,程序可读性不够好。$ d( u/ N  N) N4 H6 ]7 z+ ^8 u! U* j
containers.Map提供对数据的快速查找) Q% @) n6 c: i
映射表查找的复杂度是常数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都相同。% D) k5 I9 z  A1 g5 H9 y; ?6 Y
a = 1:1000000;( U0 L# T) n$ }5 T9 o7 p
m = containers.Map(1:1000000,ones(1,1000000));  & v! Z4 J, i4 u9 Q
数组数据结构,使用find函数依次查找从1到(10^7)的整数,在笔者的机器上,耗时28.331901秒
# }3 w2 M. R& S9 G2 @* {% array的查找/ i  Y$ y  R5 {% L6 X2 B
tic
+ p- h2 B0 w9 E7 a) Efor i=1:100000, 3 z, h  e( M+ Z6 g2 {5 K# e2 e/ U" d
    find(b==i);/ l" }0 q% S7 _& E* {/ o4 _
end; * j- P  n) U4 j$ g* \  L8 C
toc  
( \- m/ ~2 Z( \# k, A2 a% command line结果7 ^. Y* }) k) v0 F# ?+ j$ s0 I( j

/ ]9 H" f# ?! M+ J1 o% c: I
; L; p, `- r- R. T) ?6 C4 X* T* U8 O! ~
' ^! c3 ^" T1 q" ?8 l
Elapsed time is 28.331901 seconds.  : J6 E+ r! h2 H) b
containers.Map数据结构,使用键值1到(10^7)进行访问, 在笔者的机器上,耗时只需1.323007秒, 结论是:如果有大量的数据,并且要频繁的进行查找操作,可以考虑使用containers.Map数据结构。(读者可以试试减少容器中元素的个数,观察一下什么情况下,数组的查找会更快一些。)
8 o: Y; S  x9 z% r/ Z& F% 映射表的查找' J7 R3 f  H# z- X0 G& I9 K
tic
6 a6 R  z# ~  J7 qfor i=1:100000,
( H) T! b; t$ H8 N6 S    m(i);8 @8 w9 Y. s6 v$ [3 g& h
end; , c2 I4 d; G: n. O! i, z
toc
  Y& d- U% Y& D5 S% command line结果      
5 b' p( L2 s4 T$ J5 ~) J) ]! v
0 q4 R8 u& i+ l0 c+ K. H8 r, |+ ^$ {% w- X

1 A5 d/ G1 w0 M( F
; m( w7 T+ H: J0 t  J; y* AElapsed time is 1.323007 seconds.
# Z5 A8 Q0 o! b: e$ P% C谈到在MATLAB中使用高级数据结构,另一种常见的做法是直接使用Java和Python对象,这里顺便比较一下在MATLAB中使用Java的哈希表的效率。再笔者的机器上,耗时3.072889秒.4 _- _/ |* L. p' F* O
% JAVA哈希表的查找9 u0 w0 c$ I+ }) P" {+ Z/ E; P
s = java.util.HashSet();) N1 r3 o% j9 J7 _
for i=1:100000, s.add(i); end   0 N1 d( k: x$ K- D
tic;
$ ?7 m) h8 \, a- `- ifor i=1:100000, 3 ]3 F* Z' W! U" Q% e/ U5 a
    s.contains(i);
6 O! Z9 {% M8 E+ x8 u+ i9 eend; # A/ U7 A% f/ M) d( d1 r- N( k. i
toc) N1 y# U) ]4 `7 L9 E: \
% command line结果      
) d% g$ Z# u- u7 @/ \0 \4 p" a* T$ F# h- ?3 o, \6 ~
! I4 L% K8 Z6 n. x. l
1 l0 @# g! c& ^+ i
; M. Z3 E! j, w) \0 A9 T* @  @6 c& K, Y
: ^5 T5 H7 d" h" \

' f1 `" w  ?6 D0 z0 yElapsed time is 3.072889 seconds.1 B7 s5 t; R8 t2 I4 q  d, L
containers.Map的使用实例6 {/ c% o! e$ \* c4 b3 T
用来盛放元素周期表! g7 Q$ [3 ]4 v! @
工程计算中可以使用这种键-键值的例子有很多。比如物理化学计算中可以用映射表来盛放元素周期表中的内容:
8 d! d( \6 t8 C; q>> ptable = containers.Map;3 t2 d' Y5 E  P$ b; i' N
>> ptable('H')  = 1 ;
4 q  ^, H# }: T) B>> ptable('He') = 2 ;  
; {; p4 `$ u* Y8 O% p" `- o其中键是原子符号,而键值是原子量。因为键值的类型不限,所以键值本身还可以是对象,比如Atom类对象,其中包涵更多有用的内容:
5 K, y5 }# w0 P' P2 O% 类文件Atom.m5 \$ Y# p8 H, j' s/ f8 X
classdef Atom < handle5 Z  D4 ?! `! y2 R& I
  properties
+ Y3 L( w( y$ e      atomicNumber
4 c5 w2 k) q; Z; T" |3 o      fullName
0 c' \4 c& Q$ u5 a. A9 Q+ [  end
1 F# W( \* }1 Y- \% `  methods( W, i  G  {9 e' V
    function obj = Atom(num,name)% _, y8 |5 o3 C6 o6 ~
       obj.atomicNumber = num ;3 u3 b* w: J5 l4 V& \$ w% R& x
       obj.fullName = name+ ]" Z+ z1 u% Q7 j9 a
    end* y6 S9 q, A- l0 E# L+ `3 H/ V
  end
% r: W8 u: N6 ]2 z6 f, qend  
  f( t+ Y+ e5 o0 M' t\end{Verbatim} 于是:2 L* N: D; \8 c) R; r3 e, P/ q
% 键值可以是Atom对象                   . E' E1 F  ?2 ?% K- A7 n, P, x
>> ptable('H') = Atom(1,'hydrogen');
% t' T1 r1 Q$ [0 f5 G. _6 q1 L- G>> ptable('H') = Atom(2,'helium');  
0 H* x, y. S! B, i2 p, R用来实现快速地检索* x. u5 I. Z5 y( i- S& {3 D
这个问题来自ilovematlab一个网友的提问:
4 Y$ ], C, j% i9 w" f, e& h3 e大家好,最近遇到一个问题,要构建20000次以上的三元素向量,且保证每次不重复构建,该向量元素值在2000―3000之间不定数,目前采用的方法是建立一历史档案矩阵A,构建一个存储一行,A大小行数持续增加。每次在构建出一新的向量时对该向量与历史档案矩阵每行进行对比,最终确定是否构建过,若没构建过,重新构建。算法显示该检查程序运算时间在执行20000次以上时,会超过200S的运行时间。想力争减小该时间,求方法。 多谢!+ {/ G0 g1 Q8 }% d0 A7 \
这位网友的问题不在于如何构建这些向量,而是如何保证每次不重复的构建。他一开始想出的解决方法是用矩阵A来存储历史档案,每建立一个新的向量,和该历史档案已有的内容做比较,如果在档案中没有存档,则构建。这样设计的问题是,如他所述,当A中存储的向量变多之后,每次检查该向量是否之前被创建过的时间加长。 这是一个典型的可以用映射表来解决的问题,把线性的复杂度转成常数的复杂度。他可以给每个向量设计一个独立无二的ID,比如直接转数字成id : num2str(vector)
1 N0 N: x% z* ?/ O7 N; ^/ }% 构建
' {1 Q! t2 i4 K6 u9 M; I! fmydictionary = containers.Map' l9 F/ z0 `9 M5 S# u( E
1 F! w- l! `2 H* Y+ A& J+ H! k
% 插入数据
* n8 t+ ~3 }8 l! K, j8 i3 Wid = num2str(vector) ; % 比如vector = [1 2 3];
! f' |9 e& h( X5 H+ P0 i; Amydictionary('some_id') = vector ;  
7 Q( A& R! M7 Q; \3 N3 y之后的检索也是通过vector得到独一无二的ID,然后通过isKey来判断该键值是否已经被加入到MAP中去了。! r7 ~+ k+ _) ^" D
some_otherID = some_other_vector ;
" ]0 g+ d; U! T% 验证是否已经构建给过& j& t2 \7 t2 A
if mydictinary.isKey('some_otherID')% y1 f& D& m/ ~* K0 [; [* ?! r# }4 Z
      % 做要做的工作! o2 K9 x" [: d% T+ I, z- z
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-10-6 19:13 , Processed in 0.140625 second(s), 24 queries , Gzip On.

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

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

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