Sorting a mutable array using a dictionary key - objective-c

I am trying to create a simple mutable array with a single key ("dayCounter") that I intend to use for sorting. I've read loads of examples on line, but no joy.
So I create this array. Note the first entry is a NSDictionary object. (The other objects are text)
cumArray = [NSMutableArray arrayWithObjects:[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter] forKey:#"dayCounter"],[[dailyArray objectAtIndex:x]objectAtIndex:0],[[dailyArray objectAtIndex:x]objectAtIndex:1],[[dailyArray objectAtIndex:x]objectAtIndex:2], nil],nil];
I save the array in a plist and everything looks great after the load.
However, when I come to sort the array, the program crashes. I have tried every combination of the following:
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"dayCounter" ascending:YES];
[cumArray sortUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
Do I need a dictionary item to act as a key? Can I sort on the first object any easier? Any help is much appreciated.

Sometimes using too many nested expressions can obscure what's really going on. For example, the 'simple' mutable array you created actually contains a nested mutable array, rather than directly containing the dictionaries you're trying to sort.
So instead of this:
cumArray = [NSMutableArray arrayWithObjects:[NSMutableArray arrayWithObjects:
[NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter] forKey:#"dayCounter"],[[dailyArray objectAtIndex:x]objectAtIndex:0],[[dailyArray objectAtIndex:x]objectAtIndex:1],[[dailyArray objectAtIndex:x]objectAtIndex:2], nil],nil];
try doing this
NSDictionary *dict1 = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:#"%i", dayCounter]
forKey:#"dayCounter"]
NSArray *objs = [dailyArray objectAtIndex:x];
NSDictionary *dict2 = [objs objectAtIndex:0];
NSDictionary *dict3 = [objs objectAtIndex:1];
NSDictionary *dict4 = [objs objectAtIndex:2];
// Note: You might want to temporarily log the values of dict2 - 4 here to make sure they're
// really dictionaries, and that they all actually contain the key 'dayCounter'.
cumArray = [NSMutableArray arrayWithObjects:dict1, dict2, dict3, dict4, nil];
Assuming that you really have a mutable array of dictionaries, each of which contains the key dayCounter, the sort descriptor you showed in your example should work just fine.

Your setup makes no sense. You are saying yourself that only the first object in the array is a dictionary that contains the key `#"dayCounter" ("The other objects are text"). How is it supposed to be sorted if only one object contains the sort criteria?

You need to sort the array with a method, like - (NSComparisunResult)compareDict
If you have to compare 2 dictionaries and determine which one should be ordered above the other ( NSOrderedAscending ) then you need to "extend" NSDictionary:
#interface NSDictionary (SortingAdditions) {}
- (NSComparisonResult)compareTo:(NSDictionary *)other;
#end
#implementation NSDictionary (SortingAddictions)
- (NSComparisonResult)compareTo:(NSDictionary *)other
{
if( [self count] > [other count] )
{ return NSOrderedAscending; }
}
#end
This method will sort NSDictionaries according to the amount of objects that they contain.
Other values you can return here are: NSOrderedDescending and NSOrderedSame.
Then you can sort the mutable array with:
[SomeMutableArray sortUsingSelector:#selector(compareTo:)];
Keep in mind that every object in the array will need to be an NSDictionary, otherwise you will get an exception: unrecognized selector sent to instance blabla
You can do the same thing for any type of object, if the array contains both NSStrings, NSNumbers and NSDictionaries you should take a different approach

Related

take nsdictionary and nsstring values from a nsdictionary

I have a function that returns an NSDictionary named data. It contains 2 objects: a NSDictionary object with the key currency_data and an NSString object with the key time.
I want to pass :
the string value with the key time to a new NSString object
the NSDictionary object with the key currency_data to a NSMutableDictionary variable.
How can I do that?
Depending on whether you use ARC. With ARC your example should work, without ARC you need to retain at least the string values.
NSDictionary *dataDict = [foo data];
NSMutableDictionary *currency_dict= [[NSMutableDictionary alloc] initWithDictionary:dataDict[#"currency_data"]];
NSString *time = [data[#"time"] retain];
What anoop-vaidy meant is I think, if you need a mutable dictionary, create that in the data call directly and pass it out. Another note: You can use your knowledge of you data structure in a better way. Instead of building a dictionary with 2 keys, use the first value ( the time string) as the key and the second (the currency dict) as the value. You can access values and keys in dictionaries quite easily
NSArray *value = dict.allValues;
NSArray *keys = dict.allKeys;

ObjC: NSDictionary allKeysForObject *containing* (is this possible?)

I am very new to objective-C and came across the NSDictionary method allKeysForObject:. Seems very useful. However, I have a NSDictionary which has several NSArrays (all of length 2) and which are keyed by NSStrings. Basically, the keys are items and the arrays define their two properties. If I wanted to pull all the item names that have a certain property, could this be done with something like allKeysForObject, or should I just loop over the dictionary and grow a mutable array (seems inefficient).
I'd include a code snippet, but I feel like this question is conceptual enough that code wouldn't really clarify anything. Oh, what the hell. Here's some simplified code:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:2],nil],#"Car",[NSArray arrayWithObjects:[NSNumber numberWithInt:2],[NSNumber numberWithInt:3],nil],#"Boat",nil];
NSLog(#"%#",[dict allKeysForObject:???]); // this is the line I am not at all sure about.
EDIT: Thank you for the responses so far. I was not clear about my question, though. I am looking for a way to do something more general. I don't want to retrieve all keys for a particular object, say [1,2], but I want to look in the dictionary for all arrays including the NSNumber 1 and return those keys. So if I added #"Plane",[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:3],nil], I'd like to somehow query for the NSNumber 1 and get #"Car" and #"Plane". I am getting the sense that this is not what this method was designed to do.
You are looking for -keysOfEntriesPassingTest:...
NSArray * selectedKeys = [dict keysOfEntriesPassingTest:^(id key, id obj, BOOL *stop)
{
return [obj containsObject:[NSNumber numberWithInt:2]];
}];
In your example, if you call
[dict allKeysForObject: [NSArray arrayWithObjects:
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
nil]]]
you should get an array containing just #"Boat"

Create an NSMutableArray that contains several NSMutableDictionary objects

I've looked around and seen several postings concerning sorting NSMutableArray's that contain NSDictionaries, but haven't been able to find some concrete examples of how to actually make an NSMutableArray composed of NSDictionaries.
I am using NSUserDefaults to keep track of a user's preferences for several different items. The NSMutableArray will be used to keep track of the total items, and the NSDictionaries are for the individual preferences concerning each item.
I've written these methods to keep track of items in my preferences class, but I'm not too sure how to initialize the NMutableArray to contain NSDictionaries.
-(NSMutableArray *)deviceListArray
{
return [[NSUserDefaults standardUserDefaults] mutableArrayValueForKey:#"device_list_array"];
}
-(void) setDeviceListArray:(NSMutableArray *)deviceListArray
{
[[NSUserDefaults standardUserDefaults] setObject:deviceListArray forKey:#"device_list_array"];
}
If anyone could point me in the right direction, I would really appreciate it. Thank you very much! Or if someone has a better strategy for keeping track of items, each with different preferences, then that would be awesome too!
Thanks!!
Once you hand an object of to userDefaults, it owns that object - so you cannot go changing properties of dictionaries inside it.
What you can do is a two pass copy, where you have a local mutable array with mutable dictionaries in it. You would create a new mutable array of the same size, then for each mutable dictionary, call [NSDictionary dictionaryWithDictionary:], adding the new non-mutable dictionary to the new array.
If you have mutable objects buried in the dictionaries, well, you need to do the same for that.
All that said, if you have such a huge amount of stuff I suspect you have no properly thought of how to use the defaults system. I almost always just save strings, numbers, colors etc.
EDIT: code
NSMutableArray *array = [NSMutableArray arrayWithCapacity:10];
for(int i=0; i<10; ++i) {
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:10];
// add stuff to the dictionary
[array addObject:dict];
}
// later on want to change second dictionary:
NSMutableDictionary *dict = [array objectAtIndex:1];
// fool around with dict
// no need to re-save it in array, since its mutable
Not sure if this is what you were looking for:
#define kDeviceKey1 #"DeviceKey1"
#define kDeviceKey2 #"DeviceKey2"
#define kDeviceKey3 #"DeviceKey3"
#define kDeviceKey4 #"DeviceKey4"
#define kDeviceID #"DeviceID"
NSMutableArray *devices = [[NSMutableArray alloc]init];
NSMutableDictionary *deviceInfo = [[NSMutableDictionary alloc]init];
// create a dictionary record for earch device which contains thing...
// probably employ some kind of loop
deviceInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:1], kDeviceID,
// #"DeviceName1", kDeviceID, // or this
#"whateverthing1forDevice1", kDeviceKey1,
#"whateverthing2forDevice1", kDeviceKey2,
#"whateverthing3forDevice1", kDeviceKey3,
#"whateverthing4forDevice1", kDeviceKey4,
nil];
[devices addObject:deviceInfo];
// this is just an example, you would have some kind of loop to replace "whateverthing1forDevice1" with actual data
deviceInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:2], kDeviceID,
// #"DeviceName2", kDeviceID, // or this
#"whateverthing1forDevice2", kDeviceKey1,
#"whateverthing2forDevice2", kDeviceKey2,
#"whateverthing3forDevice2", kDeviceKey3,
#"whateverthing4forDevice2", kDeviceKey4,
nil];
[devices addObject:deviceInfo];
NSLog(#"devices: %#", devices);

Retrieving NSArray From an NSDictionary

I am trying to get an array full of my data, I keep getting an BAD_ACCESS error when I run this though at the calling the array which I have not included here but I even commented that code out and tried just calling it to the log and still get the BAD_ACCESS error. The array is stored in a dictionary that contains a one key that is a number. I am not sure what I am doing wrong here.
ISData *is = [[ISData alloc] init];
NSDictionary *dic = [is getData:#"right" : isNumber];
NSArray *array = [[NSArray alloc] initWithArray:[dic valueForKey:#"2"]];
NSString *out = [array objectAtIndex:0];
How the dictionary is created:
NSNumber* key = [NSNumber numberWithInt:isNumber];
NSArray *values = [NSArray arrayWithObjects:[NSString stringWithUTF8String:name], [NSString stringWithUTF8String:desc], [NSString stringWithUTF8String:intent], nil];
[dic setObject:values forKey:key];
You don't say exactly where it crashes, and you don't have an obvious crashing bug here, so it's hard to diagnose your actual issue. This could be a memory management thing that's outside the code you've presented. But a couple things are going on here that are suspicious:
You should never have a bare [MyClass alloc] without the -init call. Your init should call super's init, which is responsible for setting up the new object.
Your -valueForKey: should be -objectForKey:. The difference is probably unimportant in this case, but the former is used for "KVC" coding, which you're not using. If you set it as object, get it as object.
Your #"2" as the key into the dictionary doesn't match your input, which is an NSNumber. NSNumbers are not string versions of numbers, so you're unlikely to find any value there. Instead, use the same [NSNumber numberWithInt:2] pattern.
It is most likely that your array is empty. You can try print NSLog(#"count = %d", array.count); to see if that's the case.
If your dic is set up in the second block of code, then what's that NSDictionary *dic = [is getData:...] thing in the first block?
And is there a reason you cannot set up your array directly? Is there a reason for you to use a dictionary when it has only one key?

Terminology question regarding looping thru an NSArray in Objective-C

When you have an NSArray and you want to evaluate and change the elements, you can't change the array from inside the loop. So, you create a mutable copy that can be changed.
code example:
NSMutableArray *bin = [NSMutableArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", #"5", #"6", #"7", nil];
NSMutableArray *list = [NSMutableArray arrayWithObjects:#"a1", #"b2", #"c3", #"e4", nil];
NSMutableArray *listHolder = list; // can't mutate 'list' within loop so create a holder
for (int i = 0; i < [list count]; i++) {
[listHolder replaceObjectAtIndex:i withObject:[bin objectAtIndex:i]];
}
What is that second array listHolder called? I mean, what term is used to refer to an array in this context.
This is perfectly valid:
NSMutableArray *bin = [NSMutableArray arrayWithObjects:#"0", #"1", …, #"7", nil];
NSMutableArray *list = [NSMutableArray arrayWithObjects:#"a1", …, #"e4", nil];
// NSInteger should be used instead of int
for (NSInteger i = 0; i < [list count]; i++) {
[list replaceObjectAtIndex:i withObject:[bin objectAtIndex:i]];
}
You're not allowed to change the array inside a for … in or NSEnumerate loop, but using an index is perfectly valid.
What troubles me is your misunderstanding of pointers.
If it were a loop in which you weren't allowed to mutate the array this wouldn't copy the array but only the pointer to the array, effectively modifying the array you're not allowed to. (I'm not even sure if this works.)
Instead of just copying the pointer
// can't mutate 'list' within loop so create a holder
NSMutableArray *listHolder = list;
make a true copy:
NSMutableArray *copy = [[list mutableCopy] autorelease];
In case I really have to make a copy I try to name it according to its content. For example:
NSMutableArray *views;
NSMutableArray *reorderedViews = [views mutableCopy];
// reorder reorderedViews
Sometimes it's hard to find a good enough name, then I usually just use nameCopy.
In this context listHolder would be called a copy.
Your code has a bug though. This line is not actually making a copy, it is only letting listHolder and list both reference the same array object:
NSMutableArray *listHolder = list;
This would be an actual copy:
NSMutableArray *listHolder = [list mutableCopy];
Make sure that you use mutableCopy and not just copy if you want the copy to be mutable. The copy method will return immutable variants on all mutable classes such as NSMutableSet, NSMutableDictionary, and so forth.
Also as others have noted it is only inside the for (item in collection) loop that the enumerated collection can not be mutated. In a normal for (;;) mutation is perfectly ok, but can lead to strange result if the number of items in the collection changes.
There is not specific stylistic or common name for this that is universally used, it is your code afterall, and if there appropriate terms for them use them.
Having said that generally if you don't have specific names in this sort of situation then people refer to the original list as the "source" (src) and the final list as "destination" (dst), just like in a memory blitting style operation.
A temporary mutable copy of the original NSArray would be how I would refer to it.