Home > utils > compare_structs.m

compare_structs

PURPOSE ^

SYNOPSIS ^

function [structsAreEqual,firstViolation,violationReason] = compare_structs(struct1,struct2,varargin)

DESCRIPTION ^

 Compares two structs to see if they have identical fields and/or values

 struct1 and struct2 are the only required arguments. the rest of the
 arguments are tag,value pairs that specify how the structs will be
 compared.
 
 supported tags and values:
    'values' (true or false): specifies whether field values will be
       compared or not.
    'substruct' (true or false): specifies whether to check only
         whether struct1 is contained in struct2 (struct2 simply has
         more fields and values than struct1 but otherwise are
         equal). Otherwise the structs will be compared for an exact
         match.
    'types' (true or false): specifies whether or not to check the
         type (class) of the fields for a positive match. This can only
         be set to false if 'values' is also false. Otherwise, types
         will automatically be checked.
    'ignore_fieldnames': a cell array of strings that, if encountered
         as a fieldname, will be skipped, and will not be compared.
    'subsetIsOK': a cell array of strings identifying fieldnames for which
         a subset is of values is OK. Example: If the value of the
         fieldname 'HalfDecayTimes' in struct1 contains ([0.2 2]), the
         values of a matched fieldname in struct2 would be deemed equal if
         it contained the desired values and any others, e.g. [0.2 2 4 8].

 defaults if not specified are 'values',true and 'contained',false

  OUTPUTS
   structsAreEqual - 1 or 0, whether or not struct equality or
                     substruct is true

   firstViolation  - returns a cell array of fields (or subfields)
                     that resulted in the first violation of equality (or
                     substruct-ness). This isn't a complete list
                     and is only meant to aid in finding
                     inequalities. 

   violationReason - provides a concise descriptor of the type of
                     violation that occurred. This is short and simple to
                     allow for easy string matching if a more elaborate
                     report is desired in a higher level script.

 Copyright (c) 2007-2012 The Regents of the University of California
 All Rights Reserved.

 Author(s)
 2007 - Stefan Tomic, first version
 10/8/2008 - Stefan Tomic, extracted from check_anal_exist and added tag,value functionality
 12/30/2008 - Fred Barrett, added support for multi-dimensional cell arrays as field values
 02/24/2009 - Fred Barrett, switched cell checking to compare_cells.m,
   also added handling of struct arrays
 11/11/2009 - Fred Barrett, added 'ignore_fieldnames' option
 01Nov2011 - Petr Janata, added ability to specify those fieldnames for
             which specification of a subset of values is OK. 
 30Oct2012 - Improved handling of ignore_fieldnames; eliminated instances
             of strmatch()
 29Dec2014 - PJ - made field type mismatch non-fatal if both fields are
             empty

CROSS-REFERENCE INFORMATION ^

This function calls: This function is called by:

SOURCE CODE ^

