Rearrange Letters from Array and check if arrangement is in array

anagram, arrays, ios, objective-c, words

Solution

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:

- A setup method to build the lookup map from a dictionary of objects: `anagramMap`

- A method to calculate the character-by-character sorted key: `anagramKey`

- An algorithm that finds all permutations of the characters contained in the nine letter word and looks up the words in the map: `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);

Problem

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 this link: http://nineletterword.tompaton.com/ It doesn't just provide anagrams for 9 letters, it will do it for 4 letters, 5 letters, 6 letters... all of which contain at least the middle letter. I want to make it an offline application, so I don't want to reference any websites or use an online json... How would I go about checking if an array of the 9 letters can be rearranged into a word which is in an English dictionary which I have downloaded. E.g. I have the input of (a,b,a,n,D,o,n,e,d): how would I get the output of 4 letters or more that are English words in an array called "English Dictionary" which must contains the middle letter "D" - like "abandon", "bond", "dead"... Is the best method lots and lots of loops and if statements or is there something in xcode/ objective c which I can use to just get the list of 4 letters and then have all possible arrangements of it... Cheers

Original source