Faster way to pull specific data from NSMutableArray? - ios

I have an array full of NSObjects I created called "Questions".
One property of each Question is which level it belongs to.
If the user has chosen to play level 2, I want to get all the Questions that have a .level property of 2. Right now I am looping through all the questions to find the matches, but this is taking ~2 seconds on an iPad 3 / new iPad device. Is there a faster way of dealing with a situation like this?
int goThrough = 0;
do {
Question *currentQuestion = [allQs objectAtIndex:(goThrough)];
if (currentQuestion.level == levelChosen) {
[questions addObject:currentQuestion];
}
goThrough++;
} while (goThrough < [allQs count]);
Your help is greatly appreciated!

If you have to organize the questions by level on a regular basis, then why not keep all of the questions organized by level. Create a dictionary of arrays. Each key if the level and each array is the list of questions for that level. You do this once and it becomes trivial to get the questions for a level.

I dont have access to a mac at the moment but you can give a try to this:
[allQs enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger index, BOOL *stop) {
Question *currentQuestion = [allQs objectAtIndex:index];
if (currentQuestion.level == levelChosen) {
[questions addObject:currentQuestion];
}
}
This will use all the cores of your device so it can be twice as fast

You could always use fast enumeration (which, unless you intend on mutating the objects is the fastest way to enumerate a collection). Something like this:
for (Question *thisQuestion in allQs) {
if (thisQuestion.level == levelChosen)
[questions addObject:thisQuestion];
}
}
Since you are not mutating the collection you are iterating through (allQs), this would work fine and be faster than using enumerateObjectsUsingBlock. If you need the index of the array you are iterating through (allQs), then use enumerateObjectsUsingBlock.

I would suggest using the NSArray method enumerateObjectsUsingBlock or one of it's variants. There are even variants that will loop through the array elements concurrently. You'd probably need to use a lock to add elements to your questions array however, since I doubt if NSMutableArray's addObject method is thread-safe.
You should probably test a non-concurrent version against a concurrent version with locking to see which is faster. Which approach is faster would depend on how many of the objects in the allQs array belong to the current level. If only a few belong, the code that asserts a lock won't fire very often, and the benefit of concurrency will outweigh the time penalty of asserting a lock. If most of the objects in the allQs array match the chosen level, code will end up spending a lot of time asserting locks, and the concurrent threads will still waiting for other threads to release a lock.
Modified code might look something like this:
single-threaded version:
[allQs enumerateObjectsUsingBlock:
^(Question *currentQuestion, NSUInteger index, BOOL *stop)
{
if (currentQuestion.level == levelChosen)
[questions addObject:currentQuestion];
}
];
Concurrent version:
[allQs enumerateObjectsWithOptions:
NSEnumerationConcurrent
usingBlock:
^(Question *currentQuestion, NSUInteger index, BOOL *stop)
{
if (currentQuestion.level == levelChosen)
#synchronized
{
[questions addObject:currentQuestion];
}
}
];
Actually, now that I think about it, you would likely get still faster performance by first doing a concurrent pass on the array using indexesOfObjectsWithOptions:passingTest. In that pass you'd build an NSIndexSet of all the objects that match the current level. Then in one pass you'd extract those elements into another array:
NSIndexSet *questionIndexes = [allQs indexesOfObjectsWithOptions: NSEnumerationConcurrent
usingBlock:
^(Question *currentQuestion, NSUInteger index, BOOL *stop)
{
return (currentQuestion.level == levelChosen)
}
];
questions = [allQs objectsAtIndexes: questionIndexes];
Another poster pointed out that you are better off breaking up your arrays of questions up by level in advance. If that works with your program flow it's better, since not filtering your array at all will always be faster than the most highly optimized filtering code.

There is a simple answer that seems to be missing. If you want to filter the objects of an array to only have certain ones left, -filteredArrayUsingPredicate: is what you would want. It can be done exceptionally simply.
NSPredicate *p = [NSPredicate predicateWithBlock:^(Question *aQuestion, NSDictionary *bindings){
return (aQuestion.level==2);
}];
NSArray *filteredArray = [originalArray filteredArrayUsingPredicate:p];

