Rearrange Letters from Array and check if arrangement is in array

后端 未结 2 1688
一向
一向 2021-01-03 05:48

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

相关标签:
2条回答
  • 2021-01-03 06:20

    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);
    
    0 讨论(0)
  • 2021-01-03 06:25

    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:

    1. A setup method to build the lookup map from a dictionary of objects: anagramMap
    2. A method to calculate the character-by-character sorted key: anagramKey
    3. 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);
    
    0 讨论(0)
提交回复
热议问题