Main Content

Customize Parentheses Indexing for Mapping Class

This example shows how to customize parentheses indexing for a class. The MyMap class stores strings ("keys") that are associated with elements ("values") of a cell array. The class inherits from matlab.mixin.indexing.RedefinesParen to define custom behavior for indexing with parentheses. The class supports three customized indexing operations:

  • instanceName("keyName") returns the value associated with the key.

  • instanceName("keyName") = value adds a new key and associated value.

  • instanceName("keyName") = [] deletes the key and its associated value.

The full code for the class and its helper function, validateKeys, is available at the end of the example.

MyMap ClassExplanation
classdef MyMap... 
  < matlab.mixin.indexing.RedefinesParen

Define MyMap as a subclass of RedefinesParen. and implement its abstract methods

properties (Access = private)
   Keys (:,1) string
   Values (:,1) cell
end

The private properties Keys and Values are n-by-1 vectors.

methods (Static, Access = public)
   function obj = empty(varargin)
      if nargin == 0
         obj = MyMap(string.empty(0,1),cell.empty(0,1));
         return;
      end
            
      keys = string.empty(varargin{:});
      if ~all(size(keys) == [0, 1])
         error("MyMap:MustBeEmptyColumnVector",...
            "The only supported empty size is 0x1.");
      end
            
      obj = MyMap(keys,cell.empty(varargin{:}));
   end
end

Implementation of the static, abstract method empty, which creates a MyMap object with no keys or values.

methods (Access = public)
   function obj = MyMap(keys_in,values_in)
      if nargin == 0
         obj = MyMap.empty(0,1);
         return;
      end
            
      narginchk(2,2);
            
      if ~all(size(keys_in) == size(values_in))
         error("MyMap:InputSizesDoNotMatch",...
            "The sizes of the input keys and values must match.");
      end
            
      obj.Keys = keys_in;
      obj.Values = values_in;
   end
end

The constructor accepts the keys and values as input arguments and ensures the arrays are the same size.

   function keys = getKeys(obj)
      keys = obj.Keys;
   end
        
   function values = getValues(obj)
      values = obj.Values;
   end

Two public methods provide read access to the keys and values.

   function varargout = size(obj,varargin)
      [varargout{1:nargout}] = size(obj.Keys,varargin{:});
   end

   function C = cat(dim,varargin)
      error("MyMap:ConcatenationNotSupported",...
         "Concatenation is not supported.");
   end

   function lastKey = end(~,~,~)
       error("MyMap:EndNotSupported",...
          "Using end with MyMap objects is not supported.");"
   end

Implementation of the abstract methods size, cat, and end. In this example, cat and end are not supported.

methods (Access = private)
    function [keyExists,idx] = convertKeyToIndex(obj,keyCellArray)
       arguments
          obj
          keyCellArray cell {validateKeys}
       end

       requestedKey = keyCellArray{1};
       idx = find(contains(obj.Keys,requestedKey));
       keyExists = ~isempty(idx);
    end
end

The parenReference, parenAssign, parenDelete, and parenListLength methods use the convertKeyToIndex helper method. convertKeyToIndex uses validateKeys to ensure that keyCellArray contains only one key. (See the code for validateKeys after the end of the class.) convertKeyToIndex returns a logical that indicates whether or not the input key exists and, if it does, the index of the key.

methods (Access = protected)
   function varargout = parenReference(obj,indexOp)      
      [keyExists,idx] = convertKeyToIndex(obj,indexOp(1).Indices);

      if ~keyExists
         error("MyMap:KeyDoesNotExist",...
            "The requested key does not exist.");
      end

      if numel(indexOp) == 1
         nargoutchk(0,1);
         varargout{1} = obj.Values{idx};
      else
         % Additional operations
         [varargout{1:nargout}] = obj.Values{idx}.(indexOp(2:end));
      end
   end

Implementation of the abstract method parenReference, which handles reference indexing expressions of the form instanceName("keyName"). The method takes an IndexingOperation instance as input. That instance, indexOp, includes the indexing type (in this case, parentheses) and the index value from the expression being interpreted. parenReference passes the index in indexOp to convertKeyToIndex to verify that the key exists and, if so, return the index of that key idx. The method then returns the value that corresponds to the key. If there is more than one indexing operation in indexOp, the line labeled "Additional operations" forwards the handling of those operations to MATLAB®.

   function obj = parenAssign(obj,indexOp,varargin)
      indicesCell = indexOp(1).Indices;
      [keyExists,idx] = convertKeyToIndex(obj,indicesCell);

      if numel(indexOp) == 1
         value = varargin{1};
         if keyExists
            obj.Values{idx} = value;
         else
            obj.Keys(end+1) = indicesCell{1};
            obj.Values{end+1} = value;
         end
         return;
      end

      if ~keyExists
         error("MyMap:MultiLevelAssignKeyDoesNotExist", ...
            "Assignment failed because key %s does not exist",...
               indicesCell{1});
      end

      [obj.Values{idx}.(indexOp(2:end))] = varargin{:};
   end

Implementation of the abstract method parenAssign, which handles assignment indexing expressions of the form instanceName("keyName")= value. If the key referenced exists, then the method assigns the value from the right-hand side of the expression to that key. If the key does not exist and there is only one level of indexing, then the key and value are added to the list.

   function obj = parenDelete(obj,indexOp)
      [keyExists,idx] = convertKeyToIndex(obj,indexOp(1).Indices);
      if keyExists
         obj.Keys(idx) = [];
         obj.Values(idx) = [];
      else
         error("MyMap:DeleteNonExistentKey",...
            "Unable to perform deletion. The key %s does not exist.",...
            indexOp(1).Indices{1});
      end
   end

Implementation of the abstract method parenDelete, which handles deletion indexing expressions of the form instanceName("keyName") = []. If the key referenced exists, then the method deletes the key and its associated value. If the key does not exist, then the method issues an error.

  function n = parenListLength(obj,indexOp,indexingContext)
    [keyExists,idx] = convertKeyToIndex(obj,indexOp(1).Indices);
    if ~keyExists
      if indexingContext == matlab.indexing.IndexingContext.Assignment
         error("MyMap:MultiLevelAssignKeyDoesNotExist", ...
            "Unable to perform assignment. Key %s does not exist",...
               indexOp(1).Indices{1});
      end
      error("MyMap:KeyDoesNotExist",...
         "The requested key does not exist.");
    end   
    n = listLength(obj.Values{idx},indexOp(2:end),indexingContext);
  end
end

Implementation of the abstract method parenListLength, which determines the number of values to return from parentheses indexing expressions. The method takes an instance of matlab.indexing.IndexingContext as input to determine whether the reference is used in a statement, as a list of arguments to a function, or in an assignment operation.

 Expand for Class and Helper Function Code

Save the code for MyMap and validateKeys in your MATLAB path. Create an instance of MyMap with an initial list of three keys and values.

map = MyMap(["apple","cherry","orange"],{1,3,15});

Use the map("keyName") syntax to return the value corresponding to a specific key.

map("cherry")
ans =

     3

Use the map("keyName") = value to add a new key to the array.

map("banana") = 2;
map("banana")
ans =

     2

Use the map("keyName") = [] to delete a key and its associated value from the array. Confirm the key is no longer in the array.

map("orange") = [];
map("orange")
Error using MyMap/parenReference (line 88)
The requested key does not exist.

See Also

|