Related

Check NSArray is sorted

I have an NSMutabaleArray and I want to check it whether it would be in sorted mode or not after insertion of any element. So what would be the fastest technique to do that.
You need one loop to traverse and check if array[i+1] is greater than array[i]th element, assuming array is sorted in ascending order. If this fails then its not sorted. This would be O(n).
By using Cocoa- library:
Typical answer would be to sort it again, if it is already sorted then that would be quick, not much of time and space complexity.Then compare the two arrays, if both matches then your array was sorted.
EDIT:
The above is an example for integers. If objects (Cocoa objects) are there, then you need to decide what kind of values you want to compare, if string then need to check NSOrderedDescending, NSOrderedSame , for NSDate isGreater etc.
I have done like this (as I have a String array)
for (int i = 1; i < array.count; i++)
if ([array[i - 1] compare:array[i]] != NSOrderedAscending)
return NO;
return YES;
So this code returns me a BOOL value when I call the function.

searching large arrays in Objective-C

I have two very large NSMutableArray of strings containing more than 40k records each. I have to take each element from one array and sort that string into another array then make a new array which conatins only those records that are in both array. I have implemented the following code which take too much time as well as a lot of memory space also (crash in device). Are there any ways to solve this problem in a more efficient manner.
// _perArray and listArray contains more then 30K records each
for(NSString *gak in _perArray){
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF LIKE[c] %#",gak];
NSArray *results = [listArray filteredArrayUsingPredicate:predicate];
if(results.count>0){
[_resultArray addObject:results[0]];
}
}
Use binary search
index sort one array (the one with less records)
this will enable the usage of binary searching
sort the lesser array just to need less memory for index array
loop through the second array
for each record binary search in the first array
if found add record to output array
do not forget to preallocate the output array to avoid reallocation slowdowns
What it means:
Let N,M be the array sizes where N<=M
naive approach is O(N.M)
this approach (depending on used sort) leads to O(N.log(N).log(M))
Sort both arrays and use single pass incremental search
the complexity will lead to something like O((N.log(N))+(M.log(M))+M)
which in therms of complexity turns to O(M.log(M))
So:
index sort booth arrays
loop through M
increment index for array with lesser record
if match found add it to output array
To be more specific bullet 2 will be something like this (if arrays are sorted ascending):
// variables
string m[M],n[N],o[N]; // your arrays any string type with overloaded <,== operators
int M,N,O; // arrays sizes
int ixm[M],ixn[N]; // indexes for index sort
int i,j;
// bullet 2
for (i=0,j=0,O=0;;)
{
if (m[ixm[i]]==n[ixn[j]]) { o[O]=m[ixm[i]]; O++; }
if (m[ixm[i]]< n[ixn[j]]) { if (i<M) i++; else { if (j<N) j++; else break; }}
else { if (j<N) j++; else { if (i<M) i++; else break; }}
}
If you encode the string comparisons right you can do booth if conditions with single comparison
[notes]
if you do not want to use any of these approaches then there is also another way
you can add flag to one array telling you if it is already used
if it is skip the use of it during your comparisons
that will speed up your naive approach about 2 times
from M.N string comparisons you will need to do just M.N/2
If you have too big data chunks to fit in memory
then segmentate both arrays to some size fit to memory/Cache/...
and first index sort all segments
then do one of the above approach on all segments combinations
the only thing you need to add is checking if O[] does not already contain added string
if you arrays does not have multiples of the same string then this is not the case
otherwise keep O[] sorted or index sorted
and check by binary search ...
this segmentation will be speeded up with the used flag significantly
Always balance the performance issues with the frequency this code path is called. Going the database route might introduce a whole new set of issues to deal with while simply performing the sort in the background, and cutting down the size of the array first might be good enough.
Remove the duplicates first using NSMutableSet
Add all the objects, NSString in this case, to an NSMutableSet. This will eliminate the duplicates. Then sort the remaining objects.
NSArray *array1;
NSArray *array2;
NSMutableSet *mutableSet = [[NSMutableSet alloc] initWithArray:array1];
[mutableSet addObjectsFromArray:array2];
NSSortDescriptor *sortDescriptor = nil; // You'll need to create a sort descriptor.
NSArray *result = [mutableSet sortedArrayUsingDescriptors:#[sortDescriptor]];
// Alternative
NSArray *result = [[mutableSet allObjects] sortedArrayUsingSelector:#selector(compare:)];
I wrote a quick Obj-C test you can try at the command-line.
Run it in the background and return when finished.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// Perform the sorting
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Tell the main thread I'm done.
});
});

