Compare 2 Objects in Objective-C - ios

In my application, I want to compare 2 core data instances of the entity "Workout". I want to check if the 2 objects have identical attribute values for all of their attributes. Essentially if the two objects are the same minus the relationship, whosWorkout. Is there any way to do this without manually checking every single attribute? I know I could do:
if(object1.intAttr == object2.intAttr){
NSLog(#"This attribute is the same");
}
else{
return;
}
repeat with different attributes...
Is there any core data method to make this a bit less tedious?

First I would create an isEqual method in the Workout subclass like this...
-(BOOL)isEqualToWorkout:(Workout*)otherWorkout
{
return [self.attribute1 isEqual:otherWorkout.attribute1]
&& [self.attribute2 isEqual:otherWorkout.attribute2]
&& [self.attribute3 isEqual:otherWorkout.attribute3]
&& [self.attribute4 isEqual:otherWorkout.attribute4]
...;
}
Then whenever you want to compare to Workout objects just use...
BOOL equal = [workout1 isEqualToWorkout:workout2];

You can iterate through the attributes by name.
for (NSString *attribute in object.entity.attributesByName) {
if ([[object valueForKey:attribute] intValue] !=
[[object2 valueForKey:attribute] intValue]) {
return NO;
}
}
return YES;
This assumes all integer attributes. You could do a switch statement to check for the class with the class method and deal with different data types as well.

If you need to compare whether one object represents a greater or lesser value than another object, you can’t use the standard C comparison operators > and <. Instead, the basic Foundation types, like NSNumber, NSString and NSDate, provide a compare: method:
if ([someDate compare:anotherDate] == NSOrderedAscending) {
// someDate is earlier than anotherDate
}

I ended up doing the following:
-(BOOL)areEqual:(Workout *)firstWorkout secondWorkout:(Workout *)secondWorkout{
NSArray *allAttributeKeys = [[[firstWorkout entity] attributesByName] allKeys];
if([[firstWorkout entity] isEqual:[secondWorkout entity]]
&& [[firstWorkout committedValuesForKeys:allAttributeKeys] isEqual:[secondWorkout committedValuesForKeys:allAttributeKeys]]) {
return YES;
}
else{
return NO;
}
}

Related

Asserting properties in a custom NSMutableArray

I'm not too sure if I should of made another question for this or expanded on the last question, please correct me if I wasn't meant to.
I've currently got this code working:
if ([ myArray containsObject:#"Object1" ] && [ myArray containsObject:#"Object 2"]) {
return YES;
}
else {
return NO;
}
What I'm needing to do is modify this so it iterates through an array and accesses an Objects property value. For example:
if (myArray contains obj.ID 1 & 2) {
return YES
}
else{
return NO;
}
Any suggestions on what I should look at? I've been at this for a couple of hours and tried different permutations with no luck.
Thank you!
You can use -indexOfObjectPassingTest: to check if an object with a particular attribute value is in your array. The method returns either the object's index if it is found or NSNotFound if not.
Thus, assuming your objects are e.g. NSDictionaries and they have NSNumbers as IDs, you could do something like this:
if([myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==1;
}]!=NSNotFound && [myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==2;
}]!=NSNotFound)
{
//Array contains objects
}
If you only want the first object, you can use -indexOfObjectPassingTest: as mpramat says. If you want all the objects in the array that match your criteria, use indexesOfObjectsPassingTest:.
It takes a block as a parameter. The block evaluates each object and returns YES or know to let the method know if that object should be part of the set of objects that pass the test.

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.

What is the perfect way to avoid "beyond bounds" when using NSArray

