I ran into an issue, where I got the same hash value for different dictionaries. Maybe I'm doing something obvious wrong, but I thought, objects with different content (a.k.a. not equal objects) should have different hash values.
NSDictionary *dictA = #{ #"foo" : #YES };
NSDictionary *dictB = #{ #"foo" : #NO };
BOOL equal = [dictA hash] == [dictB hash];
NSAssert(!equal, #"Assuming, that different dictionaries have different hash values.");
Any thoughts?
There is no guarantee that two different objects will have different hash values.
In the latest open-source version of CoreFoundation, the hash of a CFDictionary (which is equivalent to an NSDictionary) is defined as:
static CFHashCode __CFDictionaryHash(CFTypeRef cf) {
return __CFBasicHashHash((CFBasicHashRef)cf);
}
and __CFBasicHashHash is defined as:
__private_extern__ CFHashCode __CFBasicHashHash(CFTypeRef cf) {
CFBasicHashRef ht = (CFBasicHashRef)cf;
return CFBasicHashGetCount(ht);
}
which is simply the number of entries stored in the collection. In the other words, both [dictA hash] and [dictB hash]'s hash value are 1, the number of entries in the dictionaries.
While it is a very bad hash algorithm, CF didn't do anything wrong here. If you need to have a more accurate hash value, you can provide one yourself in an Obj-C category.
With a dictionary with only integers, strings etc. I would use dict.description.hash as a quick code.
A solution based on igor-kulagin's answer which is not order dependent:
#implementation NSDictionary (Extensions)
- (NSUInteger) hash
{
NSUInteger prime = 31;
NSUInteger result = 1;
for (NSObject *key in [[self allKeys] sortedArrayUsingSelector:#selector(compare:)]) {
result = prime * result + [key hash];
result = prime * result + [self[key] hash];
}
return result;
}
#end
However, there is still a possibility of collision if the dictionary contains other dictionaries as values.
The function 'hash' is not a real hash function. It gives different values for strings (all predictable) but for collections (arrays and dictionaries) it just returns the count. If you want a unique hash you can calculate it yourself using primes, or the functions srandom() and random() or explore a real hash function like SHA1 available in CommonCrypto/CommonDigest.h
I created NSDictionary category and overridden hash method based on this answer: Best practices for overriding isEqual: and hash
#implementation NSDictionary (Extensions)
- (NSUInteger) hash {
NSUInteger prime = 31;
NSUInteger result = 1;
NSArray *sortedKeys = [self.allKeys sortedArrayUsingSelector: #selector(compare:)];
for (NSObject *key in sortedKeys) {
result = prime * result + key.hash;
id value = self[key];
if ([value conformsToProtocol: #protocol(NSObject)] == YES) {
result = prime * result + [value hash];
}
}
return result;
}
#end
And Swift implementation.
extension Dictionary where Key: Comparable, Value: Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
let sortedKeys = self.keys.sorted()
for (key) in sortedKeys {
let value = self[key]!
result = Int.addWithOverflow(Int.multiplyWithOverflow(prime, result).0, key.hashValue).0
result = Int.addWithOverflow(Int.multiplyWithOverflow(prime, result).0, value.hashValue).0
}
return result
}
}
Perfectly this also requires to implement Equatable protocol for Dictionary so you can also add Hashable protocol conformance.
Related
I have a dictionary that stores an object using a combination of the class name and selector as the key. I'm using the following function in order to calculate the hash:
+(NSString*) getKeyForClass:(Class) clazz andSelector:(SEL) selector {
return [NSString stringWithFormat:#"%#_%#",NSStringFromClass(clazz), NSStringFromSelector(selector)];
}
While running a profiler i've discovered that this function is the bottleneck of the computation. Is there a better (= more efficient) way to create a key from a class and a selector?
A few alternatives.
Keep using a string as a key, but do it faster:
Using a string is a bit more heavyweight than you really need, but it is at least simple.
Using -[NSString stringByAppendingString] would be faster. Parsing format strings is a lot of work.
return [[NSStringFromClass(clazz) stringByAppendingString:#"_"] stringByAppendingString:NSStringFromSelector(selector)];
It may be better to use a single NSMutableString instead of making intermediate strings. Profile it and see.
NSMutableString* result = [NSStringFromClass(clazz) mutableCopy];
[result appendString:#"_"];
[result appendString:NSStringFromSelector(selector)];
return result;
Use a custom object as a key:
You can make a custom object as the key that refers to the class and selector. Implement NSCopying and -isEqual: and -hash on it, so you can use it as a key in a dictionary.
#interface MyKey : NSObject <NSCopying>
{
Class _clazz;
SEL _selector;
}
- (id)initWithClass:(Class)clazz andSelector:(SEL)selector;
#end
#implementation MyKey
- (id)initWithClass:(Class)clazz andSelector:(SEL)selector
{
if ((self = [super init])) {
_clazz = clazz;
_selector = selector;
}
return self;
}
- (id)copyWithZone:(NSZone*)zone
{
return self; // this object is immutable, so no need to actually copy it
}
- (BOOL)isEqual:(id)other
{
if ([other isKindOfClass:[MyKey class]]) {
MyKey* otherKey = (MyKey*)other;
return _clazz == otherKey->_clazz && _selector == otherKey->_selector;
} else {
return NO;
}
}
// Hash combining method from http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html
#define NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (NSUINT_BIT - howmuch)))
- (NSUInteger)hash
{
return NSUINTROTATE([_clazz hash], NSUINT_BIT / 2) ^ (NSUInteger)_selector;
}
#end
+ (MyKey*)keyForClass:(Class)clazz andSelector:(SEL)selector
{
return [[MyKey alloc] initWithClass:clazz andSelector:selector];
}
Eliminate the middleman:
If you never need to pull the class and selector out of your key object, then you can just use the hash as computed above, stored in an NSNumber.
// Hash combining method from http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and-hashing.html
#define NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (NSUINT_BIT - howmuch)))
+ (NSNumber*)keyForClass:(Class)clazz andSelector:(SEL)selector
{
NSUInteger hash = NSUINTROTATE([clazz hash], NSUINT_BIT / 2) ^ (NSUInteger)selector;
return [NSNumber numberWithUnsignedInteger:hash];
}
SELs themselves are unique; you could use an NSValue and wrap just that:
[NSValue valueWithBytes:&selector objCType:#encode(SEL)];
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.
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.
What is the simplest way to do a binary search on an (already) sorted NSArray?
Some potential ways I have spotted so far include:
The use of CFArrayBSearchValues (mentioned here) - would this work on an NSArray?
The method indexOfObject:inSortedRange:options:usingComparator: of NSArray assumes the array is sorted and takes an opts param of type NSBinarySearchingOptions - does this mean it performs a binary search? The docs just say:
Returns the index, within a specified range, of an object compared with elements in the array using a given NSComparator block.
Write my own binary search method (something along the lines of this).
I should add that I am programming for iOS 4.3+
Thanks in advance.
The second option is definitely the simplest. Ole Begemann has a blog entry on how to use the NSArray's indexOfObject:inSortedRange:options:usingComparator: method:
NSArray *sortedArray = ... // must be sorted
id searchObject = ...
NSRange searchRange = NSMakeRange(0, [sortedArray count]);
NSUInteger findIndex = [sortedArray indexOfObject:searchObject
inSortedRange:searchRange
options:NSBinarySearchingFirstEqual
usingComparator:^(id obj1, id obj2)
{
return [obj1 compare:obj2];
}];
See NSArray Binary Search
1 and 2 will both work. #2 is probably easier; it certainly doesn't make sense for that method to do anything other than a binary search (if the range is above a certain size, say). You could verify on a large array that it only does a small number of comparisons.
I'm surprised that nobody mentioned the use of NSSet, which [when it contains objects with a decent hash, such as most Foundation data types] performs constant time lookups. Instead of adding your objects to an array, add then to a set instead (or add them to both if you need to retain a sorted order for other purposes [or alternatively on iOS 5.0 or Mac OS X 10.7 there is NSOrderedSet]).
To determine whether an object exists in a set:
NSSet *mySet = [NSSet setWithArray:myArray]; // try to do this step only once
if ([mySet containsObject:someObject])
{
// do something
}
Alternatively:
NSSet *mySet = [NSSet setWithArray:myArray]; // try and do this step only once
id obj = [mySet member:someObject];
// obj is now set to nil if the object doesn't exist or it is
// set to an object that "isEqual:" to someObject (which could be
// someObject itself).
It is important to know that you will lose any performance benefit if you convert the array to a set each time you do a lookup, ideally you will be using a preconstructed set containing the objects you want to test.
//Method to pass array and number we are searching for.
- (void)binarySearch:(NSArray *)array numberToEnter:(NSNumber *)key{
NSUInteger minIndex = 0;
NSUInteger maxIndex = array.count-1;
NSUInteger midIndex = array.count/2;
NSNumber *minIndexValue = array[minIndex];
NSNumber *midIndexValue = array[midIndex];
NSNumber *maxIndexValue = array[maxIndex];
//Check to make sure array is within bounds
if (key > maxIndexValue || key < minIndexValue) {
NSLog(#"Key is not within Range");
return;
}
NSLog(#"Mid indexValue is %#", midIndexValue);
//If key is less than the middleIndexValue then sliceUpArray and recursively call method again
if (key < midIndexValue){
NSArray *slicedArray = [array subarrayWithRange:NSMakeRange(minIndex, array.count/2)];
NSLog(#"Sliced array is %#", slicedArray);
[self binarySearch:slicedArray numberToEnter:key];
//If key is greater than the middleIndexValue then sliceUpArray and recursively call method again
} else if (key > midIndexValue) {
NSArray *slicedArray = [array subarrayWithRange:NSMakeRange(midIndex+1, array.count/2)];
NSLog(#"Sliced array is %#", slicedArray);
[self binarySearch:slicedArray numberToEnter:key];
} else {
//Else number was found
NSLog(#"Number found");
}
}
//Call Method
#interface ViewController ()
#property(nonatomic)NSArray *searchArray;
#end
- (void)viewDidLoad {
[super viewDidLoad];
//Initialize the array with 10 values
self.searchArray = #[#1,#2,#3,#4,#5,#6,#7,#8,#9,#10];
//Call Method and search for any number
[self binarySearch:self.searchArray numberToEnter:#5];
// Do any additional setup after loading the view, typically from a nib.
}
CFArrayBSearchValues should work—NSArray * is toll-free bridged with CFArrayRef.
I have a string I want to parse and return an equivalent enum. I need to use the enum type elsewhere, and I think I like how I'm defining it. The problem is that I don't know a good way to check the string against the enum values without being redundant about the order of the enums.
Is there no option other than a big if/else?
typedef enum {
ZZColorRed,
ZZColorGreen,
ZZColorBlue,
} ZZColorType;
- (ZZColorType)parseColor:(NSString *)inputString {
// inputString will be #"red", #"green", or #"blue" (trust me)
// how can I turn that into ZZColorRed, etc. without
// redefining their order like this?
NSArray *colors = [NSArray arrayWithObjects:#"red", #"green", #"blue", nil];
return [colors indexOfObject:inputString];
}
In Python, I'd probably do something like the following, although to be honest I'm not in love with that either.
## maps url text -> constant string
RED_CONSTANT = 1
BLUE_CONSTANT = 2
GREEN_CONSTANT = 3
TYPES = {
'red': RED_CONSTANT,
'green': GREEN_CONSTANT,
'blue': BLUE_CONSTANT,
}
def parseColor(inputString):
return TYPES.get(inputString)
ps. I know there are color constants in Cocoa, this is just an example.
try this: Map enum to char array
Pseudo code.. untested.
int lookup(const char* str) {
for(name = one; name < NUMBER_OF_INPUTS; name++) {
if(strcmp(str, stats[name]) == 0) return name;
}
return -1;
}
A more objective-c'ish version of the code could be:
// build dictionary
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
for(i=0; i<number_of_strings; i++) {
[dict setObject:[NSNumber numberWithInteger:i] forKey:[NSString stringWithUTF8String:names[i]]];
}
// elsewhere... lookup in dictionary
id obj = [dict objectForKey:name];
if(obj) return [obj intValue];
return -1;
This has already been answered: Converting between C enum and XML
Basically, you wind up defining corresponding strings when you define your enum, and then you use a category on NSArray so that you can do this:
static NSArray* colorNamesArray = [[NSArray alloc] initWithObjects:colorNames];
//colorNames is a nil-terminated list of string literals #defined near your enum
NSString* colorName = [colorNamesArray stringWithEnum:color];
//stringWithEnum: is defined with a category
Sure, the #define is a little ugly, but the code above, which is what you'll work with most of the time, is actually pretty clean.
I was never satisfied with any of the suggestions. (But I appreciate the effort that went into them.) I tried a few of them but they didn't feel good or were error-prone in practice.
I ended up created a custom dictionary to map integers to strings which feels a lot better because it's Cocoa through and through. (I didn't subclass NSDictionary in order to make it harder to misuse.)
#interface ZZEnumDictionary : NSObject {
NSMutableDictionary *dictionary;
}
+ (id)dictionary;
+ (id)dictionaryWithStrings:(id)firstString, ...;
- (NSString *)stringForInt:(NSInteger)intEnum;
- (NSInteger)intForString:(NSString *)stringEnum;
- (BOOL)isValidInt:(NSInteger)intEnum;
- (BOOL)isValidString:(NSString *)stringEnum;
- (BOOL)stringEquals:(NSString *)stringEnum intEnum:(NSInteger)intEnum;
- (BOOL)setContainsString:(NSSet *)set forInt:(NSInteger)intEnum;
- (NSArray *)allStrings;
#end
#interface ZZEnumDictionary ()
- (void)setInt:(NSInteger)integer forString:(NSString *)string;
#end