I\'m Making a an ios application where you input 9 lettes and it will output anagrams of those 9 letters. It is like the target word, or 9 letter word in the paper. Like thi
First you need a method to check if one word is an anagram of a second word. There are many possible solutions (search for "Objective-C anagram"). This is essentially the method from https://stackoverflow.com/a/13465672/1187415, written slightly differently:
- (BOOL)does:(NSString*)longWord contain:(NSString *)shortWord
{
NSMutableString *longer = [longWord mutableCopy];
__block BOOL retVal = YES;
// Loop over all characters (letters) in shortWord:
[shortWord enumerateSubstringsInRange:NSMakeRange(0, [shortWord length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
// Check if letter occurs in longer word:
NSRange letterRange = [longer rangeOfString:substring];
if (letterRange.location != NSNotFound) {
// Yes. Remove from longer word and continue.
[longer deleteCharactersInRange:letterRange];
} else {
// No. Set return value to NO and quit the loop.
retVal = NO;
*stop = YES;
}
}];
return retVal;
}
Examples:
[self does:@"abandoned" contain:@"bond"] = YES
[self does:@"abandoned" contain:@"sea"] = NO
, because there is no "s" in the first word.[self does:@"abandoned" contain:@"noon"] = NO
, because "noon" has 2 letters "o", but
the first word has only one "o".Then you can proceed as follows:
NSArray *englishWords = ...; // Your array of english words
NSString *inputWord = @"abandoned"; // The input string
NSString *middleLetter = [inputWord substringWithRange:NSMakeRange([inputWord length]/2, 1)];
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(NSString *word, NSDictionary *bindings) {
// Word must have at least 4 letters:
if ([word length] < 4)
return NO;
// Word must contain the middle letter:
if ([word rangeOfString:middleLetter].location == NSNotFound)
return NO;
// Word must contain only letters of the input word:
if (![self does:inputWord contain:word])
return NO;
return YES;
}];
NSArray *matchingWords = [englishWords filteredArrayUsingPredicate:predicate];
NSLog(@"%@", matchingWords);
Let me propose a different algorithm that depends on a lookup, not a search through an array.
Setup:
Iterate over the words in the dictionary. For each word, create a string with the same characters, sorted alphabetically. Using this string as a key, create a dictionary of arrays of the original words.
Usage:
Now You can do the check on any character combination very quickly: Just sort the characters like above and look the resulting key up in the map.
Example:
original array: ( bond, Mary, army )
anagram lookup map:
{
bdno : ( bond ),
amry : ( Mary, army ),
}
Using this map it's very fast to check anagrams of any word. No iteration over the dictionary array is needed.
Edit:
My proposed algorithm splits in three parts:
anagramMap
anagramKey
findAnagrams
.Here's an implementation of all three methods as a category on NSString
:
@interface NSString (NSStringAnagramAdditions)
- (NSSet *)findAnagrams;
@end
@implementation NSString (NSStringAnagramAdditions)
+ (NSDictionary *)anagramMap
{
static NSDictionary *anagramMap;
if (anagramMap != nil)
return anagramMap;
// this file is present on Mac OS and other unix variants
NSString *allWords = [NSString stringWithContentsOfFile:@"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
NSMutableDictionary *map = [NSMutableDictionary dictionary];
@autoreleasepool {
[allWords enumerateLinesUsingBlock:^(NSString *word, BOOL *stop) {
NSString *key = [word anagramKey];
if (key == nil)
return;
NSMutableArray *keyWords = [map objectForKey:key];
if (keyWords == nil) {
keyWords = [NSMutableArray array];
[map setObject:keyWords forKey:key];
}
[keyWords addObject:word];
}];
}
anagramMap = map;
return anagramMap;
}
- (NSString *)anagramKey
{
NSString *lowercaseWord = [self lowercaseString];
// make sure to take the length *after* lowercase. it might change!
NSUInteger length = [lowercaseWord length];
// in this case we're only interested in anagrams 4 - 9 characters long
if (length < 4 || length > 9)
return nil;
unichar sortedWord[length];
[lowercaseWord getCharacters:sortedWord range:(NSRange){0, length}];
qsort_b(sortedWord, length, sizeof(unichar), ^int(const void *aPtr, const void *bPtr) {
int a = *(const unichar *)aPtr;
int b = *(const unichar *)bPtr;
return b - a;
});
return [NSString stringWithCharacters:sortedWord length:length];
}
- (NSSet *)findAnagrams
{
unichar nineCharacters[9];
NSString *anagramKey = [self anagramKey];
// make sure this word is not too long/short.
if (anagramKey == nil)
return nil;
[anagramKey getCharacters:nineCharacters range:(NSRange){0, 9}];
NSUInteger middleCharPos = [anagramKey rangeOfString:[self substringWithRange:(NSRange){4, 1}]].location;
NSMutableSet *anagrams = [NSMutableSet set];
// 0x1ff means first 9 bits set: one for each character
for (NSUInteger i = 0; i <= 0x1ff; i += 1) {
// skip permutations that do not contain the middle letter
if ((i & (1 << middleCharPos)) == 0)
continue;
NSUInteger length = 0;
unichar permutation[9];
for (int bit = 0; bit <= 9; bit += 1) {
if (i & (1 << bit)) {
permutation[length] = nineCharacters[bit];
length += 1;
}
}
if (length < 4)
continue;
NSString *permutationString = [NSString stringWithCharacters:permutation length:length];
NSArray *matchingAnagrams = [[self class] anagramMap][permutationString];
for (NSString *word in matchingAnagrams)
[anagrams addObject:word];
}
return anagrams;
}
@end
Assuming a test string in a variable called nineletters
you would log the possible values using:
for (NSString *anagram in [nineletters findAnagrams])
NSLog(@"%@", anagram);