Is there a concise way to map a string to an enum in Objective-C? - objective-c

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

Related

How to check a typedef'd obj in Objective-c NSDictionary

I've got an method that takes NSDictionary arg. This NSDictionary has some predefined keys it'll take. All the obj's should be strings. But only certain string objs are valid for each key.
So my approach was to typedef NSString for each valid string per key. I'm hoping not to extend the NSString class.
I've typedef'd some NSString's...
typedef NSString MyStringType
Then I define a few...
MyStringType * const ValidString = #"aValidString";
Here's what I'd like to do in my sample method..
- (void)setAttrbiutes:(NSDictionary *)attributes {
NSArray *keys = [attributes allKeys];
for (NSString *key in keys) {
if ([key isEqualToString:#"ValidKey"]) {
id obj = [attributes objectForKey:key];
//Here's where I'd like to check..
if (**obj is MyStringType**) {
}
}
}
}
I'm open to other ideas if there's a better approach to solve the obj type problem of an NSDictionary.
Doesn't work like that; typedefs are a compile time alias that don't survive being passed through a dictionary.
In any case, using typedefs for something like this would be unwieldy.
I suggest you create a property list -- either as a file in your project or in code -- that contains the specifications of your various keys and valid values, then write a little validator that, passed a string and value, can validate the string-value pair for validity.
This also gives you the flexibility to extend your validator in the future. For example, you might have a #"Duration" key that can only be in the range of 1 to 20.
Instead of setting up a typedef for you special values, one possible option would be to create an NSSet of the special values. Then in your code you can verify that the object in the dictionary is in your set.
What about a combination of category on NSString + associated object?
Something along the lines (untested!!):
#interface NSString (BBumSpecial)
- (NSString *) setSpecial: (BOOL) special ;
- (BOOL) special ;
#end
and:
#implementation NSString (BBumSpecial)
static void * key ;
- (NSString *) setSpecial: (BOOL) special {
objc_setAssociatedObject(self, &key, special ? #YES : #NO, OBJC_ASSOCIATION_ASSIGN) ;
return self ;
}
- (BOOL) special {
id obj = objc_getAssociatedObject(self, &key) ;
return obj && [obj boolValue] ;
}
#end
Which you could then use as:
NSString * mySpecialString = [#"I'm Special" setSpecial:YES] ;
?

Check strings for same characters in Objective-C

I have an array of strings, from which I would like to extract only those with unique character sets. (For example, "asdf" and "fdsa" would be considered redundant). This is the method I am currently using:
NSMutableArray *uniqueCharSets = [[NSMutableArray alloc] init];
NSMutableArray *uniqueStrings = [[NSMutableArray alloc] init];
for (NSString *_string in unique) {
NSCharacterSet *_charSet = [NSCharacterSet characterSetWithCharactersInString:_string];
if (![uniqueCharSets containsObject:_charSet]) {
[uniqueStrings addobject:_string];
[uniqueCharSets addObject:_charSet];
}
}
This seems to work, but it's very slow and resource-intensive. Can anyone think of a better way to do this?
Using an NSDictionary, map each string's lexicographically-sorted equivalent to an NSArray of input strings: (e.g. adfs => [afsd, asdf, ...])
Walk through the dictionary, printing out keys (or their values) which only have single-element array values
I just put together a quick example of how I would approach this, but it turns out that it is more, odd, than you first expect. For one, NSCharacterSet doesn't implement equality to check contents. It only uses the pointer value. Based on this your example will NOT work properly.
My approach is to use an NSSet to deal with the hashing of these for us.
#interface StringWrapper : NSObject
#property (nonatomic, copy) NSString *string;
#property (nonatomic, copy) NSData *charSetBitmap;
- (id)initWithString:(NSString*)aString;
#end
#implementation StringWrapper
#synthesize string, charSetBitmap;
- (id)initWithString:(NSString*)aString;
{
if ((self = [super init]))
{
self.string = aString;
}
return self;
}
- (void)setString:(NSString *)aString;
{
string = [aString copy];
self.charSetBitmap = [[NSCharacterSet characterSetWithCharactersInString:aString] bitmapRepresentation];
}
- (BOOL)isEqual:(id)object;
{
return [self.charSetBitmap isEqual:[object charSetBitmap]];
}
- (NSUInteger)hash;
{
return [self.charSetBitmap hash];
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
NSMutableSet *stringWrappers = [[NSMutableSet alloc] init];
NSArray *strings = [NSArray arrayWithObjects:#"abc",#"aaabcccc",#"awea",#"awer",#"abcde", #"ehra", #"QWEQ", #"werawe", nil];
for (NSString *str in strings)
[stringWrappers addObject:[[StringWrapper alloc] initWithString:str]];
NSArray *uniqueStrings = [stringWrappers valueForKey:#"string"];
NSLog(#"%#", uniqueStrings);
}
return 0;
}
The code is pretty straightforward. We create a container object to cache the results of the character set's bitmap representation. We use the bitmap representation because NSData implements isEqual: appropriately.
The only thing that come in my mind is not to use containsObject: since NSMutableArray is not ordered (in general), we can assume that containsObject simply iterates the array starting from the beginning until he finds the object. This means O(n) (n comparisons in the worst case).
A better solution may consists in keeping the array ordered and use a custom search method using a dichotomic approach. This way you'll have a O(log n) complexity.
Of course, you must take care of keeping your array ordered (much more efficient than add and reorder), so you should use insertObject:atIndex: method to insert the element properly.

How do I enumerate on NSString?

How do I enumerate on NSString?
example of what I am trying to do:
enum eCat{
dog,
cat,
mouse,
bunny
};
#interface
#implementation
....
enum eCat Cate;
NSString *yoda = #"mouse";
Cate = [yoda intValue];
NSLog(#"Hello: %d",Cate);
wanting the result to be
Hello: 2
thanks
You have to create a mapping (string → enumeration) yourself. One possibility would be something like this (disclaimer: only brain-compiled):
#define CAT_(a, b) a##b
#define CAT(a, b) CAT_(a, b)
#define E(en) [NSNumber numberWithInt:en], CAT(#, #en),
NSDictionary *mapping = [NSDictionary dictionaryWithObjectsAndKeys:
// ...
E(mouse)
E(bunny)
nil];
NSNumber *result = [mapping objectForKey:#"mouse"];
if (!result) {
// ... oops
} else {
enum eCat cate = [result intValue];
}
There's no direct support of such an enumeration in Objective-C.
Instead, create an array of the strings and look for an entry:
static NSArray* enumeration=nil;
if(!enumeration){
enumeration=[[NSArray arrayWithObjects:#"AAA",#"BBB",#"CCC",nil] retain];
}
then use it later:
NSInteger i=[enumeration indexOfObject:#"BBB"];
/* i is now 1 */
It's unrelated to your question, but please don't start a variable name with a capital letter, like your Cate. That's against the convention of Objective-C.

Removing duplicates from array based on a property in Objective-C

I have an array with custom objects. Each array item has a field named "name". Now I want to remove duplicate entries based on this name value.
How should I go about achieving this?
I do not know of any standard way to to do this provided by the frameworks. So you will have to do it in code. Something like this should be doable:
NSArray* originalArray = ... // However you fetch it
NSMutableSet* existingNames = [NSMutableSet set];
NSMutableArray* filteredArray = [NSMutableArray array];
for (id object in originalArray) {
if (![existingNames containsObject:[object name]]) {
[existingNames addObject:[object name]];
[filteredArray addObject:object];
}
}
You might have to actually write this filtering method yourself:
#interface NSArray (CustomFiltering)
#end
#implementation NSArray (CustomFiltering)
- (NSArray *) filterObjectsByKey:(NSString *) key {
NSMutableSet *tempValues = [[NSMutableSet alloc] init];
NSMutableArray *ret = [NSMutableArray array];
for(id obj in self) {
if(! [tempValues containsObject:[obj valueForKey:key]]) {
[tempValues addObject:[obj valueForKey:key]];
[ret addObject:obj];
}
}
[tempValues release];
return ret;
}
#end
I know this is an old question but here is another possibility, depending on what you need.
Apple does provide a way to do this -- Key-Value Coding Collection Operators.
Object operators let you act on a collection. In this case, you want:
#distinctUnionOfObjects
The #distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
NSArray *distinctArray = [arrayWithDuplicates valueForKeyPath:#"#distinctUnionOfObjects.name"];
In your case, though, you want the whole object. So what you'd have to do is two-fold:
1) Use #distinctUnionOfArrays instead. E.g. If you had these custom objects coming from other collections, use #distinctUnionOfArray.myCollectionOfObjects
2) Implement isEqual: on those objects to return if their .name's are equal
I'm going to get flak for this...
You can convert your array into a dictionary. Not sure how efficient this is, depends on the implementation and comparison call, but it does use a hash map.
//Get unique entries
NSArray *myArray = #[#"Hello", #"World", #"Hello"];
NSDictionary *uniq = [NSDictionary dictionaryWithObjects:myArray forKeys:myArray];
NSLog(#"%#", uniq.allKeys);
*Note, this may change the order of your array.
If you'd like your custom NSObject subclasses to be considered equal when their names are equal you may implement isEqual: and hash. This will allow you to add of the objects to an NSSet/NSMutableSet (a set of distinct objects).
You may then easily create a sorted NSArray by using NSSet's sortedArrayUsingDescriptors:method.
MikeAsh wrote a pretty solid piece about implementing custom equality: Friday Q&A 2010-06-18: Implementing Equality and Hashing
If you are worried about the order
NSArray * newArray =
[[NSOrderedSet orderedSetWithArray:oldArray] array]; **// iOS 5.0 and later**
It is quite simple in one line
NSArray *duplicateList = ...
If you don't care about elements order then (unordered)
NSArray *withoutDUP1 = [[NSSet setWithArray:duplicateList] allObjects];
Keep the elements in order then (ordered)
NSArray *withoutDUP2 = [[NSOrderedSet orderedSetWithArray:duplicateList] array];
Implement isEqual to make your objects comparable:
#interface SomeObject (Equality)
#end
#implementation SomeObject (Equality)
- (BOOL)isEqual:(SomeObject*)other
{
return self.hash == other.hash;
}
- (NSUInteger)hash
{
return self.name;///your case
}
#end
How to use:
- (NSArray*)distinctObjectsFromArray:(NSArray*)array
{
return [array valueForKeyPath:#"#distinctUnionOfObjects.self"];
}

Accessing constants using Key-Value Coding in Objective-C

I'm making a program that has a lot of constants. I decided to put them all into a separate class and I'm importing it by the classes that need it. The files look similar to this
// Constants.h
extern const int baseCostForBuilding;
extern const int maxCostForBuilding;
// etc
// Constants.m
const int baseCostForBuilding = 400;
const int maxCostForBuilding = 1000;
// etc
What I'm trying to do is access them using key-value coding. What I've tried so far hasn't worked.
id object = [self valueForKey:#"baseCostForBuilding"];
But I can do the following and it works fine.
id object = baseCostForBuilding;
This may seem pointless but I have a lot of variables that have to end in "CostForBuilding" and the function I need this in only gets the first part of the string. Example, "base", "max", "intermediate", etc. It will then combine it with "CostForBuilding" or something else to get the variable name.
If this is possible, it would be way nicer to only have one or two lines of code instead of multiple if-statements to access the correct variable. Does anyone know a way to do this? Thanks in advance.
You can fill a dictionary with the appropriate values:
- (id)init
{
...
buildingCosts = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:100], #"base",
[NSNumber numberWithInt:200], #"max",
...,
nil];
...
}
- (int)buildingCostForKey:(NSString *)key
{
return [(NSNumber *)[buildingCosts objectForKey:key] intValue];
}
- (void)dealloc
{
[buildingCosts release];
}
Which you could then use as follows:
int baseCost = [myClass buildingCostForKey:#"base"];