I knows some ways to avoid this, for example
if (index >= [_data count] || index < 0) return nil;
return [_data objectAtIndex:index];
But should i aways do this? or are there any other solutions about this topic?
First, I'd like to echo #rmaddy's comment, which is spot on:
There is no general solution. Every case is different.
That said, there are other techniques you can use:
firstObject and lastObject
These methods will return the object, or nil if there is none. These methods will never throw an exception.
Fast Enumeration
You can use fast enumeration and never need to check the indices:
NSArray *myStrings = #[#"one", #"two"];
for (NSString *thisString in myStrings) {
NSLog(#"A string: %#", thisString);
}
Safe Category
You can add a category on NSArray if you find yourself doing this frequently:
- (id)safeObjectAtIndex:(NSUInteger)index {
if (index >= [self count] || index < 0) return nil;
return [self objectAtIndex:index];
}
See Customizing Existing Classes if you aren't familiar with categories.
One downside of this is it may be harder to find errors in your code.
In apple library NSArray objectAtIndex method:
objectAtIndex:
Returns the object located at the specified index.
- (id)objectAtIndex:(NSUInteger)index
Parameters
index
An index within the bounds of the array.
Return Value
The object located at index.
You can use "NSUInteger index ;" so "if (index < 0)" could be omitted.
In my opinion, if you need to provide some interface to others, you may need to add these code to avoid "beyond bounds".
But if your code just works for yourself, you need not to do the stuff because most of time what you needs is an object in the array but not the nil. If the index beyonds bound, there must be some logic error which you need to fix. Let exception goes and find your bug.
U can swizzle the objectAtIndexmethod,before call objectAtIndex, invoke method like 'custom_objectAtIndex' to check if out of bounds .
+ (void)load{
Method method1 = class_getInstanceMethod(objc_getClass("__NSArrayI"),#selector(objectAtIndex:));
Method method2 = class_getInstanceMethod(objc_getClass("__NSArrayI"),#selector(custom_objectAtIndex:));
method_exchangeImplementations(method1, method2);
}
- (id)custom_objectAtIndex:(NSUInteger)index{
NSLog(#"~~~~~~~:%ld",count);
if (index >= self.count) {
return #"out of bounds";
} else {
return [self custom_objectAtIndex:index];
}
}

NSMutableArray or NSMutableDictionary : which is best for this scenario?

I need to scroll through several thousands of words to categorize them... to determine which words have the same pattern. (this part works)
For example, a four letter word that has two m's in 2nd & 4th position represent a pattern ("-m-m"). Once I have gone through all the words, I will know how many words there are for any given pattern. I am scrolling through now, but the problem I have is 'remembering' how many words I have in any given pattern.
I was thinking of using NSMutableDictionary and have the key be the pattern ('-m-m-') and the object represent the count of that pattern. This means every time I come across a pattern, I look up that pattern in the dictionary, get the key, increment the key, and put it back in the dictionary.
I need help with both the decision and syntax for performing this task.
Thank You
The answer to your question was this part of your (given) question "I will know how many words there are for any given pattern.". I would use an array of dictionary. You use the dictionary to store key value pair: a known pattern and the count. And you use the array to store those KVP records. So the next time you detect a pattern, search for the array for that record (dictionary), if found, increment the count. If not, create new record and set the count to 1.
Added sample code:
#define kPattern #"Pattern"
#define kPatternCount #"PatternCount"
-(NSMutableDictionary *)createANewDictionaryRecord:(NSString *) newPattern
{
int count = 1;
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
newPattern, kPattern,
[NSString stringWithFormat:#"%i",count], kPatternCount,
nil];
return myDictionary;
}
-(void)addANewPatternToArray:(NSMutableDictionary *)newDictionary
{
// NSMutableArray *myArrayOfDictionary = [[NSMutableArray alloc]init]; // you need to define it somewhere else and use property etc.
[self.myArrayOfDictionary addObject:newDictionary]; //or [self.myArrayOfDictionary addObject:newDictionary]; if you follow the recommendation above.
}
-(BOOL)existingPatternLookup:(NSString *)pattern
{
for (NSMutableDictionary *obj in self.myArrayOfDictionary)
{
if ([[obj objectForKey:kPattern] isEqual:pattern])
{
int count = [[obj objectForKey:kPatternCount] intValue] + 1;
[obj setValue:[NSString stringWithFormat:#"%i",count] forKey:kPatternCount];
return YES;
}
}
[self.myArrayOfDictionary addObject:[self createANewDictionaryRecord:pattern]];
return NO;
}
-(void)testData
{
NSMutableDictionary *newDict = [self createANewDictionaryRecord:#"mmm"];
[self addANewPatternToArray:newDict];
}
-(void) printArray
{
for (NSMutableDictionary * obj in self.myArrayOfDictionary)
{
NSLog(#"mydictionary: %#", obj);
}
}
- (IBAction)buttonPressed:(id)sender
{
if ([self existingPatternLookup:#"abc"])
{
[self printArray];
} else
{
[self printArray];
}
}
Not being an objective C expert but solving this problem in java before, I would say a dictionary(I used a map when doing it in java) is the best way. Check if the key(pattern) already exist if so increment that count else put a new one in the dictionary.
EDIT
If you want to not just get the count of a pattern, but in fact tell which words fall under that pattern, I would use a dictionary of strings to mutable arrays. In the arrays you store the words and the key to the array is the pattern(as a string), similar code as above but instead of just incrementing the count, you have to add the new word to the array.
The only difference in NSDictionary and NSMutableDictionary is that one can have objects added to it. I think your implementation is good, but English is a complex language. It would be more efficient to parse out the string with regex than to set a key for it.
Why don't you use NSCountedSet instead:
NSCountedSet Class Reference
..which is available in iOS 2.0 and later?
Each distinct object inserted into an NSCountedSet object has a counter associated with it. NSCountedSetkeeps track of the number of times objects are inserted [...] Thus, there is only one instance of an object in an NSSet object even if the object has been added to the set multiple times. The count method defined by the superclass NSSet has special significance; it returns the number of distinct objects, not the total number of times objects are represented in the set.
Then use:
- (NSUInteger)countForObject:(id)anObject
Use a dictionary of NSMutableArrays, and check for the existence of each search key as you recommended. If the key doesn't exist, add an NSMutableSet or NSMutableArray (depending on your needs) for the searched key type "-m-m" for example, and then add to the set or array for that key.

Case insensitive compare against bunch of strings

What would be the best method to compare an NSString to a bunch of other strings case insensitive? If it is one of the strings then the method should return YES, otherwise NO.
Here's a little helper function:
BOOL isContainedIn(NSArray* bunchOfStrings, NSString* stringToCheck)
{
for (NSString* string in bunchOfStrings) {
if ([string caseInsensitiveCompare:stringToCheck] == NSOrderedSame)
return YES;
}
return NO;
}
Of course this could be greatly optimized for different use cases.
If, for example, you make a lot of checks against a constant bunchOfStrings you could use an NSSet to hold lower case versions of the strings and use containsObject::
BOOL isContainedIn(NSSet* bunchOfLowercaseStrings, NSString* stringToCheck)
{
return [bunchOfLowercaseStrings containsObject:[stringToCheck lowercaseString]];
}
Just to add a few additions to Nikolai's answer:
NSOrderedSame is defined as 0
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};
So if you call caseInsensitiveCompare: on a nil object you would get nil. Then you compare nil with NSOrderSame (which is 0) you would get a match which of course is wrong.
Also you will have to check if parameter passed to caseInsensitiveCompare: has to be not nil. From the documentation:
This value must not be nil. If this value is nil, the behavior is
undefined and may change in future versions of OS X.

Resources