How to replace string occur in NSMutableString from another string - objective-c

I have a NSMutableString that contains a word twice.(e.g. /abc ...................... /abc).
Now I want to replace these two occurrences of /abc with /xyz. I want to replace only first and last occurence no other occurences.
- (NSUInteger)replaceOccurrencesOfString:(NSString *)target
withString:(NSString *)replacement
options:(NSStringCompareOptions)opts
range:(NSRange)searchRange
I find this instance method of NSMutableString but I am not able to use it in my case.
Anyone have any solution??

You can first find the two ranges and then replace them seperately:
NSMutableString *s = [NSMutableString stringWithString:#"/abc asdfpjklwe /abc"];
NSRange a = [s rangeOfString:#"/abc"];
NSRange b = [s rangeOfString:#"/abc" options:NSBackwardsSearch];
if ((a.location == NSNotFound) || (b.location == NSNotFound)) {
// at least one of the substrings not present
} else {
[s replaceCharactersInRange:a withString:#"/xyz"];
[s replaceCharactersInRange:b withString:#"/xyz"];
}

Related

Check if NSString only contains one character repeated

I want to know a simple and fast way to determine if all characters in an NSString are the same.
For example:
NSString *string = "aaaaaaaaa"
=> return YES
NSString *string = "aaaaaaabb"
=> return NO
I know that I can achieve it by using a loop but my NSString is long so I prefer a shorter and simpler way.
you can use this, replace first character with null and check lenght:
-(BOOL)sameCharsInString:(NSString *)str{
if ([str length] == 0 ) return NO;
return [[str stringByReplacingOccurrencesOfString:[str substringToIndex:1] withString:#""] length] == 0 ? YES : NO;
}
Here are two possibilities that fail as quickly as possible and don't (explicitly) create copies of the original string, which should be advantageous since you said the string was large.
First, use NSScanner to repeatedly try to read the first character in the string. If the loop ends before the scanner has reached the end of the string, there are other characters present.
NSScanner * scanner = [NSScanner scannerWithString:s];
NSString * firstChar = [s substringWithRange:[s rangeOfComposedCharacterSequenceAtIndex:0]];
while( [scanner scanString:firstChar intoString:NULL] ) continue;
BOOL stringContainsOnlyOneCharacter = [scanner isAtEnd];
Regex is also a good tool for this problem, since "a character followed by any number of repetitions of that character" is in very simply expressed with a single back reference:
// Match one of any character at the start of the string,
// followed by any number of repetitions of that same character
// until the end of the string.
NSString * patt = #"^(.)\\1*$";
NSRegularExpression * regEx =
[NSRegularExpression regularExpressionWithPattern:patt
options:0
error:NULL];
NSArray * matches = [regEx matchesInString:s
options:0
range:(NSRange){0, [s length]}];
BOOL stringContainsOnlyOneCharacter = ([matches count] == 1);
Both these options correctly deal with multi-byte and composed characters; the regex version also does not require an explicit check for the empty string.
use this loop:
NSString *firstChar = [str substringWithRange:NSMakeRange(0, 1)];
for (int i = 1; i < [str length]; i++) {
NSString *ch = [str substringWithRange:NSMakeRange(i, 1)];
if(![ch isEqualToString:firstChar])
{
return NO;
}
}
return YES;

Why does -[NSString compare:options:] return true when the strings are not equal?

I am checking if a string appears twice in a row within an array. This code doesn't seem to work, as it just prints out the entire array. What am I missing?
NSString *nameString =
[NSString stringWithContentsOfFile:#"/usr/share/dict/words"
encoding:NSUTF8StringEncoding
error:NULL];
NSArray *names = [nameString componentsSeparatedByString:#"\n"];
//Save last item
NSMutableString *lastOne = [NSMutableString stringWithCapacity:20];
// Go through the array one string at a time
for (NSString *n in names) {
if ([n compare:lastOne options:NSCaseInsensitiveSearch]) {
NSLog(#"%#", n);
}
[lastOne setString:n];
}
compare: and related functions don't return booleans, they return an NSComparisonResult. If you want to see if a string is equal you should instead use
if ([n compare:lastOne options:NSCaseInsensitiveSearch] == NSOrderedSame)

Objective C - Split NSString into Array, max count

I am new to Objective-C, so I am trying to split an String into an Array in this format:
NSString *str = #":49:DE:Bahnhofsstr:12:39:11";
NSArray *arr = [str componentsSeparatedByString:#":"];
I receive the following objects in arr:
[#"", #"49", #"DE", #"Bahnhofsstr", #"12", #"39", #"11"]
But I need it in this format:
[#"", #"49", #"DE", #"Bahnhofsstr:12:39:11"]
Anyone have any ideas?
You can use a regular expression. The one you want is this:
^([^:]*):([^:]*):([^:]*):(.*)$
The above matches three sequences of characters without colons in separated by colons and then a fourth group consisting of any kind of character. The ^ at the front and the $ at the end match the beginning and the end of the string respectively otherwise nonsense like 1:2:3:4:49:DE:Bahnhofsstr:12:39:11 would match because there is a match embedded in the string.
The parenthesis delimit capture groups which will be returned to you once the regular expression matching has been done. The first capture group is all the characters up to the first colon. The second capture group is all the characters between the first and second colons. The third capture group is all the characters between the second and third colons and the fourth capture group is all the characters after the third colon.
There is also a zeroth capture group which is the entire matching sequence.
Here's how to use this in Objective-C:
NSString* pattern = #"^([^:]*):([^:]*):([^:]*):(.*)$";
NSString* line = #":49:DE:Bahnhofsstr:12:39:11";
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern: pattern
options: 0
error: &error];
if (regex == nil)
{
NSLog(#"Invalid regular expression %#, %#", pattern, error);
}
else
{
NSArray* matches = [regex matchesInString: line
options: 0
range: NSMakeRange(0, [line length])];
if ([matches count] == 1)
{
// Should only be one match
NSTextCheckingResult* result = [matches objectAtIndex: 0];
NSMutableArray* captureGroups = [[NSMutableArray alloc] init];
// Omit capture group 0 because it will be the whole string
for (int i = 1 ; i < [result numberOfRanges] ; i++)
{
NSRange groupRange = [result rangeAtIndex: i];
NSString* captureGroup = [line substringWithRange: groupRange];
[captureGroups addObject: captureGroup];
}
NSLog(#"The fields are %#", captureGroups);
}
else
{
// match error
}
}
Regular expressions, as proposed by JeremyP, are an obvious solution to this sort of problem.
Some people don't like regexes, though, so another solution is to use NSScanner which is also made to scan strings and read the result into variables. Given that the delimiter is the same for all fields, it even lends itself to use a nice loop, reducing the tedious scanning code.
Here is an example:
NSString *str = #":49:DE:Bahnhofsstr:12:39:11";
const NSUInteger nFields = 4;
NSScanner *myScanner = [NSScanner scannerWithString: str];
NSMutableArray *arr = [NSMutableArray array];
for (NSUInteger i = 0; i < nFields - 1; i++) {
NSString *field;
// The BOOLs here really ought to be checked
BOOL found = [myScanner scanUpToString: #":" intoString: &field];
BOOL passedDelimiter = [myScanner scanString: #":" intoString: NULL];
[arr addObject: field ?: #"" ];
}
NSString *lastField = [[myScanner string] substringFromIndex:[myScanner scanLocation]];
[arr addObject: lastField];
That last line to read the remainder of the string is taken straight from the docs for NSScanner.

Separating NSString into NSArray, but allowing quotes to group words

I have a search string, where people can use quotes to group phrases together, and mix this with individual keywords. For example, a string like this:
"Something amazing" rooster
I'd like to separate that into an NSArray, so that it would have Something amazing (without quotes) as one element, and rooster as the other.
Neither componentsSeparatedByString nor componentsSeparatedByCharactersInSet seem to fit the bill. Is there an easy way to do this, or should I just code it up myself?
You probably will have to code some of this up yourself, but the NSScanner should be a good basis on which to build. If you use the scanUpToCharactersInSet method to look for everything up to your next whitespace or quote character to can pick off words. Once you encounter a quite character, you could continue to scan using just the quote in the character set to end at, so that spaces within the quotes don't result in the end of a token.
I made a simple way to do this using NSScanner:
+ (NSArray *)arrayFromTagString:(NSString *)string {
NSScanner *scanner = [NSScanner scannerWithString:string];
NSString *substring;
NSMutableArray *array = [[NSMutableArray alloc] init];
while (scanner.scanLocation < string.length) {
// test if the first character is a quote
unichar character = [string characterAtIndex:scanner.scanLocation];
if (character == '"') {
// skip the first quote and scan everything up to the next quote into a substring
[scanner setScanLocation:(scanner.scanLocation + 1)];
[scanner scanUpToString:#"\"" intoString:&substring];
[scanner setScanLocation:(scanner.scanLocation + 1)]; // skip the second quote too
}
else {
// scan everything up to the next space into the substring
[scanner scanUpToString:#" " intoString:&substring];
}
// add the substring to the array
[array addObject:substring];
//if not at the end, skip the space character before continuing the loop
if (scanner.scanLocation < string.length) [scanner setScanLocation:(scanner.scanLocation + 1)];
}
return array.copy;
}
This method will convert the array back to a tag string, re-quoting the multi-word tags:
+ (NSString *)tagStringFromArray:(NSArray *)array {
NSMutableString *string = [[NSMutableString alloc] init];
NSRange range;
for (NSString *substring in array) {
if (string.length > 0) {
[string appendString:#" "];
}
range = [substring rangeOfString:#" "];
if (range.location != NSNotFound) {
[string appendFormat:#"\"%#\"", substring];
}
else [string appendString:substring];
}
return string.description;
}
I ended up going with a regular expression as I was already using RegexKitLite, and creating this NSString+SearchExtensions category.
.h:
// NSString+SearchExtensions.h
#import <Foundation/Foundation.h>
#interface NSString (SearchExtensions)
-(NSArray *)searchParts;
#end
.m:
// NSString+SearchExtensions.m
#import "NSString+SearchExtensions.h"
#import "RegexKitLite.h"
#implementation NSString (SearchExtensions)
-(NSArray *)searchParts {
__block NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:5];
[self enumerateStringsMatchedByRegex:#"\\w+|\"[\\w\\s]*\"" usingBlock: ^(NSInteger captureCount,
NSString * const capturedStrings[captureCount],
const NSRange capturedRanges[captureCount],
volatile BOOL * const stop) {
NSString *result = [capturedStrings[0] stringByReplacingOccurrencesOfRegex:#"\"" withString:#""];
NSLog(#"Match: '%#'", result);
[items addObject:result];
}];
return [items autorelease];
}
#end
This returns an NSArray of strings with the search strings, removing the double quotes that surround the phrases.
If you'll allow a slightly different approach, you could try Dave DeLong's CHCSVParser. It is intended to parse CSV strings, but if you set the space character as the delimiter, I am pretty sure you will get the intended behavior.
Alternatively, you can peek into the code and see how it handles quoted fields - it is published under the MIT license.
I would run -componentsSeparatedByString:#"\"" first, then create a BOOL isPartOfQuote, initialized to YES if the first character of the string was a ", but otherwise set to NO.
Then create a mutable array to return:
NSMutableArray* masterArray = [[NSMutableArray alloc] init];
Then, create a loop over the array returned from the separation:
for(NSString* substring in firstSplitArray) {
NSArray* secondSplit;
if (isPartOfQuote == NO) {
secondSplit = [substring componentsSeparatedByString:#" "];
}
else {
secondSplit = [NSArray arrayWithObject: substring];
}
[masterArray addObjectsFromArray: secondSplit];
isPartOfQuote = !isPartOfQuote;
}
Then return masterArray from the function.

Objective-C: How to extract part of a String (e.g. start with '#')

I have a string as shown below,
NSString * aString = #"This is the #substring1 and #subString2 I want";
How can I select only the text starting with '#' (and ends with a space), in this case 'subString1' and 'subString2'?
Note: Question was edited for clarity
You can do this using an NSScanner to split the string up. This code will loop through a string and fill an array with substrings.
NSString * aString = #"This is the #substring1 and #subString2 I want";
NSMutableArray *substrings = [NSMutableArray new];
NSScanner *scanner = [NSScanner scannerWithString:aString];
[scanner scanUpToString:#"#" intoString:nil]; // Scan all characters before #
while(![scanner isAtEnd]) {
NSString *substring = nil;
[scanner scanString:#"#" intoString:nil]; // Scan the # character
if([scanner scanUpToString:#" " intoString:&substring]) {
// If the space immediately followed the #, this will be skipped
[substrings addObject:substring];
}
[scanner scanUpToString:#"#" intoString:nil]; // Scan all characters before next #
}
// do something with substrings
[substrings release];
Here is how the code works:
Scan up to a #. If it isn't found, the scanner will be at the end of the string.
If the scanner is at the end of the string, we are done.
Scan the # character so that it isn't in the output.
Scan up to a space, with the characters that are scanned stored in substring. If either the # was the last character, or was immediately followed by a space, the method will return NO. Otherwise it will return YES.
If characters were scanned (the method returned YES), add substring to the substrings array.
GOTO 1
[aString substringWithRange:NSMakeRange(13, 10)]
would give you substring1
You can calculate the range using:
NSRange startRange = [aString rangeOfString:#"#"];
NSRange endRange = [original rangeOfString:#"1"];
NSRange searchRange = NSMakeRange(startRange.location , endRange.location);
[aString substringWithRange:searchRange]
would give you substring1
Read more:
Position of a character in a NSString or NSMutableString
and
http://iosdevelopertips.com/cocoa/nsrange-and-nsstring-objects.html
Pretty simple, easy to understand version avoiding NSRange stuff:
NSArray * words = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSMutableArray * mutableWords = [NSMutableArray new];
for (NSString * word in words){
if ([word length] > 1 && [word characterAtIndex:0] == '#'){
NSString * editedWord = [word substringFromIndex:1];
[mutableWords addObject:editedWord];
}
}
Assuming that you are looking to find the first string that starts with a pound, and ends with a space, this might work. I don't have XCode in front of me, so forgive me if there's a syntax error or length off by 1 somewhere:
-(NSString *)StartsWithPound:(NSString *)str {
NSRange range = [str rangeOfString:#"#"];
if(range.length) {
NSRange rangeend = [str rangeOfString:#" " options:NSLiteralSearch range:NSMakeRange(range.location,[str length] - range.location - 1)];
if(rangeend.length) {
return [str substringWithRange:NSMakeRange(range.location,rangeend.location - range.location)];
}
else
{
return [str substringFromIndex:range.location];
}
}
else {
return #"";
}
}
Another simple solution:
NSRange hashtag = [aString rangeOfString:#"#"];
NSRange word = [[aString substringFromIndex:hashtag.location] rangeOfString:#" "];
NSString *hashtagWord = [aString substringWithRange:NSMakeRange(hashtag.location, word.location)];
This is what I'd do:
NSString *givenStringWithWhatYouNeed = #"What you want to look through";
NSArray *listOfWords = [givenStringWithWhatYouNeed componentsSeparatedByString:#" "];
for (NSString *word in listOfWords) {
if ([[word substringWithRange:NSMakeRange(0, 1)]isEqualToString:#"#"]) {
NSString *whatYouWant = [[word componentsSeparatedByString:#"#"]lastObject];
}
}
Then you can do what you need with the whatYouWant instances. If you want to know which string it is (if it's the substring 1 or 2), check the index of of word string in the listOfWords array.
I hope this helps.
A general and simple code to select all the words starting with "#" in a NSString is:
NSString * aString = #"This is the #substring1 and #subString2 ...";
NSMutableArray *selection=#[].mutableCopy;
while ([aString rangeOfString:#"#"].location != NSNotFound)
{
aString = [aString substringFromIndex:[aString rangeOfString:#"#"].location +1];
NSString *item=([aString rangeOfString:#" "].location != NSNotFound)?[aString substringToIndex:[aString rangeOfString:#" "].location]:aString;
[selection addObject:item];
}
if you still need the original string you can do a copy.
The inline conditional is used in case your selected item is the last word