I have the need to obtain the maximum value of a property of a collection of custom objects of the same class. The objects are stored in a NSArray, and the property happens to be another NSArray of numbers.
Let me explain in detail:
NSArray *samples; // of CMData, 4000 elements
CMData is a class that models a sample, for a specific moment in time, of a set of different channels that can have different values.
@interface CMData : NSObject
@property (nonatomic) NSUInteger timeStamp;
@property (nonatomic, strong) NSArray *analogChannelData; // of NSNumber, 128 elements
@end
(I have stripped other properties of the class not relevant to the question)
So for example, sample[1970] could be:
sample.timeStamp = 970800
sample.analogChannelData = <NSArray>
[
[0] = @(153.27)
[1] = @(345.35)
[2] = @(701.02)
...
[127] = @(-234.45)
]
Where each element [i] in the analogChannelData represents the value of that specific channel i for the timeStamp 970800
Now I want to obtain the maximum value for all the 4000 samples for channel 31. I use the following code:
NSUInteger channelIndex = 31;
NSMutableArray *values = [[NSMutableArray alloc] init]; // of NSNumber
// iterate the array of samples and for each one obtain the value for a
// specific channel and store the value in a new array
for (CMData *sample in samples) {
[values addObject:sample.analogChannelData[channelIndex]];
}
// the maximum
NSNumber *maxValue = [values valueForKeyPath:@"@max.self"];
I want to replace this programming structure by a filter through an NSPredcicate or use valueForKeyPath: to obtain the maximum of the data I need.
Anyone knows how to do this without a for loop? Just using NSPredicates and/or valueForKeyPath?
Thank you very much in advance for your help.
Update 1
Finally I benckmarked the for-loop version against the keyPath version (see accepted answer) and it runs much faster so it is better to go with a for loop. Recalling some lessons from my algorithms classes, I implemented an even faster version that doesn't need an array to store the values. I just iterate over the selected channel and just choose the maximum in each iteration. This is by far the fastest version.
So:
- version 1: for loop (see code above)
- version 2: version with custom property (see selected answer from Marcus, update 2)
- version 3: new code
Code for version 3:
NSUInteger channelIndex = 31;
NSNumber *maxValue = @(-INFINITY);
for (CMTData *sample in samples) {
NSNumber *value = sample.analogChannelData[channelIndex];
if (value) { // I allow the possibility of NSNull values in the NSArray
if ([value compare:maxValue] == NSOrderedDescending)
maxValue = value;
}
}
// the maximum is in maxValue at the end of the loop
Performance:
After 20.000 iterations in iOS simulator:
- Version 1: 12.2722 sec.
- Version 2: 21.0149 sec.
- Version 3: 5.6501 sec.
The decision is clear. I'll use the third version.
Update 2
After some more research, it is clear to me now that KVC does not work for infividual elements in the inner array. See the following links: KVC with NSArrays of NSArrays and Collection Accessor Patterns for To-Many Properties
Anyway because I wanted to compute the maximum of the elements it is better to iterate the array than use some tricks to make KVC work.
You can solve this with using Key Value Coding and the collection operators.
Update 1
As Arcanfel mentioned, you can join the arrays together:
I would suggest reading the documentation that we both linked to. There are some very powerful features in there.
Update 2
Further to HRD's answer, he has your solution, you need to combine his changes with KVC.
Add a propert to your CMData object for currentChannel. Then you can call
Which will set it in every instance in the array. Then call:
Then you are done.