This question already has answers here:
How do I sort an NSMutableArray with custom objects in it?
(27 answers)
Closed 9 years ago.
Hopefully someone can help.
I'm adding multiple objects to a NSMutableArray and I need to sort the order based on the first element which will always be a number.
However I'm unsure how to do this?
For example:
NSMutableArray *array = [[NSMutableArray alloc] init];
NSArray *object = [NSArray arrayWithObjects: #"1",#"Test",#"Test"];
[array addObject:object];
Thanks
If your array always contains other arrays, and the first element of the innermost array is always a string containing a number, you could use the NSMutableArray method sortUsingComparator to sort your array:
[array sortUsingComparator: ^(NSArray* obj1, NSArray* obj2)
{
int value1 = [obj1[0] integerValue];
int value2 = [obj2[0] integerValue];
if (value1==value2)
return NSOrderedSame;
else if (value1 < value2)
return NSOrderedAscending;
else
return NSOrderedDescending;
}
];
In the sortUsingComparator family of methods, you supply a block of code that the sort method uses to compare pairs of objects in your array. The block uses the standard typedef NSComparator, which takes 2 objects as parameters and returns a value of type NSComparisonResult.
The code above will probably crash if all the objects in your array are not arrays of strings. (Actually it would work if the first element of each component array was an NSNumber, since NSNumber also responds to the integerValue message.)
If you are going to use this code in a very controlled environment where you can be sure that the data you are sorting is well-formed, it should work as written. If there is any chance that the objects in the array would be of a different type, or be empty, or that their first element would not respond to the integerValue messages, then you should add error checking code.
If you sort your array alphanumerically, the object #"1" will appear before any words. Keep in mind though that #"1" in your code above is a string, not a number.
As to how to sort an array, look into [NSArray sortedArrayUsingComparator:] and similar methods.
Related
I have 3 objects that might be or not initialized in a random order.
so, if objects "objectOne, "objectTwo", "objectThree" are initialized in this order with
myArray = [NSArray arrayWithObjects:objectOne,objectTwo,objectThree nil];
all objects get inside the array without problem but in my case objectOne, objectTwo might be nil and objectThree might not be nil, and in this case I would like myArray to return(count) 1.
if objectOne is nil but objectTwo and objectThree are not nil I want my array to return(count) 2.
In these 2 last cases my array always return nil. What would be the best approach to this?
There are no magic method can solve the problem for you, you need to build the array from NSMutableArray
NSMutableArray *array = [NSMutableArray array];
if (objectOne) [array addObject:objectOne];
if (objectTwo) [array addObject:objectTwo];
if (objectThree) [array addObject:objectThree];
arrays can't contain nil. There is a special object, NSNull ([NSNull null]), that serves as a placeholder for nil. You can put NSNull in an array, but I don't think that solves your problem either.
How about this:
Create an empty mutable array.
In 3 separate statements:
If objectOne is not nil, add it to the array
if objectTwo is not nil, add it to the array
If objectThree is not nil, add it to the array.
If you need your objects to be in random order, scramble the array afterwords:
for (int index = 0; index < array.count; index++)
{
int randomIndex = arc4random_uniform()
[array exchangeObjectAtIndex: index withObjectAtIndex: randomIndex];
}
This is known as a Fisher–Yates shuffle. (or a minor variation on Fisher-Yates, anyway.)
If you're doing this rarely and you aren't trying to make things neat, you can, of course, use a mutable array and either add or don't add the items one at a time in code, depending on whether they are nil.
If you're doing this frequently and you want a syntax that looks similar to the array literal notation, you can take advantage of the C preprocessor and C arrays to create a smarter NSArray class constructor that handles nil:
#define NSArrayWithCArray(array) \
[NSArray arrayWithCArray:cArray count:sizeof(cArray) / sizeof(cArray[0])];
id cArray[] = {
object1,
object2,
object3,
...
};
NSArray *array = NSArrayWithCArray(cArray);
and then define a method on NSObject that walks through the C array programmatically, dropping any nil values.
+ (NSArray *)arrayWithCArray:(__strong id[])cArray count:(NSUInteger)count {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
for (__strong id *item = cArray; item < cArray + count; item++) {
if (*item != nil) {
[array addObject:*item];
}
}
return array;
}
Note: The code above is untested, but at least close enough to give you an idea of how to do it. :-)
I have 4 values in my NSDictionary below which are 1000,640,80 and 0
It sorts the non-zero values correctly but it always gives 0 as greater than all the other values like (descending): 0,1000,640,80 Here's the code:
NSMutableDictionary *dict = [[self model] stateFromDocumentNamed:#"state"];
NSArray *values=[dict allValues];
NSMutableArray *mutvalues = [(NSArray*)values mutableCopy];
[mutvalues sortUsingSelector:#selector(compare:)];
values=[[values reverseObjectEnumerator] allObjects];
NSLog(#"%#",mutvalues);
Presumably you are comparing strings which contain numbers. To have the sort work as you expect with the numbers you will want to use the NSNumericSearch option (in the method compare:options:, probably used with sortedArrayUsingComparator:).
This is probably because the objects in your NSMutableDictionary are strings instead of numbers. Possible solutions:
You would need to convert all objects to numbers
Use a different selector to sort
sort using a block, using sortedArrayUsingComparator
EDIT: sort using method proposed in Wain's answer
Specifically:
self.words = (NSMutableArray*)[self.text componentsSeparatedByString:#" "];
works fine so long as there are separators. I see that the method returns the original string wrapped in an NSArray if there isn't, though. This single element NSArray stubbornly refuses to be cast as an NSMutableArray.
But, when I do:
self.words = [NSMutableArray arrayWithArray:self.words];
It works just fine.
Is there something I'm missing here? Is it bad practice to cast from NSArray to NSMutableArray?
You are confused.
This code:
self.words = (NSMutableArray*)[self.text componentsSeparatedByString:#" "];
...is wrong, and is setting you up for a crash later on. Casting just tells the compiler "trust me, I know what I'm doing." It does not change the type of the underlying object. The method componentsSeparatedByString returns an NSArray, not a mutable array. If you then try to mutate the resulting array, you will crash with an unrecognized selector message. With the cast, the compiler trusts you that your object will really be a mutable array at runtime, but it will not be.
This would crash:
self.words = (NSMutableArray*)[self.text componentsSeparatedByString:#" "];
[self.words addObject: #"new"];
However, this code:
self.words = [[self.text componentsSeparatedByString:#" "] mutableCopy];
[self.words addObject: #"new"];
...does the right thing. It doesn't cast a pointer, it is a method call to a method that takes an NSArray as input and returns a mutable array with the same contents. Thus the second line will work because the first line takes the immutable array it gets back from componentsSeparatedByString and uses it to create a mutable array.
It is bad practice to cast from NSArray to NSMutableArray. It may works if you are lucky because the array are constructed using NSMutableArray, but you can't rely on it.
If you want NSMutableArray from NSArray, use mutableCopy
self.words = [[self.text componentsSeparatedByString:#" "] mutableCopy];
Is it bad practice to cast from NSArray to NSMutableArray?
Yes. Bordering on nonsensical, really.
Typecasting does not change the object in any way at all, it just tells the compiler that it should regard the object as if it were an instance of the new type. At run time though, the object stays exactly the same. You may cast an NSArray to an NSMutableArray but it doesn't make the object transform into an NSMutableArray. It's still the same object of the same type.
Your misconception seems to be about the nature of casting. Casting doesn't change the object, it just tells the compiler to pretend that it's an object of that type. If the object really isn't an NSMutableArray, casting is not expected to make it become one.
NSString* string1 = #"this thing";
NSString* string2 = #"this";
NSMutableArray* array1;
NSMutableArray* array2;
array1 = (NSMutableArray*)[string1 componentsSeparatedByString:#" "];
array2 = (NSMutableArray*)[string2 componentsSeparatedByString:#" "];
[array1 addObject:string1]; //(A)
[array2 addObject:string1]; //(B)
This will break at (B) the last line with :
-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x1702257a0
But will not break at (A)1
An object that is publicly declared as immutable may have been privately created as mutable. In this case, if you cast from the immutable public type to the private mutable type, everything will work fine. Where the object was not created as mutable, such a cast will not get you what you want.
In the case of componentsSerparatedByString, this suggests that the method implementation creates a mutable array only if it needs to - i.e. if it has to add more than one object to the array. If it only finds one object, you get an NSArray, if it finds more than one, you get an NSMutableArray. This is an implementation detail that is deliberately hidden from you as the user.
The interface tells you to expect an NSArray in all cases, and in all cases this will work.
You should not rely on such details to get you what you want. Stick to the public API, that's what it is for.
1 rather, as Bryan Chen points out, it may not break now but could well do so in the future
When you do that:
self.words = (NSMutableArray*)[self.text componentsSeparatedByString:#" "];
the array still remains immutable and if you send it a message from NSMutableArray class it will crash. The trick (NSMutableArray*) is only good to make the compiler happy.
In the second case:
self.words = [NSMutableArray arrayWithArray:self.words];
you do not CAST, you CREATE a new array object.
And of course you don't need to cast the arrays this way. NSMutableArray object IS already NSArray just like any object of a derived class is an object of a base class at the same time
Use second variant in all cases because it is right solution and more clearly for users who will support your code:
NSArray *array = [self.text componentsSeparatedByString:#" "];
self.words = [NSMutableArray arrayWithArray:array];
Never do this one:
self.words = [NSMutableArray arrayWithArray:self.words];
It will totally confuse all devs =)
In the first case, the componentsSeparatedByString: method is specifically returning an NSArray, which can't just be cast to the mutable type. If you wanted that to be mutable, you would have to do this:
self.words = [[self.text componentsSeparatedByString:#" "] mutableCopy];
The second one is calling the arrayWithArray: method on the NSMutableArray class, meaning it is making an instance of NSMutableArray. That's why it works. You can cast an NSMutableArray to an NSArray, but not the other way around, because an NSMutableArray is a subclass of NSArray.
Casting does nothing with an object. example:
NSString *mouse = (id)#[#"mouse"];
it will compile, but variable mouse is not NSString. it is NSArray. you can check it simply by writing
po mouse
in console.
The only way to create mutable copy of an object is to call 'mutableCopy' method on it:
NSArray *array = #[#"a"];
NSMutableArray *mutableCopy = [array mutableCopy];
I have my array unique that is my main array and my array kind. I need to check that only 1 value of kind is present in the array unique. Then if there is more than 1 value of the array kind in unique I need to unset all values but the first one used in the array.
The further i got to achieve this is with the following code but I can not store the indexpath of the found object to do a later comparison. xcode says "bad receiver type nsinteger"
could anyone help me to achieve this?
kind = #[#"#Routine",#"#Exercise",#"#Username"];
NSMutableArray *uniqueKind = [NSMutableArray array];
for (NSString* obj in kind) {
if ( [unique containsObject:obj] ) {
NSInteger i = [unique indexOfObject:obj];
[uniqueKind addObject: [i intValue]];
}
}
An NSInteger is like an int, so you can't send it a message ([i intValue]). Also, you can't add an NSInteger to an array without making it an NSNumber or some other object type. You can do it like this:
NSInteger i = [unique indexOfObject:obj];
[uniqueKind addObject: [NSNumber numberWithInteger:i]];
Also (without understanding what you're doing) you might want to use an NSSet instead of an array. And you can combine a couple of calls:
NSUInteger i = [unique indexOfObject:obj];
if ( i != NSNotFound ) {
[uniqueKind addObject:[NSNumber numberWithInteger:i]];
}
I'm not sure if it would solve your problem, but have you considered using sets (or mutable variation) instead of arrays? They ensure uniqueness and they allow you to check for intersection/containment. See the NSSet class reference.
You have to add objects to the NSMutableArray, not the actual intValue. Try converting teh integer to a NSNumber first.
[uniqueKind addObject: [NSNumber numberWithInt:i]];
instead.
(edited )
Let's say I have an NSArray of NSDictionaries that is 10 elements long. I want to create a second NSArray with the values for a single key on each dictionary. The best way I can figure to do this is:
NSMutableArray *nameArray = [[NSMutableArray alloc] initWithCapacity:[array count]];
for (NSDictionary *p in array) {
[nameArray addObject:[p objectForKey:#"name"]];
}
self.my_new_array = array;
[array release];
[nameArray release];
}
But in theory, I should be able to get away with not using a mutable array and using a counter in conjunction with [nameArray addObjectAtIndex:count], because the new list should be exactly as long as the old list. Please note that I am NOT trying to filter for a subset of the original array, but make a new array with exactly the same number of elements, just with values dredged up from the some arbitrary attribute of each element in the array.
In python one could solve this problem like this:
new_list = [p['name'] for p in old_list]
or if you were a masochist, like this:
new_list = map(lambda p: p['name'], old_list)
Having to be slightly more explicit in objective-c makes me wonder if there is an accepted common way of handling these situations.
In this particular case Cocoa is not outdone in succinctness :)
NSArray *newArray = [array valueForKey:#"name"];
From the NSArray documentation:
valueForKey:
Returns an array containing the
results of invoking valueForKey: using
key on each of the receiver's objects.