Ok a pretty simple question.. in c++ it seems to work but in objective-c i seem to struggle with it :S ..
If you want to compare two arrays it should be something like this right
for ( int i = 0; i < [appdelegate.nicearray count]; i++ )
{
if ( appdelegate.nicearray[i] == appdelegate.exercarray[i] )
{
NSLog(#"the same elements in this selection");
}
}
what's the problem exactly ?
These are Cocoa array objects (instances of NSArray), not C arrays or C++ vectors, and remember that Objective-C does not have operator overloading. The only things you can do with an object are pass it around, store it in variables, and send messages to it.
So the array-subscript operator is wrong with Objective-C objects. I don't think it's even linguistically valid to dereference a pointer to an Objective-C object, so this code should be giving you a compiler error. I may be misremembering, though. If it does make it to runtime, that code will crash sooner or later, since you're accessing memory beyond the ends of the array objects.
(EDIT from the year 2013: Objective-C now supports subscripting of objects. This ultimately translates into the appropriate objectAtIndex: or replaceObjectAtIndex:withObject: message. So, the code in the question would actually work now, although it's still not the proper way to simply walk an array, much less to compare two arrays.)
The proper way to retrieve an object from an NSArray object by its index is not to use the array-subscript operator, but to send the array object the objectAtIndex: message:
[myArray objectAtIndex:i]
The proper way to iterate on the elements of an array object, assuming you don't really need the index for something else (such as replacing objects in a mutable array), is to loop on it directly (this is called “fast enumeration”):
for (MyObject *myObject in myArray) {
…
}
NSArray also responds to objectEnumerator and reverseObjectEnumerator, which return a similarly-iterable object. Of the two, reverseObjectEnumerator is the more useful in new code, since you can just iterate on the array directly to iterate forward. Both of them were most useful before fast enumeration existed; that code looked like this:
NSEnumerator *myArrayEnum = [myArray objectEnumerator];
MyObject *myObject;
while ((myObject = [myArrayEnum nextObject])) {
…
}
(Yes, that's an assignment in the condition. Deliberately, hence the extra (). We coded boldly back then, didn't we?)
For what you're doing, though, you more likely want to send one of the arrays an isEqualToArray: message, as Williham Totland suggested:
BOOL theyAreEqual = [myFirstArray isEqualToArray:mySecondArray];
This will make sure both arrays have the same length, then walk them both in lock-step, sending isEqual: to each pair of objects. It'll return YES if every isEqual: message returned YES; NO otherwise. The arrays may contain different objects, but as long as each pair is equal, the arrays themselves are equal.
That assumes you want object equality. Two separate objects are equal if one of them responds with YES when you send it an isEqual: message and pass the other object. If you meant to compare the identities of the objects, then you do need to do the lock-step loop yourself and use ==:
BOOL arraysContainTheSameObjects = YES;
NSEnumerator *otherEnum = [otherArray objectEnumerator];
for (MyObject *myObject in myArray) {
if (myObject != [otherEnum nextObject]) {
//We have found a pair of two different objects.
arraysContainTheSameObjects = NO;
break;
}
}
But that's unlikely. Most of the time, I have wanted to test the objects' equality, not identities, so isEqualToArray: is what I wanted.
You want the isEqualToArray: method. As in:
if ([arrayOne isEqualToArray:arrayTwo]) {
// Do something
}
This will recursively compare the two arrays, while having the advantage of not being needlessly circuitous and not requiring a loop.
Try telling us the result you're getting when you run this code. The approach is correct, but try this one:
for (int i =0; i< appdelegate.nicearray.count; i++)
{
if ([[appdelegate objectAtIndex:i] isEqual: [appdelegate.exercarray objectAtIndex:i]])
{
NSLog(#"the same");
}
}
Here's a little one I put together based on the top ranked example. This merely checks that the arrays contains the same values, irrespective of order and if there are any duplicates. I mainly use this to compare keys of two dictionaries (which often return their allKeys arrays in various sort orders) to see if they contain the same objects. Thanks Peter Hosley for providing the example I adapted this from.
#pragma mark - Arrays
// Check to see if arrays contain the same elements, not necessarily in the same order
// This is different from [array isEqualToArray:responseKeys] which demands the same order in both arrays
// ## Does not compensate for duplicate entries in an array
+ (BOOL)doArraysContainTheSameObjects:(NSArray *)firstArray withArray:(NSArray *)secondArray {
BOOL arraysContainTheSameObjects = YES;
for (id myObject in firstArray) {
if (![secondArray containsObject:myObject]) {
// We have found an object that is not in the other array.
arraysContainTheSameObjects = NO;
break;
}
}
return arraysContainTheSameObjects;
}
I do the following when comparing arrays:
Check to see if any of the arrays are nil when the other is not
Check to see if the lengths are the same
Iterate (using a for loop like you have) over each element checking the matching element in the other array.
To compare elements you need to define what you want to regard as being "equal". Are they equal only if the pointers in the array are equal or can they be equal if the content is equal too.
For the pointer case, you can use ==.
For the deep comparison you might need to use CompareTo or something similar.
Related
I'm in the process of learning Objective-C, and am trying to get my head wrapped around the idea of using blocks.
In an example I'm seeing, there is a method meant to sort an NSArray named book in alphabetical order by name:
-(void) sort
{
[book sortUsingComparator:
^(id obj1, id obj2) {
return [[obj1 name] compare: [obj2 name]];
} ];
}
How does the method pass the objects to the obj1 and obj2 parameters? Does the sortUsingComparator somehow automatically pass 2 objects to its comparator?
sortUsingComparator: iterates over the array and calls the block with various pairs of objects to determine how to sort them. There's no magic here — it's a normal sorting function, but it uses your block instead of > to compare the values.
Well, depending on the implementation of the sort, every time the sort algorithm needs to compare 2 elements this will get called. You don't really need to know what will get passed to it, just define an order relationship for the types that get passed. It'll ask what he needs to know.
Some extra information about blocks that answers your question, a block can be assigned to a variable and called like a function. The sortUsingComparator: method signature is:
- (void)sortUsingComparator:(NSComparator)cmptr
cmptr is the variable that holds your block. The sortUsingComparator: method will run every element in the array through its sort algorithm and, as Fernando pointed out, every time the sort algorithm needs to compare 2 elements it's called like this:
NSComparisonResult result = cmptr(obj1, obj2);
And your block code is executed. So the answer to your question:
Does the sortUsingComparator somehow automatically pass 2 objects to its comparator?
is yes :)
The sort method does something like this (I'm not claiming the sort: method does this exactly, but the idea remains):
for (i = 0; i < [book count]; i++) {
id currentObject = [book objectAtIndex:i]; // obj1
for (j = 0; j < i; j++) {
id sortedObject = [book objectAtIndex:j]; // obj2
NSComparisonResult result = sortBlock(currentObject, sortedObject);
// Insert currentObject where appropriate; perform the rest of the sort
}
}
In this case, I'm assuming that the block's parameter name is ^sortBlock (the caret denotes a block). You invoke blocks just like regular functions, with parentheses and arguments.
So to answer your question…it's automatic in the sense that you don't have to think about it, but there's no magic going on.
Hope this helps!
I am still new to objective-c and am trying to figure out what this statement is doing exactly.
[names allKeys] sortedArrayUsingSelector:#selector(compare:)];
I know that allKeys is getting all the keys from my dictionary. I know that sortedArrayUsingSelector is sorting my array im creating. Then im calling the compare method, that is where I am lost what is this doing? From the document on apple it says that "Returns an NSComparisonResult value that indicates whether the receiver is greater than, equal to, or less than a given number." I dont understand how it is sorting based of that method.
NSArray * sortedKeys = [[names allKeys] sortedArrayUsingSelector:#selector(compare:)];
The above code returns a sorted array of the dictionary keys using the selector you provide. The selector is actually the function that will be called on the object that is being sorted in your array. In this case your array contains strings so in the actual NSArray sorting code the following would be happening,
//...
[key1 compare:key2];
//..
If you passed in a different selector lets say #selector(randomFunction:) then in the sorting code the following would happen
//..
[key1 randomFunction:key2];
//..
Since NSString does not respond to the selector randomFunction you would get an error. If you wanted to create your own type of comparison function you would need to add a category to the class that the array contains (in your case a category to NSString).
A better way to sort an array is to use a block statement.
id mySort = ^(NSString * key1, NSString * key2){
return [key1 compare:key2];
};
NSArray * sortedKeys = [[names allKeys] sortedArrayUsingComparator:mySort];
The reason it's a better way is sorting any objects is very easy to do.
id mySort = ^(MyObject * obj1, MyObject * obj2){
return [obj1.title compare:obj2.title];
};
NSArray * sortedMyObjects = [myObjects sortedArrayUsingComparator:mySort];
- (NSComparisonResult)compare:
{
// if (value of firstObject) < (value of secondObject)
// return NSOrderedAscending
// else if (value of firstObject) == (value of secondObject)
// return NSOrderedSame
// else
// return NSOrderedDescending
}
The -sortedArrayUsingSelector: method in your example calls the -compare: method on the objects in the array. For some objects Apple has already implemented a -compare: method, for example if you read through the NSString documentation, you'll find a -compare: method implemented. You can also call custom comparison methods on your own custom objects if you've implemented a comparison method in these objects. Please note the comparison method doesn't have to be called -compare:, of importance is only the return value (NSComparisonResult) and the object the method receives.
The new array contains references to the receiving array’s elements, not copies of them.
The comparator message is sent to each object in the array and has as its single argument another object in the array.
For example, an array of NSString objects can be sorted by using the caseInsensitiveCompare: method declared in the NSString class. Assuming anArray exists, a sorted version of the array can be created in this way:
NSArray *sortedArray =
[anArray sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
You should probably be using caseInsensitiveCompare: in this situation since you are most likely ordering an array of strings (keys from a dictionary).
You've assumed that sortedArrayUsingSelector is somehow separate to the compare: part of the code. This is not the case. compare: is the selector that is used to sort the array.
The sorting method gives you back an array where each element, when sent the specified selector and the next element in the array, gives the correct sort order.
How NSArray achieves this is not public but at root, the selector you define is used to compare pairs of objects from within the array, the result of which informs their placement in the sorted array.
How does one compare objects in Objective-C?
Is it as simple as == ?
I want to check an array for an object and if it doesnt exist add it to the array otherwise, remove it from the array.
Comparing objects in Objective-C works much the same as in Java or other object-oriented languages:
== compares the object reference; in Objective-C, whether they occupy the same memory address.
isEqual:, a method defined on NSObject, checks whether two objects are "the same." You can override this method to provide your own equality checking for your objects.
So generally to do what you want, you would do:
if(![myArray containsObject:anObject]) {
[myArray addObject:anObject];
}
This works because the Objective-C array type, NSArray, has a method called containsObject: which sends the isEqual: message to every object it contains with your object as the argument. It does not use == unless the implementation of isEqual: relies on ==.
If you're working entirely with objects that you implement, remember you can override isEqual: to provide your own equality checking. Usually this is done by comparing fields of your objects.
Every Objective-C object has a method called isEqual:.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSObject_Protocol/Reference/NSObject.html#//apple_ref/occ/intfm/NSObject/isEqual:
So you would want to override this for your custom object types.
One particular important note in the documentation:
If two objects are equal, they must
have the same hash value. This last
point is particularly important if you
define isEqual: in a subclass and
intend to put instances of that
subclass into a collection. Make sure
you also define hash in your subclass.
== will compare the pointer, you need to override
- (BOOL)isEqual:(id)anObject
Implement isEqual: and hash
Per the Apple documentation on NSObject you need to implement isEqual: and hash at a minimum. Below you'll find one way to implement object equality, of course how to implement hash enters the land of serious debate here on StackOverflow, but this will work. General rule - you need to define what constitutes object equality and for each unique object they should have a unique hash. It is best practice to add an object specific equality method as well, for example NSString has isEqualToString:.
- (BOOL)isEqual:(id)object
{
BOOL result = NO;
if ([object isKindOfClass:[self class]]) {
CLPObject *otherObject = object;
result = [self.name isEqualToString:[otherObject name]] &&
[self.shortName isEqualToString:[otherObject shortName]] &&
[self.identifier isEqualToString:[otherObject identifier]] &&
self.boardingAllowed == [otherObject isBoardingAllowed];
}
return result;
}
- (NSUInteger)hash
{
NSUInteger result = 1;
NSUInteger prime = 31;
result = prime * result + [_name hash];
result = prime * result + [_shortName hash];
result = prime * result + [_identifier hash];
result = prime * result + _boardingAllowed;
return result;
}
I am trying to make my app check the text in a text box and see if it matches any of the strings in an NSArray. I'm hoping you could point me in the right direction or give me some code to do it. I am pretty sure that I would need to use an if statement.
Thanks!
Send a containsObject: message to the array, passing the string you want to check for. That method will use isEqual: messages to compare your string to every object in the array. The message will return a BOOL, which you can test in your if statement.
If you want a more sophisticated definition of “matching” than looking for an equal string, you'll need to loop on the array yourself and perform your desired test on each object in the array one at a time.
Also, be aware that isEqual:/isEqualToString: comparisons are case-sensitive: #"foo" is not equal to #"Foo". If you want case-insensitive or locale-aware comparisons, you'll need to loop on the array yourself and use one of NSString's other comparison methods.
An 'if' statement is a pretty good assumption! - one of the basic tenets (if not the basic tenet) of all programming languages...
There are 101 ways you could do this, so you'll probably get a bunch of replies.
What you want to do in essence is this:
Create an NSEnumerator that will enable you to step through the array
Loop through that enumerator
Check the contents of the array each time
Note that there are all sorts of other approaches too, including using something called "fast enumeration", but I'll leave you to read up on these yourself.
Ok, so time for some example code:
NSEnumerator *enumerator;
enumerator = [myArray objectEnumerator]; // myArray is what we're checking
NSString *stringContents;
while (stringContents = [enumerator nextObject]) { // Cycles through array
if ([stringContents isEqualToString:#"SOMETHING"]) {
// We have a match! Call a method or something...
}
}
Like I said, many other ways of doing this too, but hopefully this will get you started!
Edit: If you want to examine multiple items, eg textField1, textField2 and textField3, your code could look like this (|| is the symbol for a logical "OR"):
if ([stringContents isEqualToString:textField1.text] ||
[stringContents isEqualToString:textField2.text] ||
[stringContents isEqualToString:textField3.text]) {
This would evaluate to true if any of the three textFields matched your array content. If you wanted to only evaluate to true if ALL of your statements matched, swap || for &&.
Edit 2: If you want to compare to multiple NSArrays, it makes sense to put the array enumeration and comparison into a separate method which takes an array and a string, and perhaps returns a BOOL dependent upon whether or not a match was found:
New method:
-(BOOL)checkArray:(NSArray *)myArray forString:(NSString *)myString {
NSEnumerator *enumerator;
enumerator = [myArray objectEnumerator];
NSString *stringContents;
while (stringContents = [enumerator nextObject]) { // Cycles through array
if ([stringContents isEqualToString:myString]) {
return YES;
}
}
return NO;
}
And you can then call this for multiple arrays:
BOOL result1 = [self checkArray:array1 forString:textField.text];
BOOL result2 = [self checkArray:array2 forString:textField.text];
// Etc...
Hope that all helps!...
Edit 3: If you need to fill the arrays with search terms, there is a nice convenience method for doing so:
NSArray *myArray = [NSArray arrayWithObjects:#"Item 1",#"Item 2", nil];
Note a couple of important things: each item is preceded by an # symbol if it's text in quotes, and the last item must always be the word nil. You can put as many items (pretty much) as you want.
I've come across a problem related to pointers within arrays in objective-c.
What I'm trying to do is take the pointers within an NSArray, pass them to a method, and then assign the returned value back to the original pointer(the pointer which belongs to the array).
Based on what I know from C and C++, by dereferencing the pointers within the array, I should be able to change the values they point to... Here is the code I'm using, but it is not working (the value phone points to never changes based on the NSLog output).
NSArray *phoneNumbers = [phoneEmailDict objectForKey:#"phone"];
for (NSString* phone in phoneNumbers) {
(*phone) = (*[self removeNonNumbers:phone]);
NSLog(#"phone:%#", phone);
}
And here is the method signature I am passing the NSString* to:
- (NSString*) removeNonNumbers: (NSString*) string;
As you can see, I am iterating through each NSString* within phoneNumbers with the variable phone. I pass the phone to removeNonNumbers:, which returns the modified NSString*. I Then dereference the pointer returned from removeNonNumber and assign the value to phone.
As you can tell, I probably do not understand Objective-C objects that well. I'm pretty sure this would work in C++ or C, but I can't see why it doesn't work here! Thanks in advance for your help!
Yeah, that's not going to work. You'll need an NSMutableArray:
NSMutableArray * phoneNumbers = [[phoneEmailDict objectForKey:#"phone"] mutableCopy];
for (NSUInteger i = 0; i < [phoneNumber count]; ++i) {
NSString * phone = [phoneNumbers objectAtIndex:i];
phone = [self removeNonNumbers:phone];
[phoneNumbers replaceObjectAtIndex:i withObject:phone];
}
[phoneEmailDict setObject:phoneNumbers forKey:#"phone"];
[phoneNumbers release];
You can't dereference Objective-C object variables. They are always pointers, but you should treat them as though they're atomic values. You need to mutate the array itself to contain the new objects you're generating.
NSArray is not a C/C++ style array. It's an Objective-C object. You need to use the instance methods of the NSArray class to perform operations on it.
In Objective-C you never "dereference" an object pointer to set its value.
Also, you're using what is called Fast Enumeration, which does not allow mutation.
You can also use enumerateObjectsUsingBlock:.
NSArray *array = [NSArray array];
__block NSMutableArray *mutableCopyArray = [array mutableCopy];
[mutableCopyArray enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
[mutableCopyArray replaceObjectAtIndex:idx withObject:[object modifiedObject]];
}];
Checkout How do I iterate over an NSArray?
While this may work to some degree, I haven't tested it, I'd file this under 'bad idea' and not touch. NSArray, and many other cocoa objects, a fairly complex and can have a variety of implementations under the hood as part of the class cluster design pattern.
So when it comes down to it you really won't know what you're dealing internally. NSArray is actually designed to be immutable so in place editing is even doubly a bad idea.
Objects that are designed to let you mess around with the internals expose those through api methods like NSMutableData's mutableBytes.
You're better off constructing a new NS(Mutable)Array with the processed values.