-
Notifications
You must be signed in to change notification settings - Fork 0
/
nestedSortStruct2.m
132 lines (115 loc) · 6.59 KB
/
nestedSortStruct2.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
function [sortedStruct index] = nestedSortStruct2(aStruct, fieldNamesCell, directions, classFields)
% [sortedStruct index] = nestedSortStruct2(aStruct, fieldNamesCell, directions, classFields)
% nestedSortStruct2 returns a nested sort of a (one-dimensional) struct array
% (aStruct), and can also return an index vector. The fields by which to sort are
% specified in a cell array of strings fieldNamesCell. Fields must be single numbers or
% logicals, or chars (usually simple strings).
%
% fieldNamesCell can also be a simple string of one fieldname, in which case
% nestedSortStruct2 will simply call sortStruct. This will be faster than putting the
% single fieldname in a cell array.
%
% directions is an optional argument to specify whether the struct array should be sorted
% in ascending or descending order for the fields. By default, the struct array will be
% sorted in ascending order for each field. If supplied, directions must be
% 1) a single 1 to sort in ascending order for all fields, or
% 2) a single -1 to sort in descending order for all fields, or
% 3) a vector of 1's and -1's, the same length as fieldNamesCell, where the struct
% array will be sorted in the order specified by directions(ii) for
% fieldNamesCell(ii).
%
% classFields is an optional argument that should not be used when calling
% nestedSortStruct2 directly. recursive calls to nestedSortStruct2 use classFields to
% bypass checking inputs and determining classes of fields.
%
% nestedSortStruct2 sorts the struct array recursively, without converting it to a cell
% array.
%
% nestedSortStruct will usually be faster than nestedSortStruct2. For nestedSortStruct,
% the speed of sorting is mostly independent of the order of the fieldnames in
% fieldNamesCell.
%
% For nestedSortStruct2, the order of the fields in fieldNamesCell affects the speed. The
% sooner a field for which most entries in the struct array have unique values will be
% used to sort the struct array (i.e., the earlier that field's location in
% fieldNamesCell), the faster nestedSortStruct2 will be. If a field with mostly unique
% entries is the first field by which the struct array will be sorted, nestedSortStruct2
% could be faster than nestedSortStruct.
%% check inputs, construct classFields
if nargin < 4 % if classFields does not exist, check inputs
if ~isstruct(aStruct)
error('first input supplied is not a struct.')
end % if
if sum(size(aStruct)>1)>1 % if more than one non-singleton dimension
error('I don''t want to sort your multidimensional struct array.')
end % if
if ~iscell(fieldNamesCell)
if isfield(aStruct, fieldNamesCell) % if fieldNamesCell is a simple string of a valid fieldname
[sortedStruct index] = sortStruct(aStruct, fieldNamesCell);
return
else
error('second input supplied is not a cell array or simple string of a fieldname.')
end % if isfield
end % if ~iscell
if ~isfield(aStruct, fieldNamesCell)
for ii=find(~isfield(aStruct, fieldNamesCell))
fprintf('%s is not a fieldname in the struct.\n', fieldNamesCell{ii})
end % for
error('at least one entry in fieldNamesCell is not actually a fieldname in the struct.')
end % if
% check classes of fieldnames, construct classFields (0 for numeric, 1 for char)
classFields = zeros(1, length(fieldNamesCell));
for ii=1:length(fieldNamesCell)
fieldEntry = aStruct(1).(fieldNamesCell{ii});
classFields(ii) = 1*((isnumeric(fieldEntry) || islogical(fieldEntry)) && numel(fieldEntry)==1) + 2*(ischar(fieldEntry)) - 1;
end % for ii
if any(classFields == -1)
for ii=find(classFields==-1)
fprintf('%s is not a valid fieldname by which to sort.\n', fieldNamesCell{ii})
end % for ii
error('at least one fieldname is not a valid one by which to sort.')
end % if any...
% check directions, create directNew
if nargin < 3 % if directions doesn't exist
directNew = ones(1, length(fieldNamesCell));
else % check directions if it does exist
if ~(isnumeric(directions) && all(ismember(directions, [-1 1])))
error('directions, if given, must be a single number or a vector with 1 (ascending) and -1 (descending).')
end % if ~(...
if numel(directions)==1
directNew = directions * ones(1, length(fieldNamesCell)); % create vector from single element
elseif length(fieldNamesCell)~=length(directions)
error('fieldNamesCell and directions vector are different lengths.')
end % if numel...
end % if exist...
else % classFields exists, so directions should be in correct form
directNew = directions;
end % if check
%% sort by the first fieldname, then recursively call nestedSortStruct2 to sort by remaining fieldnames
if length(fieldNamesCell)==1 % if one fieldname
[sortedStruct index] = sortStruct2(aStruct, fieldNamesCell{1}, directNew(1), 0); % don't check inputs
else
[sortedStruct1 index1] = sortStruct2(aStruct, fieldNamesCell{1}, directNew(1), 0); % don't check inputs
switch classFields(1)
case 0 % numeric
[b m n] = unique([sortedStruct1.(fieldNamesCell{1})]);
case 1 % char
[b m n] = unique({sortedStruct1.(fieldNamesCell{1})});
otherwise
error('invalid classFields value encountered. you shouldn''t be here.')
end % switch
index2 = zeros(length(aStruct),1); % initialize index2
sortedStruct = aStruct; % initialization of sortedStruct
for ii=1:length(b) % for each group that has the same value for fieldname1
startIdx = find(n==ii, 1, 'first'); % starting index of the group
nNum = sum(n==ii); % number of members are in the group
if nNum == 1 % don't sort if only one member in the group
sortedStruct(startIdx) = sortedStruct1(n==ii);
indexTemp = 1; % with only one member, that member is in position 1 of its group
else % sort multiple members of the group
[sortedStruct(startIdx:startIdx+nNum-1) indexTemp] = nestedSortStruct2(sortedStruct1(n==ii), fieldNamesCell(2:end), directNew(2:end), classFields(2:end)); % nested sort of remaining fieldnames
end
index2(startIdx:startIdx+nNum-1) = indexTemp + startIdx - 1; % correct the index offset for that group
end % for ii
index = index1(index2); % correct for two rounds of sorting
end % if length(fieldNamesCell)>1