## Copyright (C) 2017 Guillaume Flandin ## ## This file is part of Octave. ## ## Octave is free software; you can redistribute it and/or modify it ## under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 3 of the License, or ## (at your option) any later version. ## your option) any later version. ## ## Octave is distributed in the hope that it will be useful, but ## WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Octave; see the file COPYING. If not, see ## . ## -*- texinfo -*- ## @deftypefn {} {@var{m} =} containers.Map () ## @deftypefnx {} {@var{m} =} containers.Map (@var{key}, @var{val}) ## @deftypefnx {} {@var{m} =} containers.Map (@var{key}, @var{val}, "UniformValues", @var{uv}) ## @deftypefnx {} {@var{m} =} containers.Map ("KeyType", @var{kt}, "ValueType", @var{vt})) ## ## Create an object of the containers.Map class, storing a list of key/value ## pairs. ## ## @var{key} is an array of unique keys for the map. The keys can be numeric ## scalars or strings. Numeric keys type can be one of "int32", "uint32", ## "single", "int64", "uint64", "double". Other numeric or logical keys will be ## converted to "double". String keys have to be entered as a cellstr, unless ## there is a single one where @var{key} can then be a char array. ## @var{val} is an array of values for the map with the same number of elements ## than @var{key}. ## ## @var{uv} is a logical value specifying whether the values of the map must be ## scalars of the same type or not. ## ## @var{kt} and @var{vt} are the types of, respectively, the keys and values of ## the map. Allowed values for @var{kt} are "char", "int32", "uint32", "single", ## "int64", "uint64", "double". Allowed values for @var{vt} are "char", "int32", ## "uint32", "single", "int64", "uint64", "double", "logical", "any". ## ## @var{m} is an object of the containers.Map class. ## @end deftypefn ## -*- texinfo -*- ## @deftypefn {} {} Map.Count () ## Return the number of key/value pairs in the map, as a uint64. ## @end deftypefn ## ## @deftypefn {} {} Map.KeyType () ## Return the key type. Possible values are listed above when describing input ## variable @var{kt}. ## @end deftypefn ## ## @deftypefn {} {} Map.ValueType () ## Return the value type. Possible values are listed above when describing ## input variable @var{vt}. ## @end deftypefn ## -*- texinfo -*- ## @deftypefn {} {@var{} =} Map.isKey (@var{keySet}) ## Return a logical array which is true when the elements of @var{keySet} are ## keys of the map and false otherwise. ## @var{keySet} is a cell array of keys. If a single key is being checked, it ## can be entered directly as a scalar value or a char vector. ## @end deftypefn ## ## @deftypefn {} {@var{key} =} Map.keys () ## Return the list of all the keys of the map as a cell vector. ## @end deftypefn ## ## @deftypefn {} {@var{l} =} Map.length () ## Return the number of key/value pairs in the map. ## @end deftypefn ## ## @deftypefn {} {} Map.remove (@var{keySet}) ## Remove the list of key/value pairs specified by a cell array of keys ## @var{keySet} from the map. @var{keySet}) can also be a scalar value or ## a char vector when specifying a single key. ## @end deftypefn ## ## @deftypefn {} {@var{l} =} Map.size (@var{n}) ## @deftypefnx {} {@var{sz} =} Map.size () ## @deftypefnx {} {@var{dim_1}, @dots{}, @var{dim_n} =} Map.size () ## If @var{n} is 1, return the number of key/value pairs in the map, otherwise ## return 1. ## Without input arguments, return vector [@var{l}, 1] where @var{l} is the ## number of key/value pairs in the map. ## With multiple outputs, return [@var{l}, @dots{}, 1]. ## @end deftypefn ## ## @deftypefn {} {@var{val} =} Map.values () ## @deftypefnx {} {@var{val} =} Map.values (@var{keySet}) ## Return the list of all the values stored in the map as a cell vector. ## If @var{keySet}, a cell array of keys is provided, the corresponding ## values will be returned. ## @end deftypefn classdef Map < handle properties (GetAccess = public, SetAccess = private) KeyType = "char"; ValueType = "any"; endproperties properties (Dependent, SetAccess = protected) Count = 0; endproperties properties (private) map = struct (); ## Class equivalence for keys: ## "char", "single" or "double" key_equiv = "char"; endproperties methods (Access = public) function this = Map (varargin) if (nargin == 0) ## empty object with "char" key type and "any" value type elseif ((nargin == 2) || ((nargin == 4) && strcmpi (varargin{3}, "UniformValues"))) ## get Map's keys key = varargin{1}; if (! iscell (key)) if (isnumeric (key) || islogical (key)) key = num2cell (key); else key = { key }; endif endif if (isempty (key)) error ("Empty keys are not allowed in a containers.Map."); endif key = reshape (key, [], 1); ## get Map's values val = varargin{2}; if (! iscell (val)) if ((isnumeric (val) || islogical (val)) && !isscalar (key)) val = num2cell (val); else val = { val }; endif endif val = reshape (val, [], 1); if (numel (key) != numel (val)) error ("The number of keys and values must be the same."); endif ## get KeyType c = unique (cellfun (@class, key, "UniformOutput", false)); if (numel (c) != 1) if all (cellfun (@(x) isnumeric (x) || islogical (x), key)) warning ("All keys will be converted to double."); c = { "double" }; else error ("The specified keys must all be of the same data type."); endif endif this.KeyType = char (c); if (! strcmp (this.KeyType, "char")) if (! all (cellfun (@(x) isscalar (x) && isreal (x), key))) error ("Keys have to be real scalar numeric values or char vectors."); endif endif if ismember (this.KeyType, {"logical", "int8", "uint8", "int16", "uint16"}) this.KeyType = "double"; endif ## get ValueType c = unique (cellfun (@class, val, "UniformOutput", false)); if ((numel (c) == 1) && (ismember (char (c), {"char", "logical", "double", "single", "int32", "uint32", "int64", "uint64"}))) this.ValueType = char (c); else this.ValueType = "any"; endif ## check UniformValues if (nargin == 4) UniformValues = varargin{4}; if ((isnumeric (UniformValues) || islogical (UniformValues)) && isscalar (UniformValues)) if (UniformValues) if (!strcmp (this.ValueType, "char") && !((numel (c) == 1) && all (cellfun (@isscalar, val)))) error ("When 'UniformValues' is true, all values should be scalars of the same data type, or char vectors."); endif else this.ValueType = "any"; endif else error ("'UniformValues' should be a logical scalar."); endif endif ## fill in the Map check_types (this); key = encode_keys (this, key); this.map = cell2struct (val, key); this = sort_keys (this); elseif (nargin == 4) for i = [1, 3] switch lower (varargin{i}) case "keytype" this.KeyType = varargin{i+1}; case "valuetype" this.ValueType = varargin{i+1}; otherwise error ("Missing parameter name 'KeyType' or 'ValueType'."); endswitch endfor check_types (this); else error("Incorrect number of inputs specified."); endif endfunction function keySet = keys (this) keySet = fieldnames (this.map)'; keySet = decode_keys (this, keySet); endfunction function valueSet = values (this, keySet) if (nargin == 1) valueSet = struct2cell (this.map)'; else if (! iscell (keySet)) error ("Input argument 'keySet' must be a cell."); endif keySet = encode_keys (this, keySet); valueSet = cell (size (keySet)); for i = 1:numel (valueSet) if (isfield (this.map, keySet{i})) valueSet{i} = this.map.(keySet{i}); else error ("The specified key does not exist in the container."); endif endfor endif endfunction function tf = isKey (this, keySet) if (! iscell (keySet)) if (isnumeric (keySet) || islogical (keySet)) keySet = num2cell (keySet); else keySet = { keySet }; endif endif tf = false (size (keySet)); in = cellfun (@(x) isnumeric (x) || islogical (x), keySet); if (strcmp (this.key_equiv ,"char")) in = !in; endif keySet = encode_keys (this, keySet(in)); tf(in) = isfield (this.map, keySet); endfunction function this = remove (this, keySet) if (! iscell (keySet)) if (isnumeric (keySet) || islogical (keySet)) keySet = num2cell (keySet); else keySet = { keySet }; endif endif in = cellfun (@(x) isnumeric (x) || islogical (x), keySet); if (strcmp (this.key_equiv ,"char")) in = !in; endif keySet = encode_keys (this, keySet(in)); for i = 1:numel (keySet) if (isfield (this.map, keySet{i})) this.map = rmfield (this.map, keySet{i}); endif endfor endfunction function varargout = size (this, n) c = length (this); if (nargin == 1) if (nargout <= 1) varargout = { [c 1] }; else varargout{1} = c; [varargout{2:nargout}] = deal (1); endif else if (n == 1) varargout = { c }; else varargout = { 1 }; endif endif endfunction function l = length (this) l = double (this.Count); endfunction function tf = isempty (this) tf = this.Count == 0; endfunction function count = get.Count (this) count = uint64 (numfields (this.map)); endfunction function sref = subsref (this, s) switch s(1).type case "." switch s(1).subs case "keys" sref = keys (this); case "values" sref = values (this) case "size" sref = size (this); case "length" sref = length (this); case "isempty" sref = isempty (this); case "Count" sref = this.Count; case "KeyType" sref = this.KeyType; case "ValueType" sref = this.ValueType; otherwise error (["The name '%s' is not an accessible property for " ... "an instance of class 'containers.Map'."], s(1).subs); endswitch case "()" key = s(1).subs{1}; if ((ischar (key) != strcmp (this.KeyType, "char")) || (!strcmp (this.KeyType, "char") && (!(isnumeric (key) || islogical (key)) || !isscalar (key)))) error ("Specified key type does not match the type expected for this container."); endif key = encode_keys (this, key); if (isfield (this.map, key)) sref = this.map.(key); else error ("The specified key does not exist in the container."); endif otherwise error ("Only '()' indexing is supported by a containers.Map."); endswitch if (numel (s) > 1) sref = subsref (sref, s(2:end)); endif endfunction function this = subsasgn (this, s, val) if (numel (s) > 1) error ("Only one level of indexing is supported by a containers.Map."); endif switch s(1).type case "." error ("Properties of an instance of class containers.Map are read-only."); case "()" key = s(1).subs{1}; if ((ischar (key) != strcmp (this.KeyType, "char")) || (!strcmp (this.KeyType, "char") && (!(isnumeric (key) || islogical (key)) || !isscalar (key)))) error ("Specified key type does not match the type expected for this container."); endif if (! strcmp (this.ValueType, "any")) if ((ischar (val) != strcmp (this.ValueType, "char")) || (!strcmp (this.ValueType, "char") && (!(isnumeric (val) || islogical (val)) || !isscalar (val)))) error ("Specified value type does not match the type expected for this container."); endif val = feval (this.ValueType, val); endif key = encode_keys (this, key); if (isfield (this.map, key)) this.map.(key) = val; else this.map.(key) = val; this = sort_keys (this); endif case "{}" error ("Only '()' indexing is supported for assigning values into a containers.Map."); endswitch endfunction function newobj = horzcat (varargin) error ("Horizontal concatenation is not supported by a containers.Map."); endfunction function newobj = vertcat (varargin) ## When concatenating maps, the data type of all values must be consistent ## with the ValueType of the leftmost map. keySet = cell (1, 0); for i = 1:numel (varargin) keySet = [keySet keys(varargin{i})]; endfor valueSet = cell (1, 0); for i = 1:numel (varargin) valueSet = [valueSet values(varargin{i})]; endfor newobj = containers.Map (keySet, valueSet); endfunction function display (this) printf ("containers.Map object with properties:\n\n"); printf ([" Count : %d\n" ... " KeyType : %s\n" ... " ValueType : %s\n\n"], this.Count, this.KeyType, this.ValueType); endfunction endmethods methods (Access = private) function keys = encode_keys (this, keys) if (iscellstr (keys) || ischar (keys)) return; endif cell_input = iscell (keys); if (! cell_input) keys = { keys }; endif keys = cellfun (@(x) feval (this.KeyType, x), keys, "UniformOutput", false); keys = cellfun (@(x) typecast (x, this.key_equiv), keys, "UniformOutput", false); keys = cellfun (@num2hex, keys, "UniformOutput", false); if (! cell_input) keys = char (keys); endif endfunction function keys = decode_keys (this, keys) if (! strcmp (this.key_equiv, "char")) keys = cellfun (@(x) hex2num (x, this.key_equiv), keys, "UniformOutput", false); keys = cellfun (@(x) typecast (x, this.KeyType), keys, "UniformOutput", false); endif endfunction function this = sort_keys (this) this.map = orderfields (this.map); endfunction function check_types (this) switch (this.KeyType) case {"char"} this.key_equiv = "char"; case {"single", "int32", "uint32"} this.key_equiv = "single"; case {"double", "int64", "uint64"} this.key_equiv = "double"; otherwise error ("Unsupported KeyType."); endswitch if (! ismember (this.ValueType, {"char","double", "single", "int32", "uint32", "int64", "uint64", "logical", "any"})) error ("Unsupported ValueType."); endif endfunction endmethods endclassdef %!test %! m = containers.Map (); %! assert (m.Count, uint64 (0)); %! assert (length (m), 0); %! assert (size (m, 1), 0); %! assert (size (m, 2), 1); %! assert (isempty (m)); %! assert (isempty (keys (m))); %! assert (isempty (values (m))); %! assert (isKey (m, "Octave"), false); %! assert (isKey (m, 42), false); %!test %! key = {"One", "Two", "Three", "Four"}; %! val = [1, 2, 3, 4]; %! m = containers.Map (key, val); %! assert (strcmp (m.KeyType, "char")); %! assert (strcmp (m.ValueType, "double")); %! assert (m.Count, uint64 (4)); %! assert (m("Two"), 2); %! m("Five") = 5; %! key2 = {"Six", "Seven", "Eight"}; %! val2 = [6, 7, 8]; %! m2 = containers.Map (key2, val2); %! m = [m; m2]; %! assert (m.Count, uint64 (8)); %! k = keys (m); %! assert (isempty (setdiff (k, [key, "Five", key2]))); %! v = values (m, {"Three", "Four", "Five"}); %! assert (v, {3, 4, 5}); %! remove (m, {"Three", "Four"}); %! k = keys (m); %! assert (numel (k), 6); %!test %! key = [1, 2, 3, 4]; %! val = {"One", "Two", "Three", "Four"}; %! m = containers.Map (key, val); %! assert (strcmp (m.KeyType, "double")); %! assert (strcmp (m.ValueType, "char")); %!test %! key = [2, 3, 4]; %! val = {eye(2), eye(3), eye(4)}; %! m = containers.Map (key, val); %! assert (m(3), eye(3)); %! assert (m(2)(2,2), 1); %!test %! m = containers.Map ("KeyType","char", "ValueType","int32"); %! assert (m.KeyType, "char"); %! assert (m.ValueType, "int32"); %! assert (m.Count, uint64 (0)); %! assert (isempty (m)); %!test %! key = {"one", "two", "three"}; %! val = {1, 2, 3}; %! m = containers.Map (key, val, "UniformValues",false); %! m("four") = "GNU"; %! assert (values (m), {"GNU", 1, 3, 2}); %!test %! key = [2, 3, 4]; %! val = {2, 3, 4}; %! types = {"int32", "uint32", "int64", "uint64", "single", "double"}; %! for i = 1:numel (types) %! k = feval (types{i}, key); %! m = containers.Map (k, val); %! assert (strcmp (m.KeyType, types{i})); %! assert (isa (keys(m){1}, types{i})); %! endfor %! assert ( all (isKey (m, keys (m)))); %!test %! key = [0, 1]; %! val = {1, 2}; %! types = {"logical", "int8", "uint8", "int16", "uint16"}; %! for i = 1:numel (types) %! k = feval (types{i}, key); %! m = containers.Map (k, val); %! assert (strcmp (m.KeyType, "double")); %! assert (isa (keys(m){1}, "double")); %! endfor %! assert ( all (isKey (m, keys (m)))); %!test %! key = {"a", "b"}; %! val = {struct(), struct()}; %! m = containers.Map (key, val); %! assert (strcmp (m.ValueType, "any")); %! m = containers.Map (key, val, "UniformValues", true); %! assert (strcmp (m.ValueType, "any"));