Objective-C how to pull several random items out of NSArray without getting duplicates? [duplicate]

This question already has answers here:
Getting a random object from NSArray without duplication
(3 answers)
Closed 7 years ago.
I have an array of random properties I would like to assign to equipment within the game I'm developing.
The code that I use below is returning an NSArray. I'm interested if there's way to get item indices from that array without getting duplicate values. The obvious solution is to create a mutable array with the returned array, do random, remove item that was returned and loop until the number of items is received.
But is there a different way of getting X random items from NSArray without getting duplicates?
//get possible enchantments
NSPredicate *p = [NSPredicate predicateWithFormat:#"type = %i AND grade >= %i", kEnchantmentArmor,armor.grade];
NSArray* possibleEnchantments = [[EquipmentGenerator allEnchantmentDictionary] objectForKey:#"enchantments"];
//get only applicable enchantments
NSArray *validEnchantments = [possibleEnchantments filteredArrayUsingPredicate:p];
NSMutableArray* mutableArray = [NSMutableArray arrayWithArray:validEnchantments];
NSDictionary* enchantment = nil;
if(mutableArray.count>0)
{
//got enchantments, assign number and intensity based on grade
for (int i = 0; i<3;i++)
{
enchantment = mutableArray[arc4random()%mutableArray.count];
[mutableArray removeObject:enchantment];
//create enchantment from dictionary and assign to item.
}
}
You can shuffle the array using one of the following techniques:
What's the Best Way to Shuffle an NSMutableArray?
Non repeating random numbers
Then, take the first X elements from the array.
Many years ago, I was working on card game and I realized that shuffling the deck was an inefficient way to get random cards. What I would do in your shoes is pick a random element, and then replace it with the element at the end of the array, like so:
#interface NSMutableArray (pickAndShrink)
- (id) pullElementFromIndex:(int) index // pass in your random value here
{
id pickedItem = [self elementAtIndex:index];
[self replaceObjectAtIndex:index withObject:[self lastObject]];
[self removeLastObject];
return pickedItem;
}
#end
The array will shrink by one every time you pull an element this way.
You could use a random number generator to pick a starting index, and then pick the subsequent indices based on some kind of math function. You would still need to loop depending on how many properties you want.
Eg:
-(NSMutableArray*)getRandomPropertiesFromArray:(NSArray*)myArray
{
int lengthOfMyArray = myArray.count;
int startingIndex = arc4random()%lengthOfMyArray;
NSMutableArray *finalArray = [[NSMutableArray alloc]init]autorelease];
for(int i=0; i<numberOfPropertiesRequired; i++)
{
int index = [self computeIndex:i usingStartingIndex:startingIndex origninalArray:myArray];
[finalArray addObject:[myArray objectAtIndex:index]];
}
return finalArray;
}
-(int)computeIndex:(int)index usingStartingIndex:(int)startingIndex
{
//You write your custom function here. This is just an example.
//You will have to write some code to make use you don't pick an Index greater than the length of your array.
int computedIndex = startingIndex + index*2;
return startingIndex;
}
EDIT: Even your computeIndex function could use randomness in picking the subsequent indices. Since you have a startingIndex, and another index, you could use that to offset your function so that you never pick a duplicate.
EDIT: If your array is very large, and the subset you need to pick is small, then rather than shuffle the entire array (maybe more expensive), you could use this method to pick the number of items you need. But if your array is small, or if the number of items you need to pick are almost the size of the array, then the godel9's solution is better.
You can use a mutable array and then remove them as the are selected, use something like random()%array.count to get a random index. If you don't want to modify the array then copy it with [array mutableCopy].

Nested NSArray filtering

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.
NSNumber *result = [sample valueForKeyPath:#"#max.analogDataChannel"];
Update 1
As Arcanfel mentioned, you can join the arrays together:
NSNumber *result = [samples valueForKeyPath:#"#max.#unionOfArrays.#analogChannelData"];
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
[samples setValue:#(channelIndex) forKey:#"currentChannel"];
Which will set it in every instance in the array. Then call:
[samples valueForKeyPath:#"#max.analogDataForCurrentChannel"];
Then you are done.
I have not tested out the code yet, but I think this is exactly what you are looking for:
[samples valueForKeyPath:#"#max.(#unionOfArrays.analogChannelData)"];
I guess you can also use #distinctUnionOfArray to remove duplicate values.
Here is the link to Apple Documentation that covers collection operators.
Hope this is helpful!
Cheers!
A suggestion for further exploration only
Offhand it is not clear you can do this as-is with a single KVC operator. What you might consider is adding two properties to your class: currentChannel, which sets/gets the current channel; and analogChannelDataForCurrentChannel, which is equivalent to analogChannelData[currentChannel]. Then you can:
samples.currentChannel = channelIndex;
... [samples valueForKeyPath:"#max.analogChannelDataForCurrentChannel"];
with any appropriate locking between the two calls if thread-safety is required (so one thread does not set currentChannel, then a second, and then the first do the KVC operator with the second's channel...).
HTH

Timing loop results

I got a little stuck and I'm hoping someone can point me in the right direction. I have an NSMutableArray that stores a sequence. I created an enumerator so that a while loop can get the content of the array one by one.
Everything works fine however I want the methods to be called with a 10 second gap in between each call. Right now it plays all at once (or in very quick order). What should I look at to create a delay in between method calls?
Below is what I got so far. Thanks!
NSEnumerator * enumerator = [gameSequenceArray objectEnumerator];
id element;
while(element = [enumerator nextObject])
{
NSLog(element);
int elementInt = [element intValue];
[self.view showButton:elementInt];
}
You almost certainly don't want to stick a "delay" in your loop, which will block the thread until it's over (and, unless your app is multi-threaded, block the entire thing). You could use multiple threads, but instead I'd probably split the loop out over repeated timed calls of another selector. Store the enumerator (or current index), and then look at NSObject's performSelector:awithObject:afterDelay:
So something like
[NSObject performSelector:#selector(some:selector:name:) withObject:objInstance afterDelay: 10]
where the selector will pickup the current enumerator, use it, advance it and schedule another call. Make sure you don't allow changes to the collection whilst this set of timed methods is executing.
This is what NSTimer is for. Use NSTimer to get each element in the array sequentially.
As an aside: you might want to take a look at Objective-C 2.0's Fast Enumeration
if gameSequenceArray is an array, then you don't need to use an enumerator:
NSTimeInterval time = 10;
for (id elementId in gameSequenceArray) {
[self.view performSelector:#selector(showButton:) withObject:elementID afterDelay:time];
}
and then you declare showButton:
- (void)showButton:(id)anElement {
...
}
If you end up passing your object enumerator around with a timer, know that you are not allowed to modify your array's contents until you are finished enumerating it.
So here was the solution that I came up with based on everyones input.
NSEnumerator * enumerator = [gameSequenceArray objectEnumerator];
NSTimeInterval time = 5;
for (NSString *element in enumerator) {
id elementId = element;
time++;
[self.view performSelector:#selector(showButton:) withObject:elementId afterDelay:time];
}
Thanks again for pointing me in the right direction everyone.

Resources