What would be the most efficient way to convert a string like "ThisStringIsJoined" to "This String Is Joined" in objective-c?
I receive strings like this from a web service thats out of my control and I would like to present the data to the user, so I would just like to tidy it up a bit by adding spaces infront of each uppercase word. The strings are always formatted with each word beginning in an uppercase letter.
I'm quite new to objective-c so cant really figure this one out.
Thanks
One way of achieving this is as follows:
NSString *string = #"ThisStringIsJoined";
NSRegularExpression *regexp = [NSRegularExpression
regularExpressionWithPattern:#"([a-z])([A-Z])"
options:0
error:NULL];
NSString *newString = [regexp
stringByReplacingMatchesInString:string
options:0
range:NSMakeRange(0, string.length)
withTemplate:#"$1 $2"];
NSLog(#"Changed '%#' -> '%#'", string, newString);
The output in this case would be:
'ThisStringIsJoined' -> 'This String Is Joined'
You might want to tweak the regular expression to you own needs. You might want to make this into a category on NSString.
NSRegularExpressions are the way to go, but as trivia, NSCharacterSet can also be useful:
- (NSString *)splitString:(NSString *)inputString {
int index = 1;
NSMutableString* mutableInputString = [NSMutableString stringWithString:inputString];
while (index < mutableInputString.length) {
if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:[mutableInputString characterAtIndex:index]]) {
[mutableInputString insertString:#" " atIndex:index];
index++;
}
index++;
}
return [NSString stringWithString:mutableInputString];
}
Here's a category on NSString that will do what you want. This will handle non-ASCII letters. It will also split "IDidAGoodThing" properly.
#implementation NSString (SeparateCapitalizedWords)
-(NSString*)stringBySeparatingCapitalizedWords
{
static NSRegularExpression * __regex ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError * error = nil ;
__regex = [ NSRegularExpression regularExpressionWithPattern:#"[\\p{Uppercase Letter}]" options:0 error:&error ] ;
if ( error ) { #throw error ; }
});
NSString * result = [ __regex stringByReplacingMatchesInString:self options:0 range:(NSRange){ 1, self.length - 1 } withTemplate:#" $0" ] ;
return result ;
}
#end
Here is Swift Code (objective c code by webstersx), Thanks !
var str: NSMutableString = "iLoveSwiftCode"
var str2: NSMutableString = NSMutableString()
for var i:NSInteger = 0 ; i < str.length ; i++ {
var ch:NSString = str.substringWithRange(NSMakeRange(i, 1))
if(ch .rangeOfCharacterFromSet(NSCharacterSet.uppercaseLetterCharacterSet()).location != NSNotFound) {
str2 .appendString(" ")
}
str2 .appendString(ch)
}
println("\(str2.capitalizedString)")
}
Output : I Love Swift Code
For anyone who came here looking for the similar question answered in Swift:
Perhaps a cleaner (adding to Sankalp's answer), and more 'Swifty' approach:
func addSpaces(to givenString: String) -> String{
var string = givenString
//indexOffset is needed because each time replaceSubrange is called, the resulting count is incremented by one (owing to the fact that a space is added to every capitalised letter)
var indexOffset = 0
for (index, character) in string.characters.enumerated(){
let stringCharacter = String(character)
//Evaluates to true if the character is a capital letter
if stringCharacter.lowercased() != stringCharacter{
guard index != 0 else { continue } //"ILoveSwift" should not turn into " I Love Swift"
let stringIndex = string.index(string.startIndex, offsetBy: index + indexOffset)
let endStringIndex = string.index(string.startIndex, offsetBy: index + 1 + indexOffset)
let range = stringIndex..<endStringIndex
indexOffset += 1
string.replaceSubrange(range, with: " \(stringCharacter)")
}
}
return string
}
You call the function like so:
var string = "iLoveSwiftCode"
addSpaces(to: string)
//Result: string = "i Love Swift Code"
Alternatively, if you prefer extensions:
extension String{
mutating func seperatedWithSpaces(){
//indexOffset is needed because each time replaceSubrange is called, the resulting count is incremented by one (owing to the fact that a space is added to every capitalised letter)
var indexOffset = 0
for (index, character) in characters.enumerated(){
let stringCharacter = String(character)
if stringCharacter.lowercased() != stringCharacter{
guard index != 0 else { continue } //"ILoveSwift" should not turn into " I Love Swift"
let stringIndex = self.index(self.startIndex, offsetBy: index + indexOffset)
let endStringIndex = self.index(self.startIndex, offsetBy: index + 1 + indexOffset)
let range = stringIndex..<endStringIndex
indexOffset += 1
self.replaceSubrange(range, with: " \(stringCharacter)")
}
}
}
}
Call the method from a string:
var string = "iLoveSwiftCode"
string.seperatedWithSpaces()
//Result: string = "i Love Swift Code"
You could try making a new string that is a lowercase copy of the original string. Then compare the two strings and insert spaces wherever the characters are different.
Use the NSString method to turn to lowercase.
- (NSString *)lowercaseString
Related
I have a long string that contain keywords that start and end with the percent sign. E.g.:
My name is %user_username% and I live at %location_address%. You can
reach me at %user_phone%.
What method would I use to extract all strings that begin and end with % and put those into an NSArray so that I can replace them with their correct text representations?
Assuming that there are no % signs inside your strings of interest (e.g "a%ab%b%c"), you could use the componentsSeparatedByString: or componentsSeparatedByCharactersInSet: to get an array of strings separated by the % sign. From there, it's pretty easy to figure out which strings in that array are between the percent signs, and which are unnecessary.
I think internally though, those methods are likely implemented as something like a loop looking for %s. Maybe they parallelize the search on big strings, or use special knowledge of the internal structure of the string to make things faster -- those are the only ways I can see to speed up the search, assuming that you're stuck with keeping it all in a % delimited string (if speed is really an issue, then the answer is probably to use an alternative representation).
This is what I came up with that works:
- (NSArray *)replaceKeywords:(NSString *)keywordString {
NSString *start = #"%";
NSString *end = #"%";
NSMutableArray* strings = [NSMutableArray arrayWithCapacity:0];
NSRange startRange = [keywordString rangeOfString:start];
for( ;; ) {
if (startRange.location != NSNotFound) {
NSRange targetRange;
targetRange.location = startRange.location + startRange.length;
targetRange.length = [keywordString length] - targetRange.location;
NSRange endRange = [keywordString rangeOfString:end options:0 range:targetRange];
if (endRange.location != NSNotFound) {
targetRange.length = endRange.location - targetRange.location;
[strings addObject:[keywordString substringWithRange:targetRange]];
NSRange restOfString;
restOfString.location = endRange.location + endRange.length;
restOfString.length = [keywordString length] - restOfString.location;
startRange = [keywordString rangeOfString:start options:0 range:restOfString];
} else {
break;
}
} else {
break;
}
}
return strings;
}
I slightly modified the method from Get String Between Two Other Strings in ObjC
please help me with this problem.
I want to check if the targetString match the keyword or not. Consider some character may different, but should still return true.
Example:
targetString = #"#ß<"
keyword = #"abc", #"∂B(", #"#Aß<"
result: all must return true.
(Matched.targetString and all keyword are the same.)
Consider me have an array, contains list of character set that can be the same:
NSArray *variants = [NSArray arrayWithObjects:#"aA#∂", #"bBß", #"c©C<(", nil]
So that when matching, with this rule, it can match as the example above.
Here is what i've done so far (using recursion):
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
char c = [keyWord characterAtIndex:i];
NSString *rs = [self variantsWithChar:c];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:[NSString stringWithCharacters:[aString characterAtIndex:i] length:1]].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
- (NSString *) variantsWithChar:(char) c {
for (NSString *s in self.variants) {
if ([s rangeOfString:[NSString stringWithFormat:#"%c",c]].location != NSNotFound) {
return s;
}
}
return [NSString stringWithFormat:#"%c", c];
}
The main problem is, variantsWithChar: doesn't return the correct string. I don't know which datatype and which function should I use here. Please help.
For thou who know ruby, here's the example in ruby. It work super fine!
require 'test/unit/assertions'
include Test::Unit::Assertions
class String
def matching?(keyword)
length >= keyword.length && (keyword.chars.zip(chars).all? { |cs| variants(cs[0]).include?(cs[1]) } || slice(1, length - 1).matching?(keyword))
end
private
VARIANTS = ["aA#∂", "bBß", "c©C<("]
def variants(c)
VARIANTS.find { |cs| cs.include?(c) } || c
end
end
assert "abc".matching?("#ß<")
PS: The fact is, it's containt a japanese character set that sounds the same (like あア, いイ... for thou who know japanese)
PS 2: Please feel free to edit this Question, since my engrish is sooo bad. I may not tell all my thought.
PS 3: And, maybe some may comment about the performance. Like, search about 10,000 target words, with nearly 100 variants, each variant have at most 4 more same characters.
So first off, ignore comments about ASCII and stop using char. NSString and CFString use unichar
If what you really want to do is transpose hiragana and katakana you can do that with CFStringTransform()
It wraps the ICU libraries included in OS X and iOS.
It makes it very simple.
Search for that function and you will find examples of how to use it.
After a while (a day) working on the code above, I finally get it through. But don't know about the performance. Someone comment and help me improve about performance, please. Thanks.
- (BOOL) test:(NSString*)aString include:(NSString*) keyWord doTrim:(BOOL)doTrim {
// break recursion.
if([aString length] < [keyWord length]) return false;
// First, loop through each keyword's character
for (NSUInteger i = 0; i < [keyWord length]; i++) {
// Get #"aA#∂", #"bBß", #"c©C<(" or only the character itself.
// like, if the keyword's character is A, return the string #"aA#∂".
// If the character is not in the variants set, eg. P, return #"P"
NSString* c = [NSString stringWithFormat:#"%C", [keyWord characterAtIndex:i]];
NSString *rs = [self variantsWithChar:c];
NSString *theTargetChar = [NSString stringWithFormat:#"%C", [aString characterAtIndex:i]];
// Check if rs (#"aA#∂" or #"P") contains aString[i] character
if([rs rangeOfString:theTargetChar].location == NSNotFound) {
// If not the same char, remove first char in targetString (aString), recursion to match again.
return [self test:[aString substringFromIndex:1] include:keyWord doTrim:NO];
}
}
// If all match with keyword, return true.
return true;
}
If you remove all comment, it'll be pretty short...
////////////////////////////////////////
- (NSString *) variantsWithChar:(NSString *) c{
for (NSString *s in self.variants) {
if ([s rangeOfString:c].location != NSNotFound) {
return s;
}
}
return c;
}
You could try comparing ascii values of the japanese characters in the variants's each character's ascii value. These japanese characters aren't treated like usual characters or string. Hence, string functions like rangeOfString won't work on them.
to be more precise: have a look at the following code.
it will search for "∂" in the string "aA#∂"
NSString *string = #"aA#∂";
NSMutableSet *listOfAsciiValuesOfString = [self getListOfAsciiValuesForString:string]; //method definition given below
NSString *charToSearch = #"∂";
NSNumber *ascii = [NSNumber numberWithInt:[charToSearch characterAtIndex:0]];
int countBeforeAdding = [listOfAsciiValuesOfString count],countAfterAdding = 0;
[listOfAsciiValuesOfString addObject:ascii];
countAfterAdding = [listOfAsciiValuesOfString count];
if(countAfterAdding == countBeforeAdding){ //element found
NSLog(#"element exists"); //return string
}else{
NSLog(#"Doesnt exists"); //return char
}
===================================
-(NSMutableSet*)getListOfAsciiValuesForString:(NSString*)string{
NSMutableSet *set = [[NSMutableSet alloc] init];
for(int i=0;i<[string length];i++){
NSNumber *ascii = [NSNumber numberWithInt:[string characterAtIndex:i]];
[set addObject:ascii];
}
return set;
}
I know there are a few different ways to find text in file, although I haven't found a way to return the text after the string I'm searching for. For example, if I was to search file.txt for the term foo and wanted to return bar, how would I do that without knowing it's bar or the length?
Here's the code I'm using:
if (!fileContentsString) {
NSLog(#"Error reading file");
}
// Create the string to search for
NSString *search = #"foo";
// Search the file contents for the given string, put the results into an NSRange structure
NSRange result = [fileContentsString rangeOfString:search];
// -rangeOfString returns the location of the string NSRange.location or NSNotFound.
if (result.location == NSNotFound) {
// foo not found. Bail.
NSLog(#"foo not found in file");
return;
}
// Continue processing
NSLog(#"foo found in file");
}
you could use [NSString substringFromIndex:]
if (result.location == NSNotFound)
{
// foo not found. Bail.
NSLog(#"foo not found in file");
return;
}
else
{
int startingPosition = result.location + result.length;
NSString* foo = [fileContentsString substringFromIndex:startingPosition]
NSLog(#"found foo = %#",foo);
}
You might want to use RegexKitLite and perform a regex look up:
NSArray * captures = [myFileString componentsMatchedByRegex:#"foo\\s+(\\w+)"];
NSString * wordAfterFoo = captures[1];
Not test though.
hi here is my function, but everytime when i try to init and alloc foo it falls, can you tell me why?
-(NSString*)modifyTheCode:(NSString*) theCode{
if ([[theCode substringToIndex:1]isEqualToString:#"0"]) {
if ([theCode length] == 1 ) {
return #"0000000000";
}
NSString* foo = [[NSString alloc]initWithString:[theCode substringWithRange:NSMakeRange(2, [theCode length]-1)]];
return [self modifyTheCode:foo];
} else {
return theCode;
}
}
the error message:
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3.2 (8H7)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
replace this line
NSString* foo = [[NSString alloc]initWithString:[theCode substringWithRange:NSMakeRange(2, [theCode length]-1)]];
with this line
NSString* foo = [[NSString alloc]initWithString:[theCode substringWithRange:NSMakeRange(1, [theCode length]-1)]];
and try..
What is the error message?
If you are working with NSRange maybe you should check the length of theCode first.
Because the range is invalid. NSRange has two members, location and length. The range you give starts at the third character of the string and has the length of the string minus one. So your length is one character longer than the amount of characters left in the string.
Suppose theCode is #"0123". The range you create is { .location = 2, .length = 3 } This represents:
0123
^ start of range is here
^ start of range + 3 off the end of the string.
By the way, you'll be pleased to know that there are convenience methods so you don't have to mess with ranges. You could do:
if ([theCode hasPrefix: #"0"])
{
NSString* foo = [theCode substringFromIndex: 1]; // assumes you just want to strip off the leading #"0"
return [self modifyTheCode:foo];
} else {
return theCode;
}
By the way, your original code leaked foo because you never released it.
suppose I have string like "this str1ng for test" now i want to check if character at position [i-1] and [i+1] are both alphabet but character at [i] is number, like this example in word "str1ng" then character at position [i] replaced by appropriate alphabet.
or vice versa.
I need this for post processing for output of OCR. TQ
You might have an easier time using Regular Expressions.
NSString are immutable, so you'll have to create a new NSMutableString from it, and mutate this copy, or to allocate a unichar* buffer, copy data from the NSString, perform the correction, and then recreate a new NSString from the result. Once you're working on a mutable copy of the string, you can use whatever algorithm you want.
So you'll need to have a function like that:
- (NSString*)correctOCRErrors:(NSString*)string
{
BOOL hasError = NO;
for (int i = 0; i < [string length]; ++ i)
{
if (isIncorrect([string characterAtIndex:i]))
{
hasError = YES;
break;
}
}
if (hasError)
{
unichar* buffer = (unichar*)malloc([string length]);
for (int i = 0; i < [string length]; ++ i)
{
unichar chr = [string characterAtIndex:i];
if (isIncorrect(chr))
chr = correctChar(chr);
buffer[i] = chr;
}
string = [[[NSString alloc] initWithCharactersNoCopy:buffer length:[string length] freeWhenDone:YES] autorelease];
}
return string;
}
You can access character in a NSString by passing a message charAtIndex:(NSUInteger)index.
And now you can get the ascii value at the particular index you are interested in and change it according to your requirement.
NSString Ref
Hope this is helpful !