Replacing several different characters in NSString - objective-c

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.

Related

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

Cutting the length of an NSString without splitting the last word

I'm trying to cut the length of an NSString without splitting the last word with this method:
// cut a string by words
- (NSString* )stringCutByWords:(NSString *)string toLength:(int)length;
{
// search backwards in the string for the beginning of the last word
while ([string characterAtIndex:length] != ' ' && length > 0) {
length--;
}
// if the last word was the first word of the string search for the end of the word
if (length <= 0){
while ([string characterAtIndex:length] != ' ' && length > string.length-1) {
length++;
}
}
// define the range you're interested in
NSRange stringRange = {0, length};
// adjust the range to include dependent chars
stringRange = [string rangeOfComposedCharacterSequencesForRange:stringRange];
// Now you can create the short string
string = [string substringWithRange:stringRange];
return [NSString stringWithFormat:#"%#...",string];
}
now my question is:
Is there a build-in way in objective-c or cocoa-touch which i did not see or else is there a "nicer" way to do this because iam not very happy with this solution.
greetings and thanks for help
C4rmel
My proposal for a Category method
#interface NSString (Cut)
-(NSString *)stringByCuttingExceptLastWordWithLength:(NSUInteger)length;
#end
#implementation NSString (Cut)
-(NSString *)stringByCuttingExceptLastWordWithLength:(NSUInteger)length
{
__block NSMutableString *newString = [NSMutableString string];
NSArray *components = [self componentsSeparatedByString:#" "];
if ([components count] > 0) {
NSString *lastWord = [components objectAtIndex:[components count]-1];
[components enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
if (([obj length]+[newString length] + [lastWord length] + 2) < length) {
[newString appendFormat:#" %#", obj];
} else {
[newString appendString:#"…"];
[newString appendFormat:#" %#", lastWord];
*stop = YES;
}
}];
}
return newString;
}
Usage:
NSString *string = #"Hello World! I am standing over here! Can you see me?";
NSLog(#"%#", [string stringByCuttingExceptLastWordWithLength:25]);
Suggestions:
make it a category method;
use NSCharacterSet and the built-in search methods rather than rolling your own.
So:
/* somewhere public */
#interface NSString (CutByWords)
- (NSString *)stringCutByWordsToMaxLength:(int)length
#end
/* in an implementation file, somewhere */
#implementation NSString (CutByWords)
// cut a string by words
- (NSString *)stringCutByWordsToMaxLength:(int)length
{
NSCharacterSet *whitespaceCharacterSet =
[NSCharacterSet whitespaceCharacterSet];
// to consider: a range check on length here?
NSRange relevantRange = NSMakeRange(0, length);
// find beginning of last word
NSRange lastWordRange =
[self rangeOfCharacterFromSet:whitespaceCharacterSet
options:NSBackwardsSearch
range:relevantRange];
// if the last word was the first word of the string,
// consume the whole string; this looks to be the same
// effect as the original scan forward given that the
// assumption is already made in the scan backwards that
// the string doesn't end on a whitespace; if I'm wrong
// then get [whitespaceCharacterSet invertedSet] and do
// a search forwards
if(lastWordRange.location == NSNotFound)
{
lastWordRange = relevantRange;
}
// adjust the range to include dependent chars
stringRange = [self rangeOfComposedCharacterSequencesForRange:stringRange];
// Now you can create the short string
NSString *string = [self substringWithRange:stringRange];
return [NSString stringWithFormat:#"%#...",string];
}
#end
/* subsequently */
NSString *string = ...whatever...;
NSString *cutString = [string stringCutByWordsToMaxLength:100];

UITextChecker without proper nouns

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;
}
}
}

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

How to capitalize the first word of the sentence in Objective-C?

I've already found how to capitalize all words of the sentence, but not the first word only.
NSString *txt =#"hi my friends!"
[txt capitalizedString];
I don't want to change to lower case and capitalize the first char. I'd like to capitalize the first word only without change the others.
Here is another go at it:
NSString *txt = #"hi my friends!";
txt = [txt stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:[[txt substringToIndex:1] uppercaseString]];
For Swift language:
txt.replaceRange(txt.startIndex...txt.startIndex, with: String(txt[txt.startIndex]).capitalizedString)
The accepted answer is wrong. First, it is not correct to treat the units of NSString as "characters" in the sense that a user expects. There are surrogate pairs. There are combining sequences. Splitting those will produce incorrect results. Second, it is not necessarily the case that uppercasing the first character produces the same result as capitalizing a word containing that character. Languages can be context-sensitive.
The correct way to do this is to get the frameworks to identify words (and possibly sentences) in the locale-appropriate manner. And also to capitalize in the locale-appropriate manner.
[aMutableString enumerateSubstringsInRange:NSMakeRange(0, [aMutableString length])
options:NSStringEnumerationByWords | NSStringEnumerationLocalized
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[aMutableString replaceCharactersInRange:substringRange
withString:[substring capitalizedStringWithLocale:[NSLocale currentLocale]]];
*stop = YES;
}];
It's possible that the first word of a string is not the same as the first word of the first sentence of a string. To identify the first (or each) sentence of the string and then capitalize the first word of that (or those), then surround the above in an outer invocation of -enumerateSubstringsInRange:options:usingBlock: using NSStringEnumerationBySentences | NSStringEnumerationLocalized. In the inner invocation, pass the substringRange provided by the outer invocation as the range argument.
Use
- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)separator
and capitalize the first object in the array and then use
- (NSString *)componentsJoinedByString:(NSString *)separator
to join them back
pString = [pString
stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:[[pString substringToIndex:1] capitalizedString]];
you can user with regular expression i have done it's works for me simple you can paste below code
+(NSString*)CaptializeFirstCharacterOfSentence:(NSString*)sentence{
NSMutableString *firstCharacter = [sentence mutableCopy];
NSString *pattern = #"(^|\\.|\\?|\\!)\\s*(\\p{Letter})";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
[regex enumerateMatchesInString:sentence options:0 range:NSMakeRange(0, [sentence length]) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
//NSLog(#"%#", result);
NSRange r = [result rangeAtIndex:2];
[firstCharacter replaceCharactersInRange:r withString:[[sentence substringWithRange:r] uppercaseString]];
}];
NSLog(#"%#", firstCharacter);
return firstCharacter;
}
//Call this method
NsString *resultSentence = [UserClass CaptializeFirstCharacterOfSentence:yourTexthere];
An alternative solution in Swift:
var str = "hello"
if count(str) > 0 {
str.splice(String(str.removeAtIndex(str.startIndex)).uppercaseString, atIndex: str.startIndex)
}
For the sake of having options, I'd suggest:
NSString *myString = [NSString stringWithFormat:#"this is a string..."];
char *tmpStr = calloc([myString length] + 1,sizeof(char));
[myString getCString:tmpStr maxLength:[myString length] + 1 encoding:NSUTF8StringEncoding];
int sIndex = 0;
/* skip non-alpha characters at beginning of string */
while (!isalpha(tmpStr[sIndex])) {
sIndex++;
}
toupper(tmpStr[sIndex]);
myString = [NSString stringWithCString:tmpStr encoding:NSUTF8StringEncoding];
I'm at work and don't have my Mac to test this on, but if I remember correctly, you couldn't use [myString cStringUsingEncoding:NSUTF8StringEncoding] because it returns a const char *.
In swift you can do it as followed by using this extension:
extension String {
func ucfirst() -> String {
return (self as NSString).stringByReplacingCharactersInRange(NSMakeRange(0, 1), withString: (self as NSString).substringToIndex(1).uppercaseString)
}
}
calling your string like this:
var ucfirstString:String = "test".ucfirst()
I know the question asks specifically for an Objective C answer, however here is a solution for Swift 2.0:
let txt = "hi my friends!"
var sentencecaseString = ""
for (index, character) in txt.characters.enumerate() {
if 0 == index {
sentencecaseString += String(character).uppercaseString
} else {
sentencecaseString.append(character)
}
}
Or as an extension:
func sentencecaseString() -> String {
var sentencecaseString = ""
for (index, character) in self.characters.enumerate() {
if 0 == index {
sentencecaseString += String(character).uppercaseString
} else {
sentencecaseString.append(character)
}
}
return sentencecaseString
}