0001 function [structsAreEqual,firstViolation,violationReason] = compare_structs(struct1,struct2,varargin)
0002 %
0003 % Compares two structs to see if they have identical fields and/or values
0004 %
0005 % struct1 and struct2 are the only required arguments. the rest of the
0006 % arguments are tag,value pairs that specify how the structs will be
0007 % compared.
0008 %
0009 % supported tags and values:
0010 %    'values' (true or false): specifies whether field values will be
0011 %       compared or not.
0012 %    'substruct' (true or false): specifies whether to check only
0013 %         whether struct1 is contained in struct2 (struct2 simply has
0014 %         more fields and values than struct1 but otherwise are
0015 %         equal). Otherwise the structs will be compared for an exact
0016 %         match.
0017 %    'types' (true or false): specifies whether or not to check the
0018 %         type (class) of the fields for a positive match. This can only
0019 %         be set to false if 'values' is also false. Otherwise, types
0020 %         will automatically be checked.
0021 %    'ignore_fieldnames': a cell array of strings that, if encountered
0022 %         as a fieldname, will be skipped, and will not be compared.
0023 %    'subsetIsOK': a cell array of strings identifying fieldnames for which
0024 %         a subset is of values is OK. Example: If the value of the
0025 %         fieldname 'HalfDecayTimes' in struct1 contains ([0.2 2]), the
0026 %         values of a matched fieldname in struct2 would be deemed equal if
0027 %         it contained the desired values and any others, e.g. [0.2 2 4 8].
0028 %
0029 % defaults if not specified are 'values',true and 'contained',false
0030 %
0031 %  OUTPUTS
0032 %   structsAreEqual - 1 or 0, whether or not struct equality or
0033 %                     substruct is true
0034 %
0035 %   firstViolation  - returns a cell array of fields (or subfields)
0036 %                     that resulted in the first violation of equality (or
0037 %                     substruct-ness). This isn't a complete list
0038 %                     and is only meant to aid in finding
0039 %                     inequalities.
0040 %
0041 %   violationReason - provides a concise descriptor of the type of
0042 %                     violation that occurred. This is short and simple to
0043 %                     allow for easy string matching if a more elaborate
0044 %                     report is desired in a higher level script.
0045 %
0046 % Copyright (c) 2007-2012 The Regents of the University of California
0047 % All Rights Reserved.
0048 %
0049 % Author(s)
0050 % 2007 - Stefan Tomic, first version
0051 % 10/8/2008 - Stefan Tomic, extracted from check_anal_exist and added tag,value functionality
0052 % 12/30/2008 - Fred Barrett, added support for multi-dimensional cell arrays as field values
0053 % 02/24/2009 - Fred Barrett, switched cell checking to compare_cells.m,
0054 %   also added handling of struct arrays
0055 % 11/11/2009 - Fred Barrett, added 'ignore_fieldnames' option
0056 % 01Nov2011 - Petr Janata, added ability to specify those fieldnames for
0057 %             which specification of a subset of values is OK.
0058 % 30Oct2012 - Improved handling of ignore_fieldnames; eliminated instances
0059 %             of strmatch()
0060 % 29Dec2014 - PJ - made field type mismatch non-fatal if both fields are
0061 %             empty
0062 
0063 numberTypes = {'int8','uint8','int16','uint16','int32','uint32','int64','uint64','double'};
0064 firstViolation = {};
0065 violationReason = '';
0066 VERBOSE = 0;
0067 
0068 if (nargin > 2)
0069   for iarg = 1:2:nargin-2
0070     switch(varargin{iarg})
0071      case 'values'
0072       checkValues = varargin{iarg+1};
0073      case 'substruct'
0074       checkSubstruct = varargin{iarg+1};
0075      case 'types'
0076       checkTypes = varargin{iarg+1};
0077      case 'ignore_fieldnames'
0078              ignore_fieldnames = varargin{iarg+1};
0079       case 'subsetIsOK'
0080         subsetIsOK = varargin{iarg+1};
0081       case 'verbose'
0082         VERBOSE = varargin{iarg+1};
0083     end
0084   end
0085 end
0086 
0087 if ~exist('checkValues','var'), checkValues = true; end
0088 if ~exist('checkSubstruct','var'), checkSubstruct = true; end
0089 if ~exist('checkTypes','var'), checkTypes = true; end
0090 if ~exist('ignore_fieldnames','var')
0091   ignore_fieldnames = {}; 
0092 elseif ischar(ignore_fieldnames)
0093   ignore_fieldnames = {ignore_fieldnames};
0094 end
0095 if ~exist('subsetIsOK','var'), subsetIsOK = {}; end
0096 
0097 struct1_fieldNames = fieldnames(struct1)';
0098 struct2_fieldNames = fieldnames(struct2)';
0099 
0100 struct1_nFields = length(struct1_fieldNames);
0101 struct2_nFields = length(struct2_fieldNames);
0102 
0103 fieldNames_intersect = intersect(struct1_fieldNames,struct2_fieldNames);
0104 intersect_nFields = length(fieldNames_intersect);
0105 
0106 %the structs must have all fieldnames in common
0107 if(checkSubstruct)
0108   if(struct1_nFields ~= intersect_nFields)
0109     fieldNames_diff = setdiff(struct1_fieldNames,struct2_fieldNames);
0110     for k=1:length(fieldNames_diff)
0111       if isempty(strmatch(fieldNames_diff{k},ignore_fieldnames))
0112         firstViolation = fieldNames_diff{k};
0113         violationReason = 'missing_fields_struct2';
0114         structsAreEqual = 0;
0115         return
0116       end
0117     end
0118   end
0119 else
0120   if(struct1_nFields ~= intersect_nFields || struct2_nFields ~= intersect_nFields)
0121     fieldNames_xor = setxor(struct1_fieldNames,struct2_fieldNames);
0122     for k=1:length(fieldNames_xor)
0123       if isempty(strmatch(fieldNames_xor{k},ignore_fieldnames))
0124         firstViolation = fieldNames_xor{k};
0125         violationReason = 'missing_fields_either_struct';
0126         structsAreEqual = 0;
0127         return
0128       end
0129     end
0130   end
0131 end
0132 
0133 % get the size of the structs ... are they struct arrays?
0134 struct1_size = size(struct1);
0135 struct2_size = size(struct2);
0136 
0137 if ~all(struct1_size == struct2_size)
0138   firstViolation = 'struct2';
0139   violationReason = 'struct_arrays_different_size';
0140   structsAreEqual = 0;
0141   return
0142 else
0143   nstruct_array = prod(struct1_size); % number of elements
0144 end
0145 
0146 % if we are checking only for a substruct, the following will
0147 % still work because we are only cycling through the fields of
0148 % struct1 and checking for a match in struct2. If we are checking
0149 % for an exact match, then we have already
0150 % verified in the logic above that both structs have the exactly
0151 % the same fields
0152   
0153 for iField = 1:struct1_nFields
0154     
0155   struct1_fieldName = struct1_fieldNames{iField};
0156   
0157   if ismember(struct1_fieldName,ignore_fieldnames)
0158     if VERBOSE
0159       warning('fieldname %s encountered, but ignored',struct1_fieldName);
0160     end
0161     continue
0162   end
0163   
0164   % step through each element of the struct array, compare
0165   for sIdx = 1:nstruct_array
0166 
0167     struct1_val = struct1(sIdx).(struct1_fieldName);
0168     struct2_val = struct2(sIdx).(struct1_fieldName);
0169     struct1_fieldType = class(struct1_val);
0170     struct2_fieldType = class(struct2_val);
0171   
0172     %see if the fields are of the same type
0173     %if both checkTypes and checkValues are turned off, then bypass
0174     if(~strcmp(struct1_fieldType,struct2_fieldType) &&...
0175         (checkTypes || checkValues))
0176       % If both are empty, don't worry about it
0177       if isempty(struct1_val) && isempty(struct2_val)
0178         continue
0179       end
0180       firstViolation = {struct1_fieldName};
0181       violationReason = 'type_differs';
0182       structsAreEqual = 0;
0183       return
0184     end
0185   
0186     %if we are not comparing values and the type of field is anything
0187     %but a struct, set them to equal and move on to the next
0188     %field. Otherwise, we will compare their values in the switch statement
0189     if ~checkValues && ~isstruct(struct1_val)
0190       fieldsAreEqual = 1;
0191       continue;
0192     end
0193   
0194     %for each case (except for type struct), we need to see if
0195     %(checkValues) is true. This actually seemed like the most
0196     %efficient route because a lot of the necessary logic when not comparing
0197     %values is still contained in the 'for' loop.
0198     switch struct1_fieldType
0199       
0200       case numberTypes
0201         if size(struct1_val) == size(struct2_val)
0202           fieldsAreEqual = all(struct1_val(:) == struct2_val(:));
0203         else
0204           fieldsAreEqual = 0;
0205         end
0206         
0207         if ~fieldsAreEqual && ismember(struct1_fieldName, subsetIsOK)
0208           if all(ismember(struct1_val, struct2_val))
0209             fieldsAreEqual = 1;
0210           end
0211         end
0212  
0213       case 'char' 
0214         fieldsAreEqual = strcmp(struct1_val,struct2_val);
0215 
0216       case 'struct'
0217         [fieldsAreEqual,subFieldViolation,violationReason] = ...
0218             compare_structs(struct1_val,struct2_val,'values',checkValues,...
0219             'substruct',checkSubstruct,'types',checkTypes,...
0220             'ignore_fieldnames',ignore_fieldnames, ...
0221                         'subsetIsOK', subsetIsOK);
0222 
0223       case 'cell'
0224         [fieldsAreEqual,subFieldViolation,violationReason] = ...
0225             compare_cells(struct1_val,struct2_val);
0226 
0227       case 'logical'
0228         if struct1_val == struct2_val
0229          fieldsAreEqual = 1;
0230         else
0231          fieldsAreEqual = 0;
0232         end
0233 
0234       case 'function_handle'
0235         struct1_str = func2str(struct1_val);
0236         struct2_str = func2str(struct1_val);
0237         fieldsAreEqual = strcmp(struct1_str,struct2_str);
0238             
0239       otherwise
0240         error('Fieldtype %s not supported.',struct1_fieldType);
0241 
0242 
0243     end % switch struct1
0244 
0245     if ~fieldsAreEqual
0246         if isstruct(struct1_val) 
0247           firstViolation = strcat(struct1_fieldName,'.',subFieldViolation);
0248         else
0249           firstViolation = {struct1_fieldName};
0250           violationReason = 'values_differ';
0251         end
0252         structsAreEqual = 0;
0253         return
0254     end % if (~fieldsAre
0255   end % for sIdx
0256   
0257 end % for iField
0258 
0259 % if any one field was not equal, then we would not have
0260 % made it this far. Set structsAreEqual =1 and return
0261 structsAreEqual = 1;
0262 return
0263 
0264

Generated on Wed 20-Sep-2023 04:00:50 by m2html © 2003