Calculating percent for 1,000s of objects slow in xcode - ios

can anyone suggest a faster approach to the following:
I have an array of 5,000 managed objects (faulted) (an array of car.h objects)
Each object has a set of items (toCarParts.h). This set can have any number of objects.
Now i want to sort these out by the most matches in my search query carpart array.
I search for wheel, seat, window, mirror.
The method will go through each car and find the closest match, and calculate a percentage. So if car a has wheel, seat, window, mirror, mat, tire, wiper, pipe --> the % should be 50%. (Matched 4/8 parts.
This is simple enough, but the problem is with 5,000 items the search takes a long time (even using coredata).
The logic i am using goes something like: (Pseudocode)
For each Car*car in array.
NSMutableArray *x=[car tocarparts]allobjects];
For the count of objects in x.
Carpart*part=objectatindex...i.
If the name of this matches one of my parts
add a count to my counter.
At the end of the loop counter/[x count] =%.car.percent=%.
There has to be a better way, any suggestions? (I think its the dividing and checking each part that takes forever.
Thank you in advance.
Edited, added code below:.
-(NSMutableArray*)calculatePercentagePerFind:(NSMutableArray*)CarArray:(NSMutableArray*)partsArray{
NSArray*defaultParts =[NSArray arrayWithArray:[[[HelperMethods alloc]init]getObjectUserDefault:#"AvailableDefaultParts"]];
int lowestPercentMatchInt=[[[HelperMethods alloc]init]getIntegerUserDefault:#"lowestPercentageMatch"];
NSMutableArray*partsFromCarArray=[[NSMutableArray alloc]init];
NSMutableArray*returnArray=[[NSMutableArray alloc]init];
NSMutableArray *partsWithDefaultParts =[NSMutableArray arrayWithArray:partsArray];
[partsWithDefaultParts addObjectsFromArray:defaultParts];
for (int i=0; i<[CarArray count]; i++) {
double matchCount=0;
Car *CarResult =(Car*)[CarArray objectAtIndex:i];
//Check if it will at least be 30% match
double number1 = [partsWithDefaultParts count];
number1 =(number1/[CarResult.numberOfParts doubleValue])*100;
if (number1>lowestPercentMatchInt) {
partsFromCarArray =[NSMutableArray arrayWithArray:[[CarResult toParts]allObjects]];
NSMutableArray *faultedParts=[[NSMutableArray alloc]init];
for (int i =0; i<[partsFromCarArray count]; i++) {
CarPart*part = (CarPart*)[partsFromCarArray objectAtIndex:i];
[faultedParts addObject:part.name];
}
// for each part in the Car
for (NSString *partInCar in partsWithDefaultParts){
//if the search parts contain that part, add one to count
if ([faultedParts containsObject:partInCar]) {
matchCount++;
}
}
//Calculate percent match
double percentMatch = matchCount;
percentMatch =(percentMatch/[CarResult.numberOfParts doubleValue])*100;
//if at least 30%(user default) then add the percent match to Car result
if (percentMatch >lowestPercentMatchInt) {
if (percentMatch>100) {
CarResult.percentMatch = [NSNumber numberWithDouble:100.00];
}else{
CarResult.percentMatch = [NSNumber numberWithDouble:percentMatch];
}
[returnArray addObject:CarResult];
}
}
}
NSLog(#"Percent Matched Cars = %i",[returnArray count]);
return [self arrangeByHighestPercentMatch:returnArray];
}

Try this, which I believe will minimize the strain on core data.
NSSet *selectionSet; // contains the selected parts
NSPredicate *filter = [NSPredicate predicateWithFormat:
#"self IN %#", selectionSet];
float percentageSum = 0;
NSSet *parts;
for (Car *car in fetchedObjects) {
parts = car.parts; // so the relationship is retrieved only once
percentageSum +=
[parts filteredSetUsingPredicate:predicate].count*1.0f
/ (parts.count*1.0f);
}
return percentageSum/fetchedObjects.count;
This would average out the percentages across all cars. There are other methods to weigh the parts differently in the aggregate.
It is not clear from your question, but if you do not need a total percentage but one percentage for each car there would be no need to loop through all cars - you could just calculate the percentage on the fly when displaying it (e.g. with a transient property).

Related

Finding the lowest NSInteger from NSArray

I am trying to return the lowest number in an array.
Parameter: arrayOfNumbers - An array of NSNumbers.
Return: The lowest number in the array as an NSInteger.
The code I have thus far doesn't give me any errors, but does not pass the unit tests. What am I doing wrong?
- (NSInteger) lowestNumberInArray:(NSArray *)arrayOfNumbers {
NSNumber* smallest = [arrayOfNumbers valueForKeyPath:#"#min.self"];
for (NSInteger i = 0; i < arrayOfNumbers.count; i++) {
if (arrayOfNumbers[i] < smallest) {
smallest = arrayOfNumbers[i];
}
}
NSInteger smallestValue = [smallest integerValue];
return smallestValue;
}
This is the unit test:
- (void) testThatLowestNumberIsReturned {
NSInteger lowestNumber = [self.handler lowestNumberInArray:#[#3, #8, #-4, #0]];
XCTAssertEqual(lowestNumber, -4, #"Lowest number should be -4.");
lowestNumber = [self.handler lowestNumberInArray:#[#83, #124, #422, #953, #1004, #9532, #-1000]];
XCTAssertEqual(lowestNumber, -1000, #"Lowest number should be -1000.");
}
This method
NSNumber* smallest = [arrayOfNumbers valueForKeyPath:#"#min.self"];
will already determine the smallest number in the array, so the loop inside the method is superfluous (on top of being plain wrong, as #vikingosegundo notices).
you are comparing objects with c types, resulting im pointer addresses being compared with an int.
Beside the fact your smallest is already the smallest, as you used the KVC collection operator #min.self (see Glorfindel answer), the following code shows you correct comparison
if (arrayOfNumbers[i] < smallest)
should be
if ([arrayOfNumbers[i] compare:smallest] == NSOrderingAscending)
or
if ([arrayOfNumbers[i] integerValue] < [smallest integerValue])

How to compare every pair of elements inside a NSArray?

I have an NSArray filled with only NSStrings
I understand that to iterate over a NSArray of n elements, all I have to do is use for (NSString *element in arrayOfElements). However, I was wondering if there is specific function that will perform a comparison between every string element in the array with each other. For example, if I have the array:
[#"apple", #"banana", #"peach", #"kiwi"],
how would I do the comparison so apple is compared to banana, peach and then kiwi; and then banana is against peach and wiki, and finally peach is against kiwi?
Try using nested for loops, ex:
for (int i = 0 ; i < array.count ; i ++) {
for (int j = i + 1 ; j < array.count ; j ++) {
// compare array[i] to array [j]
}
}
Edit: And although wottle's suggestion would work, I'd recommend mine in this case, since it won't waste iterations going over the same comparisons multiple times. What I've done in this algorithm by setting j = i + 1 is compare each element in the array only to the ones after it.
Given "the array will not have any duplicates, and every NSString will be unique" this sounds like a great case for using NSSet classes instead of NSArray. NSMutableSet provides:
minusSet:
Removes each object in another given set from the receiving
set, if present.
and
intersectSet:
Removes from the receiving set each object that isn’t a
member of another given set.
I'm not sure which operation you're looking for but it sounds like one of those should cover your exact use case.
What you're trying to do is a bit beyond what custom comparators were meant to do. Typically when you have a list and you want to run a custom comparator, you're doing it to sort the list. You seem to want to do some specific action when certain items in the list compare to others, and for that, I think a loop within a loop is your best bet. It won't be very good performance, so hopefully you are not expecting a large array:
-(void) compareArrayToSelf
{
NSArray *array=#[#"apple", #"bananna", #"peach", #"kiwi"];
for( NSString *value1 in array)
{
for( NSString *value2 in array)
{
if( ![value1 isEqualToString:value2] && [self compareArrayValue:value1 toOtherValue:value2])
{
//Do something with either value1 or value2
}
}
}
}

how to include index number in a class name

I am making a program where I need to loop through an array with a list of letters. I want the letters to be shown on their specific label. I have therefore created an outlet of each (about 38) and given them the name "alif01", "alif02", etc.
for (int i = 0; i < [arabicLetters count]; i++) {
int num = i;
NSString *letterString = [arabicLetters objectAtIndex:i];
NSLog(#"alif0%d is %#", num, letterString);
alif0**[i]**.text = arabicLetters[i];
}
is it possible to use the index [i] instead of writing it all manually?
You should not have 38 IBOutlet properties for this. You should have an array (possibly an IBOutletCollection) so that you can loop over the array / index into the array.
While technically you can create a key name and use KVC valueForKey: (appending strings / string format), the array approach is a much better solution.
Indeed, as you already have a loop, you would be better served by creating the labels in the loop directly, then you know you have the correct number. This is particularly beneficial later, when you change the contents of arabicLetters (though that sounds like it isn't a concern in this particular case).
Try with below code:
for (int i = 0; i < [arabicLetters count]; i++) {
NSString *letterString = [arabicLetters objectAtIndex:i];
NSString *propertyName = [NSString stringWithFormat:#"alif0%d.text",i];
[self setValue:letterString forKeyPath:propertyName];
}

How to sort NSSet subset in same order as full set NSArray, using NSPredicate?

My self.allArray contains all my objects. Then self.enabledSet contains a subset of these objects.
To create a sortedEnabledArray I currently do this:
NSArray* enabledArray = [self.enabledSet allObjects];
NSArray* sortedEnabledArray;
sortedEnabledArray = [enabledArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
{
int indexA = [self.allArray indexOfObject:a];
int indexB = [self.allArray indexOfObject:b];
if (indexA < indexB)
{
return NSOrderedAscending;
}
else if (indexA > indexB)
{
return NSOrderedDescending;
}
else
{
return NSOrderedSame;
}
}];
but I am wondering if this can't be done in a smarter/shorter way, using an NSPredicate for example. Any ideas?
EDIT:
One idea to shorten is:
int difference = indexA - indexB;
// Convert difference to NSOrderedAscending (-1), NSOrderedSame (0), or NSOrderedDescending (1).
return (difference != 0) ? (difference / abs(difference)) : 0;
It depends on number of items in your set and array, but you can do this way:
NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings)
{
return [self.enabledSet containsObject:object];
}];
NSArray* sortedEnabledArray = [self.allArray filteredArrayUsingPredicate:predicate];
My suggestion is to use a different approach. There is a companion to NSArray called NSIndexSet (and it's mutable counterpart, NSMutableIndexSet). It is an object that is specifically intended to keep track of subsets of an array that meet a given criteria.
NSArray includes methods like indexesOfObjectsPassingTest (and other variants that include additional parameters.) that let you add the indexes of some members of an array to an index set.
Once you have an index set that represents a subset of your allArray, you could use a method like objectsAtIndexes to get an array of just the selected objects.
Store in NSSet not objects, but indexes from allArray array.

Why Rand() always generates random numbers with fixed Unique Group count?

Even though , I execute the below code again & again I get the same Output. But , I think it should not:
int ObjectCount =500;
NSMutableArray *mut_arr = [[NSMutableArray alloc]initWithCapacity:0];
for (int i = 0; i<ObjectCount ; i++)
{
[mut_arr addObject:[NSNumber numberWithInt: rand()%ObjectCount]];
}
NSSet* uniqueSet = [NSSet setWithArray:mut_arr];
NSLog(#"Array of %d objects generates %d Unique Objects",[mut_arr count],[uniqueSet count]);
The output is as follows:
Array of 500 objects generates 317 Unique Objects
Here, Since the array contains random numbers the unique set count should be same again & again for same ObjectCount.
You're not actually generating unique NSNumber objects; some of them are equal.
A NSArray can contain multiple objects that are equal. A NSSet can not. This is why the set created from your array has less objects.
The reason why you're always getting 317 objects is that you're using rand() without seeding: Why do I always get the same sequence of random numbers with rand()?
Consider using arc4random() instead, which is seeding automatically.
Use like this
[mut_arr addObject:[NSNumber numberWithInt:(arc4random() % (ObjectCount-1) + 1)]];

Resources