Get object by key with certain value from NSDictionary - objective-c

I have NSDictionary with objects. The NSDictionary is consist of json(You can see it below). I need to populate my table view with the name by id. And the id can repeat. It means I can have several "name" with id which is equal 0. I should get name by key with certain value from the dictionary. Here is my NSDictionary:
{
name = "smth1";
id = 0;
},
{
name = "smth2";
id = 1;
},
{
name = "smth3";
id = 2;
},
{
name = "smth4";
id = 2;
},
...
For example, I want to get value of key "name" where id is 2. Then I will get name = "smth3" and name = "smth4". Generally, I am trying to populate my table view component with the nested data. How can I do this? Any tips, ideas. Thank you.

Actually you show an array of dictionaries, where each dictionary has 2 keys.
You could use the NSArray function indexOfObjectPassingTest. That code might look like this (starting from an NSArray) :
int idToFind = 2;
NSArray *dictArray = #[#{#"name": #"smth1",
#"id": #(0)},
#{#"name": #"smth2",
#"id": #(1)},
#{#"name": #"smth3",
#"id": #(2)}
];
NSUInteger dictIndex =
[dictArray indexOfObjectPassingTest: ^BOOL(
NSDictionary *dict,
NSUInteger idx,
BOOL *stop) {
return [dict[#"id"] intValue] == idToFind;
}];
if (dictIndex != NSNotFound) {
NSLog(#"Found id %d with name %#", idToFind, dictArray[dictIndex][#"name"]);
}
}
EDIT:
If you need to match all of the items then you need to use the NSArray method indexesOfObjectsPassingTest. That finds all the items in an array that match.
That code would look like this:
int idToFind = 2;
NSArray *dictArray = #[#{#"name": #"smth1",
#"id": #(0)},
#{#"name": #"Fred",
#"id": #(2)},
#{#"name": #"smth2",
#"id": #(1)},
#{#"name": #"smth3",
#"id": #(2)},
];
NSIndexSet *dictIndexes;
dictIndexes =
[dictArray indexesOfObjectsPassingTest: ^BOOL(NSDictionary *dict,
NSUInteger idx,
BOOL *stop)
{
return [dict[#"id"] intValue] == idToFind;
}];
if (dictIndexes.count == 0) {
NSLog(#"No matches found");
}
[dictIndexes enumerateIndexesUsingBlock:^(NSUInteger index,
BOOL * _Nonnull stop)
{
NSLog(#"Found id=%# with name \"%#\" at index %lu",
dictArray[index][#"id"],
dictArray[index][#"name"],
index);
}];
Both approaches above use methods that take a block. The block contains code that you write that returns a BOOL for the item(s) that match your desired search criteria.
Search/sort methods that take blocks are very flexible because you can provide any code you want to do the matching/comparison.
The indexOfObjectPassingTest method searches for a single object in your array and stops when it finds the first match.
In contrast, the indexesOfObjectsPassingTest function will match multiple items. It returns an NSIndexSet, a special class that's used to index into NSArrays.
There is a function enumerateIndexesUsingBlock that invokes a block of code for each index specified in the array.
We could also have used the method objectsAtIndexes to extract only the elements in the array that are listed in the resulting index set, and then used for...in to loop through the items. That code would look like this:
NSArray *filteredArray = [dictArray objectsAtIndexes: dictIndexes];
for (NSDictionary *aDict in filteredArray) {
NSLog(#"Found id=%# with name \"%#\"", aDict[#"id"], aDict[#"name"]);
}
Note that this sort of thing is simpler and cleaner in Swift. We could use a filter statement on the array and provide a closure that selects items that match our search criteria

Related

How can I create an NSMutableDictionary from an NSMutableArray?

I'm trying to figure out the best way to sort an NSMutableDictionary. I have a dictionary of Card keys (i.e. aceSpades) that store Card values (i.e. 14). I have then been using an NSMutableArray to shuffle the 52 Card keys into an array called shuffledCards. Finally I make another array from shuffledCards thats takes a portion (15) of shuffledCards and puts them into an array called computerHand.
The new array computerHand is not good enough because I need to be able to connect the Card values with the Card keys. What I really need to do is create a new NSMutableDictionary for computerHand from the array shuffledCards so that I can sort it by Card values and still be able to retrieve the Card keys.
I'm thinking I need something like this, where currentCard is the first card of the shuffedCards array:
if (currentCard == 1) {
[compHandDictionary setObject:[[highCardDictionary
valueForKey:[shuffledCards objectAtIndex:currentCard]] intValue]
forKey:[cardsShuffled objectAtIndex:currentCard]];
}
However this is not allowed because "int" to "id" is not allowed.
There might be a better way but I have not been able to find anything. Any help would be appreciated.
...
I got this to work by modifying jstevenco's answer. I created two arrays and formed a new dictionary for the computer hand of just the 15 cards. Then to sort I used:
NSArray* sortedKeys = [newDict keysSortedByValueUsingComparator: ^(id obj1, id obj2) {
if ([obj1 integerValue] > [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedAscending;
}
if ([obj1 integerValue] < [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}
return (NSComparisonResult)NSOrderedSame;
}];
Thanks all!
You can sort a dictionary's keys using:
NSArray* sortedKeys = [[dict allKeys] sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
If you then want to create a sorted array from this you can use:
NSArray* objects = [dict objectsForKeys:sortedKeys notFoundMarker:[NSNull null]];
Consider using an array of dictionaries, where each dictionary contains the name and value for a given card, for example like so:
NSArray *cards = #[#{#"name" : #"Queen", #"value" : #12},
#{#"name" : #"Jack", #"value" : #11},
#{#"name" : #"Ace", #"value" : #14},
#{#"name" : #"King", #"value" : #13}];
You could then easily sort the cards array as shown below:
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"value" ascending:YES];
NSArray *sortedCards = [cards sortedArrayUsingDescriptors:#[sortDescriptor]];
NSLog(#"\n\n%#\n\n", sortedCards);
Output of the NSLog statement above would be as follows:
(
{
name = Jack;
value = 11;
},
{
name = Queen;
value = 12;
},
{
name = King;
value = 13;
},
{
name = Ace;
value = 14;
}
)
To obtain an array of card names, you could then simply send a valueForKey: message to the sorted array of card dictionaries:
NSArray *sortedNames = [sortedCards valueForKey:#"name"];
NSLog(#"\n\n%#\n\n", sortedNames);
The output of the preceding NSLog statement would be:
(
Jack,
Queen,
King,
Ace
)

Search String in NSDictionary store in NSMutableArray

I am trying to search a String in NSDictionary stored in NSMutableArray
for (int k = 0; k < [onlyActiveArr count]; k++) {
NSString *localID = [onlyActiveArr objectAtIndex:k];
NSLog(#"%#",localID);
int localIndex = [onlyActiveArr indexOfObject:localActiveCallID];
NSLog(#"%d",localIndex);
for (NSDictionary *dict in self.SummaryArr) {
NSLog(#"%#",[dict objectForKey:#"ActiveID"]);
if (![[dict objectForKey:#"ActiveID"] isEqualToString:localID]) {
NSLog(#"found such a key, e.g. %#",localID);
}
}
}
But I am getting
NSLog(#"found such a key, e.g. %#",localActiveCallID);
when the ID is still there in SummaryArr, I am checking if localID retrieved from onlyActiveArr is not present in dictionary.
Please suggest me how to overcome my problem.
You cannot make a decision that a key is not present until you finish processing the entire dictionary. Make a boolean variable initially set to NO, and change it to YES if you find an item in the dictionary, like this:
BOOL found = NO;
for (NSDictionary *dict in self.SummaryArr) {
NSLog(#"%#",[dict objectForKey:#"ActiveID"]);
found = [[dict objectForKey:#"ActiveID"] isEqualToString:localID];
if (found) break;
}
if (!found) {
NSLog(#"found such a key, e.g. %#",localID);
}
If you like predicates, then you can use the fact that accessing an inexistent key in a dictionary produces a nil value and make a predicate that filters out those dictionaries that have nil for your key.
If the count of the result is larger than zero, your key is somewhere in the array. It won't tell you where, though.
A snippet to show the idea:
NSPredicate *keyPred = [NSPredicate predicateWithFormat: #"NOT ActiveID == nil"];
BOOL found = [[self.SummaryArr filteredArrayUsingPredicate: keyPred] count] > 0;
I don't know how the performance stacks up, so if your data set is large you may want to check that the execution time is within your limits.

What is the best way to build a one-to-many relationship?

I have an array of Videos objects with, among other things, the properties id and tags.
I want to build a dictionary whose key is a tag and whose value is an array of id's.
For example, some Video objects might look like this:
Video{ id:1, tags:[funny,political,humor] }
Video{ id:2, tags:[political,america] }
I want the result dictionary to look like this:
VideosWithTags["funny":[1]; "political":[1,2]; "humor":[1]; "america":[2]]
Is there a standard algorithm to accomplish this?
Currently I'm doing something like this:
for (NSDictionary *video in videos)
{
NSNumber *videoId = [video objectForKey:#"id"];
NSArray *tags = [video objectForKey:#"tags"];
for (NSString *tag in tags)
{
NSMutableArray *videoIdsForTag = nil;
if ([videosAndTags objectForKey:tag] != nil) //same tag with videoIds already exists
{
videoIdsForTag = [videosAndTags objectForKey:tag];
[videoIdsForTag addObject:videoId];
//add the updated array to the tag key
[videosAndTags setValue:videoIdsForTag forKey:tag];
}
else //tag doesn't exist yet, create it and add the videoId to a new array
{
NSMutableArray *videoIds = [NSMutableArray array];
[videoIds addObject:videoId];
//add the new array to the tag key
[videosAndTags setObject:videoIds forKey:tag];
}
}
}
You can make this look a little cleaner by using the new literal syntax.
I think you could benefit by making your if branches do less work. e.g. You would be better of trying to retrieve the videoIds array then if it doesn't exist - create it and add it to the videosAndTags object and then the code after this point can be consistent with no duplication of logic
for (NSDictionary *video in videos) {
NSNumber *videoId = video[#"id"];
NSArray *tags = video[#"tags"];
for (NSString *tag in tags) {
NSMutableArray *videoIds = videosAndTags[tag];
if (!videoIds) {
videoIds = [NSMutableArray array];
videosAndTags[tag] = videoIds;
}
// This is the only line where I manipulate the array
[videoIds addObject:videoId];
}
}
NSArray* videos =
#[#{ #"id" : #1, #"tags" : #[ #"funny", #"political", #"humor" ] },
#{ #"id" : #2, #"tags" : #[ #"political", #"america" ] } ];
NSMutableDictionary* videosAndTags = [NSMutableDictionary new];
// find distinct union of tags
NSArray* tags = [videos valueForKeyPath: #"#distinctUnionOfArrays.tags"];
// for each unique tag
for( NSString* tag in tags )
{
// filter array so we only have ones that have the right tag
NSPredicate* p = [NSPredicate predicateWithFormat: #"tags contains %#", tag];
videosAndTags[ tag ] = [[videos filteredArrayUsingPredicate: p] valueForKeyPath: #"id"];
}
Here is another approach using NSPredicate and valueForKeyPath.
I don't used them often, but sometimes they can prove to be useful.
(I think they call this the Functional Programming style of things, but I am not so sure)
NSPredicate reference
Key Value Coding

Finding minimum and maximum values in a nested NSDictionary

I have a Person NSDictionary, whose key is the Name of the person, and the object is an NSDictionary with two keys: his nickname (NSString) and his age (NSNumber).
I would like to end up with the Person dictionary sorted by the ascending order of their age, so that I could get the name of the youngest and the oldest person.
What is the best way to do it?
Thanks!
There are a few convenience methods defined in NSDictionary to sort items by values and get back the sorted keys.
See docs,
keysSortedByValueUsingComparator:
keysSortedByValueUsingSelector:
keysSortedByValueWithOptions:usingComparator:
I'm guessing you're using the modern Objective-C syntax and the age is actually represented as numbers. Here's how it looks:
[people keysSortedByValueUsingComparator:(NSDictionary *firstPerson, NSDictionary *secondPerson) {
return [firstPerson[#"age"] compare:secondPerson[#"age"]];
}];
Some languages offer sorted dictionaries, but the standard NSDictionary is inherently unsorted. You can get all the keys, sort the key array and then walk over the dictionary according to the sorted keys. (NSDictionary has several convenience methods for this use case that I didn’t know about, see Anurag’s answer.)
Your case is a bit more complex, one way to solve it is to introduce a temporary dictionary mapping ages to names. But if you’re only after the minimum and maximum ages, just iterate over all persons and keep track of the maximum & minimum ages and names:
NSString *oldestName = nil;
float maxAge = -1;
for (NSString *name in [persons allKeys]) {
NSDictionary *info = persons[name];
float age = [info[#"age"] floatValue];
if (age > maxAge) {
oldestName = info[#"nick"];
maxAge = age;
}
}
And if we get back to the idea of sorting the dictionary, this could work:
NSArray *peopleByAge = [people keysSortedByValueUsingComparator:^(id a, id b) {
// Again, see Anurag’s answer for a more concise
// solution using the compare: method on NSNumbers.
float ageA = [a objectForKey:#"age"];
float ageB = [b objectForKey:#"age"];
return (ageA > ageB) ? NSOrderedDescending
: (ageB > ageA) ? NSOrderedAscending
: NSOrderedSame;
}];
As #Zoul said the standard NSDictionary is unsorted.
To sort it you can use an array, and I do things like that
//the dictionary is called dict : in my case it is loaded from a plist file
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
//make a dicoArray that is sorted so the results are sorted
NSArray *dicoArray = [[dict allKeys] sortedArrayUsingComparator:^(id firstObject, id secondObject) {
return [((NSString *)firstObject) compare:((NSString *)secondObject) options:NSNumericSearch];
}];
check the help for all the sort options. In the presented case the dictionary is sorted with keys treated as numeric value (which was the case for me).
If you need to sort another way the list of sort possibilities is
enum {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2,
NSBackwardsSearch = 4,
NSAnchoredSearch = 8,
NSNumericSearch = 64,
NSDiacriticInsensitiveSearch = 128,
NSWidthInsensitiveSearch = 256,
NSForcedOrderingSearch = 512,
NSRegularExpressionSearch = 1024
};
In iOS 9.2
// Dictionary of NSNumbers
NSDictionary * phoneNumbersDict = #{#"400-234-090":67,#"701-080-080":150};
// In Ascending Order
NSArray * keysArraySortedByValue = [phoneNumbersDict keysSortedByValueUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
// In Descending Order
NSArray * keysArraySortedByValue = [phoneNumbersDict keysSortedByValueUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj2 compare:obj1];
}];
Here is the enum for NSComparisonResults.
enum {
NSOrderedAscending = -1,
NSOrderedSame,
NSOrderedDescending
};
typedef NSInteger NSComparisonResult;
Look at the NSDictionary's method that returns keys sorted by a selector. There are more than one such method. You get an array of sorted keys, then access the first and last and have your youngest and oldest person.

Count equal objects in NSArray

I've been trying to figure out a way of checking how many of a certain object are in an NSArray.
I've looked through the docs and I'm pretty sure there is no premade method for this. Also I can't find anything here on SO.
Do anybody know about a good way to do this? Because I seriously can't come up with anything.
In this specific case I have an array with strings (most cases several of each) and I want to count how many strings in the array that matches to whatever I ask for.
If this is a primary use of the data structure and order doesn't matter, consider switching to an NSCountedSet which is specifically for solving this problem efficiently.
If you need an ordered collection, and you don't have a huge set of objects, than the fast enumeration answers are the best approach.
If you want to know where the objects are, then use indexesOfObjectsPassingTest:.
If you have a huge number of object, I would look at indexesOfObjectsWithOptions:passingTest: with the NSEnumerationConcurrent option. This will allow you to search the array on multiple cores. (This is only possibly faster on a multi-core device, and even then is probably only faster if you have a very large collection. You should absolutely test before assuming that concurrent will be faster.) Even if you just need the final count, it may be faster for certain data sets to use this method and then use count on the final index set.
There actually is a method for this: - (NSIndexSet *)indexesOfObjectsPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:^(id obj, NSUInteger index, BOOL *stop) {
return [obj isEqualTo:myOtherObject];
}];
Sounds like a case for NSCountedSet, which does what you are after with its initWithArray: initializer:
// Example array of strings
NSArray *array = [NSArray arrayWithObjects:
#"Joe", #"Jane", #"Peter", #"Paul",
#"Joe", #"Peter", #"Paul",
#"Joe",
#"Jane", #"Peter",
nil];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray: array];
// for-in will let you loop over the counted set
for (NSString *str in countedSet) {
NSLog(#"Count of %#: %ld", str, (long)[countedSet countForObject:str]);
}
One approach would be to iterate and check.
- (int)repeatsOf:(NSString *)repeater inArray:(NSArray *)array {
int count = 0;
for (NSString *item in array) {
if ([item isEqualToString:repeater]) {
count++;
}
}
return count;
}
You could try a simple loop. Suppose needle is your reference string and array is your NSArray of strings:
unsigned int n = 0;
for (NSString * str in array)
{
if ([needle isEqualToString:str])
{
++n;
}
}
Now n holds the count of strings in equal to needle.
You could define a function like this:
- (int)countStringsThatMatch:(NSString*)match inArray:(NSArray*)array
{
int matches = 0;
for (id string in array) {
if ([string isEqualToString:match]) {
matches++;
}
}
return matches;
}
And then use it like:
int count = [self countStringsThatMatch:#"someString" inArray:someArray];
- (NSUInteger) objectCountInArray:(NSArray *)array
matchingString:(NSString *)stringToMatch {
NSUInteger count = 0;
for (NSString *string in array) {
count += [string isEqualToString:stringToMatch] ? 1 : 0;
}
return count;
}
You can try to expand this to use a block that gets an object and returns a BOOL. Then you can use it to compare an array of whatever you want.