UITextChecker without proper nouns - objective-c

I'm exercising the UITextChecker class to do a quick check on a string for a word-spelling game. Works a little TOO well. Unfortunately, as far as I can tell, the only methods that operate on this class return "correct" words that also include proper nouns. I would like to check my strings against a list of common words that do NOT include proper nouns. Here's my code so far:
//Test the answer for a word
UITextChecker *checker = [[UITextChecker alloc] init];
NSString *testString = wordString;
NSRange range = NSMakeRange(0,0);
range = [checker rangeOfMisspelledWordInString:[testString lowercaseString]
range:NSMakeRange(0, [testString length])
startingAt:0
wrap:NO
language:#"en_US"];
if (range.location == NSNotFound) {
spelledWord = YES;
} else {
spelledWord = NO;
}
Any help would be appreciated!

Not sure if this is the easiest way but you could put a second condition. First store an array with proper nouns (or other words you don't want) elsewhere in your code do a search on Google if you can't think of them. (I've adapted this slightly from a method i use)
if (range.location == NSNotFound) {
int i = 1;
NSString *p;
foundrand = FALSE;
if ([[MyArray sharedKelArray].Myarray count] >2){
////NSLog(#"GOTTEN - %d", choosennumber);
while(i<[[MyArray sharedKelArray].Myarray count])//would check that if equal
{
p = [[[MyArray sharedKelArray].Myarray objectAtIndex:i] NSString];
NSLog(#"Checking word - %d",p);
if (testString == p){
NSLog(#"Matched");
spelledWord = NO;
i = 5 + [[MyArray sharedKelArray].Myarray count];
}
i+=1;
}
spelledWord = YES;
}
}
}

Related

Replacing several different characters in NSString

I am making an iPad app for personal use and I m struggling with some character replacement in some strings. For example I got an NSString which contains "\t\t\t C D". Now what I want to do is replace every C and every D there is in there with C# and D#. I have managed to do that but unfortunately it doesn't look efficient at all to me.
Here is my code so far:
- (IBAction)buttonPressed:(id)sender
{
if(sender)
{
NSError *error;
NSString *newTab = [[NSString alloc] init];
NSRegularExpression *regexC = [NSRegularExpression regularExpressionWithPattern:#"C" options:0 error:&error];
NSRegularExpression *regexD = [NSRegularExpression regularExpressionWithPattern:#"D" options:0 error:&error];
newTab = [regexC stringByReplacingMatchesInString:self.tab options:0 range:NSMakeRange(0, self.tab.length) withTemplate:#"C#"];
NSString *newTabAfterFirstRegex = [[NSString alloc] initWithString:newTab];
newTabAfterFirstRegex = [regexD stringByReplacingMatchesInString:newTab options:0 range:NSMakeRange(0, newTab.length) withTemplate:#"D#"];
NSLog(#"%#",newTabAfterFirstRegex);
}
}
Plus this is just a small tester code. What I would really like to do is to have an algorithm that checks for instances of all music tabs (C C# D D# E F F# G G# A A# B) in a given string and when the IBAction is triggered I would like each one of them to be replaced by the next one (and B becomes C).
Any ideas would be very much appreciated!
Thank you very much!
You can set a regular expression (e.g. '[A-G]#?') to match certain strings. With method -matchesInString:options:range: you can loop through all the matches (it will give back a range for each match) and use that range to do the replacements.
Regular expressions seem a bit like overkill for this, you could just do two string replacements, so that you don't get all the overhead from regexes, using
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target
withString:(NSString *)replacement
and just replace it twice. Also, you don't need to do the NSString allocations, because it creates a reference in the return.
I created the following methods for encryption the other day. I've tested it for your purpose, and it seems to work.
-(NSString *)ReplaceMe:(NSString *)s {
// Putting the source into an array
NSMutableArray *myArray = [[NSMutableArray alloc] init];
int i;
for (i = 0; i < s.length; i++) {
[myArray addObject: [self Mid:s :i :1]];
}
// Creating a string with the revised array
NSMutableString *myString = [NSMutableString new];
for (i = 0; i < s.length; i++) {
[myString appendString:[self Conversion:[myArray objectAtIndex:i]]];
}
// Final
return myString;
}
The method above requires two additional functions.
-(NSString *)Mid:(NSString *)str:(NSInteger)s:(NSInteger)l {
if ((s <= str.length-1) && (s + l <= str.length) && (s >= 0) && (l >= 1)) {
return [str substringWithRange:NSMakeRange(s, l)];
}
else {
return #"";
}
}
The other is...
-(NSString *)Conversion:(NSString *)s {
if ([s isEqualToString:#"C"]) {
return #"C#";
}
else if ([s isEqualToString:#"D"]) {
return #"D#";
}
else {
return s;
}
}
You can put other conversion pairs in the function above. The following is an example as to how to use ReplaceMe.
- (IBAction)clickAction:(id)sender {
textField2.text = [self ReplaceMe:textField1.text];
}
So it's ReplaceMe is quite easy to use.

Algorithm to find anagrams Objective-C

I've got an algorithm to find anagrams within a group of eight-letter words. Effectively it's alphabetizing the letters in the longer word, doing the same with the shorter words one by one, and seeing if they exist in the longer word, like so:
tower = eortw
two = otw
rot = ort
The issue here is that if I look for ort in eortw (or rot in tower), it'll find it, no problem. Rot is found inside tower. However, otw is not inside eortw (or two in tower), because of the R in the middle. Ergo, it doesn't think two is found in tower.
Is there a better way I can do this? I'm trying to do it in Objective-C, and both the eight-letter words and regular words are stored in NSDictionaries (with their normal and alphabetized forms).
I've looked at various other posts re. anagrams on StackOverflow, but none seem to address this particular issue.
Here's what I have so far:
- (BOOL) doesEightLetterWord: (NSString* )haystack containWord: (NSString *)needle {
for (int i = 0; i < [needle length] + 1; i++) {
if (!needle) {
NSLog(#"DONE!");
}
NSString *currentCharacter = [needle substringWithRange:NSMakeRange(i, 1)];
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString: currentCharacter];
NSLog(#"Current character is %#", currentCharacter);
if ([haystack rangeOfCharacterFromSet:set].location == NSNotFound) {
NSLog(#"The letter %# isn't found in the word %#", currentCharacter, haystack);
return FALSE;
} else {
NSLog(#"The letter %# is found in the word %#", currentCharacter, haystack);
int currentLocation = [haystack rangeOfCharacterFromSet: set].location;
currentLocation++;
NSString *newHaystack = [haystack substringFromIndex: currentLocation];
NSString *newNeedle = [needle substringFromIndex: i + 1];
NSLog(#"newHaystack is %#", newHaystack);
NSLog(#"newNeedle is %#", newNeedle);
}
}
}
If you use only part of the letters it isn't a true anagram.
A good algorithm in your case would be to take the sorted strings and compare them letter by letter, skipping mis-matches in the longer word. If you reach the end of the shorter word then you have a match:
char *p1 = shorter_word;
char *p2 = longer_word;
int match = TRUE;
for (;*p1; p1++) {
while (*p2 && (*p2 != *p1)) {
p2++;
}
if (!*p2) {
/* Letters of shorter word are not contained in longer word */
match = FALSE;
}
}
This is one that approach I might take for finding out if one ordered word contained all of the letters of another ordered word. Note that it won't find true anagrams (That simply requires the two ordered strings to be the same) but this does what I think you're asking for:
+(BOOL) does: (NSString* )longWord contain: (NSString *)shortWord {
NSString *haystack = [longWord copy];
NSString *needle = [shortWord copy];
while([haystack length] > 0 && [needle length] > 0) {
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString: [needle substringToIndex:1]];
if ([haystack rangeOfCharacterFromSet:set].location == NSNotFound) {
return NO;
}
haystack = [haystack substringFromIndex: [haystack rangeOfCharacterFromSet: set].location+1];
needle = [needle substringFromIndex: 1];
}
return YES;
}
The simplest (but not most efficient) way might be to use NSCountedSet. We can do this because for counted sets, [a isSubsetOfSet:b] return YES if and only if [a countForObject:object] <= [b countForObject:object] for every object in a.
Let's add a category to NSString to do it:
#interface NSString (lukech_superset)
- (BOOL)lukech_isSupersetOfString:(NSString *)needle;
#end
#implementation NSString (lukech_superset)
- (NSCountedSet *)lukech_countedSetOfCharacters {
NSCountedSet *set = [NSCountedSet set];
[self enumerateSubstringsInRange:NSMakeRange(0, self.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[set addObject:substring];
}];
return set;
}
- (BOOL)lukech_isSupersetOfString:(NSString *)needle {
return [[needle lukech_countedSetOfCharacters] isSubsetOfSet:[self lukech_countedSetOfCharacters]];
}
#end

Check if NSString contains all or some characters

I have an NSString called query which contains ~10 characters.
I would like to check to see if a second NSString called word contains all of the characters in query, or some characters, but no other characters which aren't specified in query.
Also, if there is only one occurrence of the character in the query, there can only be one occurrence of the character in the word.
Please could you tell me how to do this?
NSString *query = #"ABCDEFJAKSUSHFKLAFIE";
NSString *word = #"fearing"; //would pass as NO as there is no 'n' in the query var.
The following answers the first half:
NSCharacterSet *nonQueryChars = [[NSCharacterSet characterSetWithCharactersInString:[query lowercaseString]] invertedSet];
NSRange badCharRange = [[word lowercaseString] rangeOfCharacterFromSet:nonQueryChars];
if (badCharRange.location == NSNotFound) {
// word only has characters in query
} else {
// found unwanted characters in word
}
I need to think about the second half of the requirement.
Ok, the following code should fulfill both requirements:
- (NSCountedSet *)wordLetters:(NSString *)text {
NSCountedSet *res = [NSCountedSet set];
for (NSUInteger i = 0; i < text.length; i++) {
[res addObject:[text substringWithRange:NSMakeRange(i, 1)]];
}
return res;
}
- (void)checkWordAgainstQuery {
NSString *query = #"ABCDEFJAKSUSHFKLAFIE";
NSString *word = #"fearing";
NSCountedSet *queryLetters = [self wordLetters:[query lowercaseString]];
NSCountedSet *wordLetters = [self wordLetters:[word lowercaseString]];
BOOL ok = YES;
for (NSString *wordLetter in wordLetters) {
int wordCount = [wordLetters countForObject:wordLetter];
// queryCount will be 0 if this word letter isn't in query
int queryCount = [queryLetters countForObject:wordLetter];
if (wordCount > queryCount) {
ok = NO;
break;
}
}
if (ok) {
// word matches against query
} else {
// word has extra letter or too many of a matching letter
}
}

Compare two NSStrings

In my app there is a mechanism that requires that at a certain point two NSStrings will be the same to do something; for some reason when I compare the two, even when they are the same, it still doesn't recognize that. The code is something like this:
NSString * aString = [self someMethodThatGetsAString];
NSString * bString;
BOOL areStringsTheSame = NO;
while (areStringsTheSame != YES) {
bString = [self someMethodThatTakesNSStringsFromAnArrey];
if (bString == aString) {
areStringsTheSame = YES;
{ }
I even inserted an NSLog() and made sure that at a certain point they were the same (and as far as I know this is what == stands for...), but still it didn't get into the if to change the BOOL value.
Is there another way to do this comparison? Am I missing something?
You can use the method isEqualToString::
if ([bString isEqualToString:aString])
== compares the references (addresses of) the strings, and not the value of the strings.
This approach worked for me:
if ([firstString compare:secondString] == NSOrderedSame) {
//Do something when they are the same
} else {
//Do something when they are different
}
Recently I was shocked by the fact that two NSStrings that resemble each other on NSLog may be different. It is because sometimes NSString can contain a zero width space character. Be aware of that and consider:
#define ZERO_WIDTH_SPACE_STRING #"\u200B"
To conquer this you should clean your string from zero width white space characters before comparing:
NSMutableString *eMailToAdd = [NSMutableString string];
NSMutableCharacterSet *charSet = [[NSCharacterSet whitespaceCharacterSet] mutableCopy];
//[charSet formUnionWithCharacterSet:[NSCharacterSet punctuationCharacterSet]];
NSString *rawStr = [[tokenField textField] text];
for (int i = 0; i < [rawStr length]; i++)
{
if (![charSet characterIsMember:[rawStr characterAtIndex:i]])
{
[eMailToAdd appendFormat:#"%#",[NSString stringWithFormat:#"%c", [rawStr characterAtIndex:i]]];
}
}

How can I optimise out this nested for loop?

How can I optimise out this nested for loop?
The program should go through each word in the array created from the word text file, and if it's greater than 8 characters, add it to the goodWords array. But the caveat is that I only want the root word to be in the goodWords array, for example:
If greet is added to the array, I don't want greets or greetings or greeters, etc.
NSString *string = [NSString stringWithContentsOfFile:#"/Users/james/dev/WordParser/word.txt" encoding:NSUTF8StringEncoding error:NULL];
NSArray *words = [string componentsSeparatedByString:#"\r\n"];
NSMutableArray *goodWords = [NSMutableArray array];
BOOL shouldAddToGoodWords = YES;
for (NSString *word in words)
{
NSLog(#"Word: %#", word);
if ([word length] > 8)
{
NSLog(#"Word is greater than 8");
for (NSString *existingWord in [goodWords reverseObjectEnumerator])
{
NSLog(#"Existing Word: %#", existingWord);
if ([word rangeOfString:existingWord].location != NSNotFound)
{
NSLog(#"Not adding...");
shouldAddToGoodWords = NO;
break;
}
}
if (shouldAddToGoodWords)
{
NSLog(#"Adding word: %#", word);
[goodWords addObject:word];
}
}
shouldAddToGoodWords = YES;
}
How about something like this?
//load the words from wherever
NSString * allWords = [NSString stringWithContentsOfFile:#"/usr/share/dict/words"];
//create a mutable array of the words
NSMutableArray * words = [[allWords componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] mutableCopy];
//remove any words that are shorter than 8 characters
[words filterUsingPredicate:[NSPredicate predicateWithFormat:#"length >= 8"]];
//sort the words in ascending order
[words sortUsingSelector:#selector(caseInsensitiveCompare:)];
//create a set of indexes (these will be the non-root words)
NSMutableIndexSet * badIndexes = [NSMutableIndexSet indexSet];
//remember our current root word
NSString * currentRoot = nil;
NSUInteger count = [words count];
//loop through the words
for (NSUInteger i = 0; i < count; ++i) {
NSString * word = [words objectAtIndex:i];
if (currentRoot == nil) {
//base case
currentRoot = word;
} else if ([word hasPrefix:currentRoot]) {
//word is a non-root word. remember this index to remove it later
[badIndexes addIndex:i];
} else {
//no match. this word is our new root
currentRoot = word;
}
}
//remove the non-root words
[words removeObjectsAtIndexes:badIndexes];
NSLog(#"%#", words);
[words release];
This runs very very quickly on my machine (2.8GHz MBP).
A Trie seems suitable for your purpose. It is like a hash, and is useful for detecting if a given string is a prefix of an already seen string.
I used an NSSet to ensure that you only have 1 copy of a word added at a time. It will add a word if the NSSet does not already contain it. It then checks to see if the new word is a substring for any word that has already been added, if true then it won't add the new word. It's case-insensitive as well.
What I've written is a refactoring of your code. It's probably not that much faster but you really do want a tree data structure if you want to make it a lot faster when you want to search for words that have already been added to your tree.
Take a look at RedBlack Trees or B-Trees.
Words.txt
objective
objectively
cappucin
cappucino
cappucine
programme
programmer
programmatic
programmatically
Source Code
- (void)addRootWords {
NSString *textFile = [[NSBundle mainBundle] pathForResource:#"words" ofType:#"txt"];
NSString *string = [NSString stringWithContentsOfFile:textFile encoding:NSUTF8StringEncoding error:NULL];
NSArray *wordFile = [string componentsSeparatedByString:#"\n"];
NSMutableSet *goodWords = [[NSMutableSet alloc] init];
for (NSString *newWord in wordFile)
{
NSLog(#"Word: %#", newWord);
if ([newWord length] > 8)
{
NSLog(#"Word '%#' contains 8 or more characters", newWord);
BOOL shouldAddWord = NO;
if ( [goodWords containsObject:newWord] == NO) {
shouldAddWord = YES;
}
for (NSString *existingWord in goodWords)
{
NSRange textRange = [[newWord lowercaseString] rangeOfString:[existingWord lowercaseString]];
if( textRange.location != NSNotFound ) {
// newWord contains the a substring of existingWord
shouldAddWord = NO;
break;
}
NSLog(#"(word:%#) does not contain (substring:%#)", newWord, existingWord);
shouldAddWord = YES;
}
if (shouldAddWord) {
NSLog(#"Adding word: %#", newWord);
[goodWords addObject:newWord];
}
}
}
NSLog(#"***Added words***");
int count = 1;
for (NSString *word in goodWords) {
NSLog(#"%d: %#", count, word);
count++;
}
[goodWords release];
}
Output:
***Added words***
1: cappucino
2: programme
3: objective
4: programmatic
5: cappucine