According to NSArray class reference there are 4 type of methods to sort array:
1- sortedArrayUsingComparator:
2- sortedArrayUsingSelector:
3- sortedArrayUsingFunction:context:
4- sortedArrayUsingDescriptors:
For first three methods it mentioned :
The new array contains references to the receiving array’s elements, not copies of them.
But for the forth method (descriptor) it mentioned:
A copy of the receiving array sorted as specified by sortDescriptors.
But following example shows like the other 3 methods, descriptor also retain original array and do not return a new copy of it:
NSString *last = #"lastName";
NSString *first = #"firstName";
NSMutableArray *array = [NSMutableArray array];
NSDictionary *dict;
NSMutableString *FN1= [NSMutableString stringWithFormat:#"Joe"];
NSMutableString *LN1= [NSMutableString stringWithFormat:#"Smith"];
NSMutableString *FN2= [NSMutableString stringWithFormat:#"Robert"];
NSMutableString *LN2= [NSMutableString stringWithFormat:#"Jones"];
dict = [NSDictionary dictionaryWithObjectsAndKeys: FN1, first, LN1, last, nil];
[array addObject:dict];
dict = [NSDictionary dictionaryWithObjectsAndKeys: FN2, first, LN2, last, nil];
[array addObject:dict];
// array[0].first = "Joe" , array[0].last = "Smith"
// array[1].first = "Robert" , array[1].last = "Jones"
NSSortDescriptor *lastDescriptor =[[NSSortDescriptor alloc] initWithKey:last
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *firstDescriptor =[[NSSortDescriptor alloc] initWithKey:first
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *descriptors = [NSArray arrayWithObjects:lastDescriptor, firstDescriptor, nil];
NSArray *sortedArray = [array sortedArrayUsingDescriptors:descriptors];
// array[1] == sortedArray[0] == ("Robert" , "Jones")
// comparing array entries whether they are same or not:
NSLog(#" %p , %p " , [array objectAtIndex:1] , [sortedArray objectAtIndex:0] );
// 0x10010c520 , 0x10010c520
it shows objects in both arrays are same,
"A copy of the receiving array sorted as specified by sortDescriptors" means that the array object is copied not the elements in the array. The reason the documentation uses the word "copy" is to make it clear that the returned array is not the same array instance as the receiver.
Elements in an array are never copied in Cocoa with the exception of initWithArray:copyItems:YES which will copy the first level items in the original array to the new array. Even then, this copy is done by calling copyWithZone: on the elements, so caveats apply depending on what elements are in your array.
Note that Cocoa is reference counted, so the concept of "deep copies" is not inherently built in for a reason. This is also (in part) the reason why array objects in cocoa come in two flavors (NSArray and NSMutableArray) and are usually immutable (NSArray) instead of as in other languages where there is not usually a concept of immutable and mutable arrays.
see this SO answer for how to get a "deep copy" of an NSArray.
Related
I have a large mutable array with lots of duplicate values in alphabetical order.
I need to be able to convert my array *Array into a new array that contains one entry for each string variant.
I am currently using:
NSArray *array = [NSArray arrayWithObjects:papersObject.paperSubject, nil];
NSCountedSet *paperSet = [[NSCountedSet alloc] initWithArray:array];
NSMutableArray *namesArray = [[NSMutableArray alloc] initWithCapacity:[array count]];
[namesSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop){
if ([paperSet countForObject:obj] == 1) {
[namesArray addObject:obj];
}
}];
NSLog(#"%#", namesArray);
But this returns a long list of the same array, still with duplicates.
Any ideas?
What about:
NSArray *arrayWithNoDuplicates = [[NSSet setWithArray:papersObject.paperSubject] allObjects];
A. What is namesSet? paperSet?
B. However:
NSOrderedSet *set = [NSOrderedSet orderedSetWithArray:array];
NSArray *arrayWithUniquesIsAnOrderedSet = set.array;
BTW: I would highly recommend to use an ordered set instead of an array, because an array with unique objects is an ordered set.
I will be putting a variety of things in this mutable array, but first I am just trying to make sure it works by putting in strings, and then pulling out the strings. Here is my code
str1=#"1";
str2=#"2";
str3=#"3";
NSMutableArray *testArray;
[testArray addObject:str1];
[testArray addObject:str2];
[testArray addObject:str3];
retrieve =[testArray objectAtIndex:1];
NSLog(#"the test number is %#",retrieve);
The problem is that my string:retrieve equals "null" after receiving the string from the array. I'm not sure what I am doing wrong, I've looked at Apple's documentation but I'm having trouble making sense of it. I know I must be interacting with the array incorrectly, but I'm not sure how exactly. Help will be appreciated.
-Thank you!
You did not initialize your testArray:
NSMutableArray *testArray = [NSMutableArray array];
You can populate the array using new syntax. If you needed mutability only to add the three items, you could use a non-mutable array instead, like this:
NSArray *testArray = #[ #"1", #"2", #"3"];
If you do need mutability, call mutableCopy:
NSMutableArray *testArray = [#[ #"1", #"2", #"3"] mutableCopy];
Well, that's because testArray is nil. you should change the 4th line to
NSMutableArray *testArray = [NSMutableArray array];
You array is nil.
You are missing
NSMutableArray *testArray = [NSMutableArray array];
To clarify what others are telling you:
This line
NSMutableArray *testArray;
Does not create an array. It creates a pointer variable that can be used to point to a mutable array. It starts out containing a zero value (nil, points to nothing.)
It's like a postal address that points to an empty lot.
You need to create (allocate) and initialize a mutable array object in order to use it. (Continuing our analogy, you have to build a house and put a mailbox in front of it before the address becomes valid.)
So you need to say:
testArray = [#[ #"1", #"2", #"3"] mutableCopy];
Breaking that down:
The inner part,
#[ #"1", #"2", #"3"]
Creates an immutable array that contains 3 string objects.
Then we ask the immutable array to create a mutable copy of itself. We save the address of that newly created mutable array into the pointer variable testArray.
We could do it in 3 steps:
NSMutableArray *testArray;
NSArray *tempArray = #[ #"1", #"2", #"3"];
testArray = [tempArray mutableCopy];
Or all at once, like #dasblinkenlight's code:
NSMutableArray *testArray = [#[ #"1", #"2", #"3"] mutableCopy];
I have an array which contains multiple Dictionaries each one with 3 keys (#"date", #"username", #"text").
What I want to check for, is whether the same user (#"username") exists in more than one dictionary in that Array. And, if she does, combine the text for those "duplicates" into one dictionary.
I have considered this answer to check for duplicates and this one
but I cannot figure out how to combine these two.
Jumping in here because although I think you should work on the code yourself first, I think Miro's answer is more complicated than the issue requires and though I like the idea of using predicates in Greg's answer, here's a 3rd solution that (1) wouldn't require you to change your data structure and (2) references the necessary loops...
The way I'd do it: Create an NSMutableArray then start adding the usernames in order. If the NSMutableArray already contains the username though, don't add another instance of the username, but instead merge the dictionary info.
ex.
// Note: I'm calling your array of user dictionaries userArray.
// Create a username array to store the usernames and check for duplicates
NSMutableArray *usernames = [[NSMutableArray alloc] init];
// Create a new userArray to store the updated dictionary info, merged
// entries et. al.
NSMutableArray *newUserArray = [[NSMutableArray alloc] init];
// Go through the array of user dictionaries
for (NSDictionary *userDict in userArray) {
// If the usernames array doesn't already contain the username,
// add it to both the usernames array and the newUserArray as is
if (![usernames containsObject:[userDict objectForKey:#"username"]]) {
[usernames addObject:[userDict objectForKey:#"username"]];
[newUserArray addObject:userDict];
}
// Otherwise, merge the userArray entries
else {
// Get a mutable copy of the dictionary entry at the first instance
// with this username
int indexOfFirstInstance = [usernames indexOfObject:[userDict objectForKey:#"username"]];
NSMutableDictionary *entry = [[newUserArray objectAtIndex:indexOfFirstInstance] mutableCopy];
// Then combine the "text" or whatever other values you wanted to combine
// by replacing the "text" value with the combined text.
// (I've done so with a comma, but you could also store the value in an array)
[entry setValue:[[entry objectForKey:#"text"] stringByAppendingString:[NSString stringWithFormat:#", %#", [userDict objectForKey:#"text"]]] forKey:#"text"];
// Then replace this newly merged dictionary with the one at the
// first instance
[newUserArray replaceObjectAtIndex:indexOfFirstInstance withObject:entry];
}
}
Maybe something like this [untested] example? Loop through, maintain a hash of existing items, and if a duplicate is found then combine with existing and remove.
NSMutableArray main; // this should exist, with content
NSMutableDictionary *hash = [[NSMutableDictionary alloc] init];
// loop through, backwards, as we're attempting to modify array in place (risky)
for(int i = [main count] - 1; i >= 0; i--){
// check for existing
if(hash[main[i][#"username"]] != nil){
int existingIdx = [hash[main[i][#"username"]] integerValue]; // get existing location
main[existingIdx][#"text"] = [main[existingIdx][#"text"] stringByAppendingString:main[i][#"text"]]; // "combine text" .. or however you'd like to
[main removeObjectAtIndex:i]; // remove duplicate
} else {
[hash setValue:[[NSNumber alloc] initWithInt:i] forKey:main[i][#"username"]]; // mark existance, with location
}
}
If you use NSMutableDictionary, NSMutableArray and NSMutableString you can do it with predicate like that:
NSMutableDictionary *d1 = [#{#"username": #"Greg", #"text" : [#"text 1" mutableCopy]} mutableCopy];
NSMutableDictionary *d2 = [#{#"username": #"Greg", #"text" : [#"text 2" mutableCopy]} mutableCopy];
NSMutableDictionary *d3 = [#{#"username": #"John", #"text" : [#"text 3" mutableCopy]} mutableCopy];
NSMutableArray *array = [#[d1, d2, d3] mutableCopy];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username = %#", #"Greg"];
NSArray *filterArray = [array filteredArrayUsingPredicate:predicate];
NSMutableDictionary * firstDict = filterArray[0];
for (NSDictionary *d in filterArray)
{
if (firstDict != d)
{
[firstDict[#"text"] appendString:d[#"text"]];
[array removeObject:d];
}
}
I have an NSDictionary which contains several arrays with several strings in them. I want to put all strings under each array in one single array. How can I receive all the strings at once? I've tried this:
NSMutableArray *mutarr = [[NSMutableArray alloc] initWithObjects:[self.firstTableView allValues], nil];
NSArray *mySorted = [[NSArray alloc]init];
mySorted = [mutarr sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSLog(#"First table view values: %#", mySorted);
NOTE: self.firsttableview is a NSDictionary like this:
NSString *path = [[NSBundle mainBundle] pathForResource:#"firstTableView" ofType:#"plist"];
self.firstTableView = [NSDictionary dictionaryWithContentsOfFile:path];
EFFECT: This gives me a list in NSLog, but it isn't in alphabetic order.
You are initializing your mutarr with one object which is the array returned by allValues on your dictionary. Instead try this:
NSMutableArray* mutarr = [NSMutableArray array];
for (NSArray* array in self.firstTableView.allValues) {
[mutarr addObjectsFromArray:array];
}
Your code is going to mean that mutarr is an array containing an array of arrays, not an array of strings. You should be looping over the array values from the dictionary and adding the items from each one to your mutarr to make it an array of strings that you can sort.
If your NSDictionary values are arrays then you are trying to sort the arrays instead of the strings. Try to create a for loop to iterate [self.firstTableView allValues] then add all values in that array to your main to sort array.
I have a
NSDictionary* dict = [[NSDictionary alloc]initWithObjectsAndKeys::arrayOne,#"Plants",arrayTwo,#"Animals"),arrayThree,#"Birds",nil];`
self.displayArray =[[dict allKeys] sortedArrayUsingSelector:#selector(compare:)];
Everything works fine, I am able to see all the key value/pair in the table but they are in sorted order. i.e Animals,Birds,Plants.
But I want to display as Plants,Animals,Birds.
Can anyone tell me how to sort the array in my customized order?
I have googled and found that we can use NSSortDescriptor for sorting. But I am not very clear with that. Can anyone help me ?
As your ordering doesnt follow any natural order, a simple solution could be to keep track of the order with another array
NSArray *array1 = [NSArray arrayWithObjects:#"rose",#"orchid",#"sunflower",nil];
NSArray *array2 = [NSArray arrayWithObjects:#"dog", #"cat",#"ogre",#"wookie", nil];
NSArray *array3 = [NSArray arrayWithObjects:#"parrot",#"canary bird",#"tweety",#"bibo",nil];
NSArray *keys = [NSArray arrayWithObjects:#"Plants",#"Animals",#"Birds", nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
array1,[keys objectAtIndex:0],
array2,[keys objectAtIndex:1],
array3,[keys objectAtIndex:2],
nil];
for (NSString *key in keys) {
NSLog(#"%# %#", key, [dict objectForKey:key]);
}
Matt shows in his fantastic blog, how to create a ordered dictionary, that essentially uses another array to keep the order just as I showed here: OrderedDictionary: Subclassing a Cocoa class cluster
You're on the right track, Cyril.
Here is some Apple documentation on "Creating and using Sort Descriptors"
Basically you need to subclass NSSortDescriptor and in your subclass, implement your own "compare:" method (you can actually name it anything you want; it needs to return a "NSComparisonResult") that somehow logically returns "Plants" before "Animals".