Suppose I have the following string:
Mary had a little lamb, she also had a little sheep.
My goal is to extract every word after had and before the period. (In this case a little sheep).
I tried this way:
- (NSInteger)indexOf:(NSString*)substring from:(NSInteger)starts {
NSRange r;
r.location = starts;
r.length = [self length] - r.location;
NSRange index = [self rangeOfString:substring options:NSLiteralSearch range:r];
if (index.location == NSNotFound) {
return -1;
}
return index.location + index.length;
}
As in:
NSInteger sheepSpot = [string indexOf:#"had" from:23];
// I know that I want to grab everything after the index of sheepSpot but before the period.
// Suppose now that I have an arbitrary number of periods in the sentence, how can I extract the above text without getting the wrong thing?
Try this one:
-(NSRange)lastRangeOf:(NSString *)substring inString:(NSString *)string{
return [string rangeOfString:substring options:NSBackwardsSearch];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
NSString *string=#"had Mary had a little lamb, she also had a had little sheep.";
NSString *word=#"had";
NSRange hadRange=[self lastRangeOf:word inString:string];
NSInteger start=hadRange.location+word.length;
NSInteger lengthToCut=string.length-start;
NSString *substring=[string substringWithRange:NSMakeRange(start,lengthToCut)];
NSLog(#"->%#",substring);
}
This code will find the last "had" and the last period and give you everything in between:
NSString *text = #"Mary had a little lamb, she also had a little sheep.";
NSString *subtext = nil;
NSRange lastHadRange = [text rangeOfString:#"had" options:NSBackwardsSearch];
if (lastHadRange.location != NSNotFound) {
NSRange lastPeriodRange = [text rangeOfString:#"." options:NSBackwardsSearch];
if (lastPeriodRange.location != NSNotFound) {
NSUInteger start = lastHadRange.location + lastHadRange.length;
NSUInteger length = lastPeriodRange.location - start;
subtext = [text substringWithRange:NSMakeRange(start, length)];
}
}
NSLog(#"Subtext is: %#", subtext);
Related
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
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];
Here's what I want to do. I have 2 strings and I want to determine if one string is a permutation of another. I was thinking to simply remove the characters from string A from string B to determine if any characters are left. If no, then it passes.
However, I need to make sure that only 1 instance of each letter is removed (not all occurrences) unless there are multiple letters in the word.
An example:
String A: cant
String B: connect
Result: -o-nec-
Experimenting with NSString and NSScanner has yielded no results so far.
Hmmm, let's have a go:
NSString *stringA = #"cant";
NSString *stringB = #"connect";
NSUInteger length = [stringB length];
NSMutableCharacterSet *charsToRemove = [NSMutableCharacterSet characterSetWithCharactersInString:stringA];
unichar *buffer = calloc(length, sizeof(unichar));
[stringB getCharacters:buffer range:NSMakeRange(0, length)];
for (NSUInteger i = 0; i < length; i++)
{
if ([charsToRemove characterIsMember:buffer[i]])
{
[charsToRemove removeCharactersInRange:NSMakeRange(buffer[i], 1)];
buffer[i] = '-';
}
}
NSString *result = [NSString stringWithCharacters:buffer length:length];
free (buffer);
An inefficient yet simple way might be something like this (this is implemented as a category on NSString, but it could just as easily be a method or function taking two strings):
#implementation NSString(permutation)
- (BOOL)isPermutation:(NSString*)other
{
if( [self length] != [other length] ) return NO;
if( [self isEqualToString:other] ) return YES;
NSUInteger length = [self length];
NSCountedSet* set1 = [[[NSCountedSet alloc] initWithCapacity:length] autorelease];
NSCountedSet* set2 = [[[NSCountedSet alloc] initWithCapacity:length] autorelease];
for( int i = 0; i < length; i++ ) {
NSRange range = NSMakeRange(i, 1);
[set1 addObject:[self substringWithRange:range]];
[set2 addObject:[self substringWithRange:range]];
}
return [set1 isEqualTo:set2];
}
#end
This returns what your example asks for...
NSString* a = #"cant";
NSString* b = #"connect";
NSMutableString* mb = [NSMutableString stringWithString:b];
NSUInteger i;
for (i=0; i<[a length]; i++) {
NSString* theLetter = [a substringWithRange:NSMakeRange(i, 1)];
NSRange r = [mb rangeOfString:theLetter];
if (r.location != NSNotFound) {
[mb replaceCharactersInRange:r withString:#"-"];
}
}
NSLog(#"mb: %#", mb);
However, I wouldn't call that a permutation. To me a permutation would only hold true if all the characters from string "a" were contained by string "b". In your example, since the letter a in cant isn't in string b then I would say that cant is not a permutation of connect. With this definition I would use this:
-(BOOL)isString:(NSString*)firstString aPermutationOfString:(NSString*)secondString {
BOOL isPermutation = YES;
NSMutableString* mb = [NSMutableString stringWithString:secondString];
NSUInteger i;
for (i=0; i<[firstString length]; i++) {
NSString* theLetter = [firstString substringWithRange:NSMakeRange(i, 1)];
NSRange r = [mb rangeOfString:theLetter];
if (r.location != NSNotFound) {
[mb deleteCharactersInRange:r];
} else {
return NO;
}
}
return isPermutation;
}
How can I get the number of times an NSString (for example, #"cake") appears in a larger NSString (for example, #"Cheesecake, apple cake, and cherry pie")?
I need to do this on a lot of strings, so whatever method I use would need to be relatively fast.
Thanks!
This isn't tested, but should be a good start.
NSUInteger count = 0, length = [str length];
NSRange range = NSMakeRange(0, length);
while(range.location != NSNotFound)
{
range = [str rangeOfString: #"cake" options:0 range:range];
if(range.location != NSNotFound)
{
range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
count++;
}
}
A regex like the one below should do the job without a loop interaction...
Edited
NSString *string = #"Lots of cakes, with a piece of cake.";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"cake" options:NSRegularExpressionCaseInsensitive error:&error];
NSUInteger numberOfMatches = [regex numberOfMatchesInString:string options:0 range:NSMakeRange(0, [string length])];
NSLog(#"Found %i",numberOfMatches);
Only available on iOS 4.x and superiors.
was searching for a better method then mine but here's another example:
NSString *find = #"cake";
NSString *text = #"Cheesecake, apple cake, and cherry pie";
NSInteger strCount = [text length] - [[text stringByReplacingOccurrencesOfString:find withString:#""] length];
strCount /= [find length];
I would like to know which one is more effective.
And I made an NSString category for better usage:
// NSString+CountString.m
#interface NSString (CountString)
- (NSInteger)countOccurencesOfString:(NSString*)searchString;
#end
#implementation NSString (CountString)
- (NSInteger)countOccurencesOfString:(NSString*)searchString {
NSInteger strCount = [self length] - [[self stringByReplacingOccurrencesOfString:searchString withString:#""] length];
return strCount / [searchString length];
}
#end
simply call it by:
[text countOccurencesOfString:find];
Optional:
you can modify it to search case insensitive by defining options:
There are a couple ways you could do it. You could iteratively call rangeOfString:options:range:, or you could do something like:
NSArray * portions = [aString componentsSeparatedByString:#"cake"];
NSUInteger cakeCount = [portions count] - 1;
EDIT I was thinking about this question again and I wrote a linear-time algorithm to do the searching (linear to the length of the haystack string):
+ (NSUInteger) numberOfOccurrencesOfString:(NSString *)needle inString:(NSString *)haystack {
const char * rawNeedle = [needle UTF8String];
NSUInteger needleLength = strlen(rawNeedle);
const char * rawHaystack = [haystack UTF8String];
NSUInteger haystackLength = strlen(rawHaystack);
NSUInteger needleCount = 0;
NSUInteger needleIndex = 0;
for (NSUInteger index = 0; index < haystackLength; ++index) {
const char thisCharacter = rawHaystack[index];
if (thisCharacter != rawNeedle[needleIndex]) {
needleIndex = 0; //they don't match; reset the needle index
}
//resetting the needle might be the beginning of another match
if (thisCharacter == rawNeedle[needleIndex]) {
needleIndex++; //char match
if (needleIndex >= needleLength) {
needleCount++; //we completed finding the needle
needleIndex = 0;
}
}
}
return needleCount;
}
A quicker to type, but probably less efficient solution.
- (int)numberOfOccurencesOfSubstring:(NSString *)substring inString:(NSString*)string
{
NSArray *components = [string componentsSeparatedByString:substring];
return components.count-1; // Two substring will create 3 separated strings in the array.
}
Here is a version done as an extension to NSString (same idea as Matthew Flaschen's answer):
#interface NSString (my_substr_search)
- (unsigned) countOccurencesOf: (NSString *)subString;
#end
#implementation NSString (my_substring_search)
- (unsigned) countOccurencesOf: (NSString *)subString {
unsigned count = 0;
unsigned myLength = [self length];
NSRange uncheckedRange = NSMakeRange(0, myLength);
for(;;) {
NSRange foundAtRange = [self rangeOfString:subString
options:0
range:uncheckedRange];
if (foundAtRange.location == NSNotFound) return count;
unsigned newLocation = NSMaxRange(foundAtRange);
uncheckedRange = NSMakeRange(newLocation, myLength-newLocation);
count++;
}
}
#end
<somewhere> {
NSString *haystack = #"Cheesecake, apple cake, and cherry pie";
NSString *needle = #"cake";
unsigned count = [haystack countOccurencesOf: needle];
NSLog(#"found %u time%#", count, count == 1 ? #"" : #"s");
}
If you want to count words, not just substrings, then use CFStringTokenizer.
Here's another version as a category on NSString:
-(NSUInteger) countOccurrencesOfSubstring:(NSString *) substring {
if ([self length] == 0 || [substring length] == 0)
return 0;
NSInteger result = -1;
NSRange range = NSMakeRange(0, 0);
do {
++result;
range = NSMakeRange(range.location + range.length,
self.length - (range.location + range.length));
range = [self rangeOfString:substring options:0 range:range];
} while (range.location != NSNotFound);
return result;
}
Swift solution would be:
var numberOfSubstringAppearance = 0
let length = count(text)
var range: Range? = Range(start: text.startIndex, end: advance(text.startIndex, length))
while range != nil {
range = text.rangeOfString(substring, options: NSStringCompareOptions.allZeros, range: range, locale: nil)
if let rangeUnwrapped = range {
let remainingLength = length - distance(text.startIndex, rangeUnwrapped.endIndex)
range = Range(start: rangeUnwrapped.endIndex, end: advance(rangeUnwrapped.endIndex, remainingLength))
numberOfSubstringAppearance++
}
}
Matthew Flaschen's answer was a good start for me. Here is what I ended up using in the form of a method. I took a slightly different approach to the loop. This has been tested with empty strings passed to stringToCount and text and with the stringToCount occurring as the first and/or last characters in text.
I use this method regularly to count paragraphs in the passed text (ie. stringToCount = #"\r").
Hope this of use to someone.
- (int)countString:(NSString *)stringToCount inText:(NSString *)text{
int foundCount=0;
NSRange range = NSMakeRange(0, text.length);
range = [text rangeOfString:stringToCount options:NSCaseInsensitiveSearch range:range locale:nil];
while (range.location != NSNotFound) {
foundCount++;
range = NSMakeRange(range.location+range.length, text.length-(range.location+range.length));
range = [text rangeOfString:stringToCount options:NSCaseInsensitiveSearch range:range locale:nil];
}
return foundCount;
}
Example call assuming the method is in a class named myHelperClass...
int foundCount = [myHelperClass countString:#"n" inText:#"Now is the time for all good men to come to the aid of their country"];
for(int i =0;i<htmlsource1.length-search.length;i++){
range = NSMakeRange(i,search.length);
checker = [htmlsource1 substringWithRange:range];
if ([search isEqualToString:checker]) {
count++;
}
}
No built-in method. I'd suggest returning a c-string and using a common c-string style algorithm for substring counting... if you really need this to be fast.
If you want to stay in Objective C, this link might help. It describes the basic substring search for NSString. If you work with the ranges, adjust and count, then you'll have a "pure" Objective C solution... albeit, slow.
-(IBAction)search:(id)sender{
int maincount = 0;
for (int i=0; i<[self.txtfmainStr.text length]; i++) {
char c =[self.substr.text characterAtIndex:0];
char cMain =[self.txtfmainStr.text characterAtIndex:i];
if (c == cMain) {
int k=i;
int count=0;
for (int j = 0; j<[self.substr.text length]; j++) {
if (k ==[self.txtfmainStr.text length]) {
break;
}
if ([self.txtfmainStr.text characterAtIndex:k]==[self.substr.text characterAtIndex:j]) {
count++;
}
if (count==[self.substr.text length]) {
maincount++;
}
k++;
}
}
NSLog(#"%d",maincount);
}
}
I'm looking for a simple, efficient way to convert strings in CamelCase to underscore notation (i.e., MyClassName -> my_class_name) and back again in Objective C.
My current solution involves lots of rangeOfString, characterAtIndex, and replaceCharactersInRange operations on NSMutableStrings, and is just plain ugly as hell :) It seems that there must be a better solution, but I'm not sure what it is.
I'd rather not import a regex library just for this one use case, though that is an option if all else fails.
Chris's suggestion of RegexKitLite is good. It's an excellent toolkit, but this could be done pretty easily with NSScanner. Use -scanCharactersFromSet:intoString: alternating between +uppercaseLetterCharacterSet and +lowercaseLetterCharacterSet. For going back, you'd use -scanUpToCharactersFromSet: instead, using a character set with just an underscore in it.
How about these:
NSString *MyCamelCaseToUnderscores(NSString *input) {
NSMutableString *output = [NSMutableString string];
NSCharacterSet *uppercase = [NSCharacterSet uppercaseLetterCharacterSet];
for (NSInteger idx = 0; idx < [input length]; idx += 1) {
unichar c = [input characterAtIndex:idx];
if ([uppercase characterIsMember:c]) {
[output appendFormat:#"_%#", [[NSString stringWithCharacters:&c length:1] lowercaseString]];
} else {
[output appendFormat:#"%C", c];
}
}
return output;
}
NSString *MyUnderscoresToCamelCase(NSString *underscores) {
NSMutableString *output = [NSMutableString string];
BOOL makeNextCharacterUpperCase = NO;
for (NSInteger idx = 0; idx < [underscores length]; idx += 1) {
unichar c = [underscores characterAtIndex:idx];
if (c == '_') {
makeNextCharacterUpperCase = YES;
} else if (makeNextCharacterUpperCase) {
[output appendString:[[NSString stringWithCharacters:&c length:1] uppercaseString]];
makeNextCharacterUpperCase = NO;
} else {
[output appendFormat:#"%C", c];
}
}
return output;
}
Some drawbacks are that they do use temporary strings to convert between upper and lower case, and they don't have any logic for acronyms, so myURL will result in my_u_r_l.
Try this magic:
NSString* camelCaseString = #"myBundleVersion";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])" options:0 error:nil];
NSString *underscoreString = [[regex stringByReplacingMatchesInString:camelCaseString options:0 range:NSMakeRange(0, camelCaseString.length) withTemplate:#"_$1$2"] lowercaseString];
NSLog(#"%#", underscoreString);
Output: my_bundle_version
If your concern is just the visibility of your code, you could make a category for NSString using the methods you've designed already. That way, you only see the ugly mess once. ;)
For instance:
#interface NSString(Conversions) {
- (NSString *)asCamelCase;
- (NSString *)asUnderscored;
}
#implementation NSString(Conversions) {
- (NSString *)asCamelCase {
// whatever you came up with
}
- (NSString *)asUnderscored {
// whatever you came up with
}
}
EDIT: After a quick Google search, I couldn't find any way of doing this, even in plain C. However, I did find a framework that could be useful. It's called RegexKitLite. It uses the built-in ICU library, so it only adds about 20K to the final binary.
Here's my implementation of Rob's answer:
#implementation NSString (CamelCaseConversion)
// Convert a camel case string into a dased word sparated string.
// In case of scanning error, return nil.
// Camel case string must not start with a capital.
- (NSString *)fromCamelCaseToDashed {
NSScanner *scanner = [NSScanner scannerWithString:self];
scanner.caseSensitive = YES;
NSString *builder = [NSString string];
NSString *buffer = nil;
NSUInteger lastScanLocation = 0;
while ([scanner isAtEnd] == NO) {
if ([scanner scanCharactersFromSet:[NSCharacterSet lowercaseLetterCharacterSet] intoString:&buffer]) {
builder = [builder stringByAppendingString:buffer];
if ([scanner scanCharactersFromSet:[NSCharacterSet uppercaseLetterCharacterSet] intoString:&buffer]) {
builder = [builder stringByAppendingString:#"-"];
builder = [builder stringByAppendingString:[buffer lowercaseString]];
}
}
// If the scanner location has not moved, there's a problem somewhere.
if (lastScanLocation == scanner.scanLocation) return nil;
lastScanLocation = scanner.scanLocation;
}
return builder;
}
#end
Here's yet another version based on all the above. This version handles additional forms. In particular, tested with the following:
camelCase => camel_case
camelCaseWord => camel_case_word
camelURL => camel_url
camelURLCase => camel_url_case
CamelCase => camel_case
Here goes
- (NSString *)fromCamelCaseToDashed3 {
NSMutableString *output = [NSMutableString string];
NSCharacterSet *uppercase = [NSCharacterSet uppercaseLetterCharacterSet];
BOOL previousCharacterWasUppercase = FALSE;
BOOL currentCharacterIsUppercase = FALSE;
unichar currentChar = 0;
unichar previousChar = 0;
for (NSInteger idx = 0; idx < [self length]; idx += 1) {
previousChar = currentChar;
currentChar = [self characterAtIndex:idx];
previousCharacterWasUppercase = currentCharacterIsUppercase;
currentCharacterIsUppercase = [uppercase characterIsMember:currentChar];
if (!previousCharacterWasUppercase && currentCharacterIsUppercase && idx > 0) {
// insert an _ between the characters
[output appendString:#"_"];
} else if (previousCharacterWasUppercase && !currentCharacterIsUppercase) {
// insert an _ before the previous character
// insert an _ before the last character in the string
if ([output length] > 1) {
unichar charTwoBack = [output characterAtIndex:[output length]-2];
if (charTwoBack != '_') {
[output insertString:#"_" atIndex:[output length]-1];
}
}
}
// Append the current character lowercase
[output appendString:[[NSString stringWithCharacters:¤tChar length:1] lowercaseString]];
}
return output;
}
If you are concerned with the speed of your code you probably want to write a more performant version of the code:
- (nonnull NSString *)camelCaseToSnakeCaseString {
if ([self length] == 0) {
return #"";
}
NSMutableString *output = [NSMutableString string];
NSCharacterSet *digitSet = [NSCharacterSet decimalDigitCharacterSet];
NSCharacterSet *uppercaseSet = [NSCharacterSet uppercaseLetterCharacterSet];
NSCharacterSet *lowercaseSet = [NSCharacterSet lowercaseLetterCharacterSet];
for (NSInteger idx = 0; idx < [self length]; idx += 1) {
unichar c = [self characterAtIndex:idx];
// if it's the last one then just append lowercase of character
if (idx == [self length] - 1) {
if ([uppercaseSet characterIsMember:c]) {
[output appendFormat:#"%#", [[NSString stringWithCharacters:&c length:1] lowercaseString]];
}
else {
[output appendFormat:#"%C", c];
}
continue;
}
unichar nextC = [self characterAtIndex:(idx+1)];
// this logic finds the boundaries between lowercase/uppercase/digits and lets the string be split accordingly.
if ([lowercaseSet characterIsMember:c] && [uppercaseSet characterIsMember:nextC]) {
[output appendFormat:#"%#_", [[NSString stringWithCharacters:&c length:1] lowercaseString]];
}
else if ([lowercaseSet characterIsMember:c] && [digitSet characterIsMember:nextC]) {
[output appendFormat:#"%#_", [[NSString stringWithCharacters:&c length:1] lowercaseString]];
}
else if ([digitSet characterIsMember:c] && [uppercaseSet characterIsMember:nextC]) {
[output appendFormat:#"%#_", [[NSString stringWithCharacters:&c length:1] lowercaseString]];
}
else {
// Append lowercase of character
if ([uppercaseSet characterIsMember:c]) {
[output appendFormat:#"%#", [[NSString stringWithCharacters:&c length:1] lowercaseString]];
}
else {
[output appendFormat:#"%C", c];
}
}
}
return output;
}
I have combined the answers found here into my refactoring library, es_ios_utils. See NSCategories.h:
#property(nonatomic, readonly) NSString *asCamelCaseFromUnderscores;
#property(nonatomic, readonly) NSString *asUnderscoresFromCamelCase;
Usage:
#"my_string".asCamelCaseFromUnderscores
yields #"myString"
Please push improvements!
I happened upon this question looking for a way to convert Camel Case to a spaced, user displayable string. Here is my solution which worked better than replacing #"_" with #" "
- (NSString *)fromCamelCaseToSpaced:(NSString*)input {
NSCharacterSet* lower = [NSCharacterSet lowercaseLetterCharacterSet];
NSCharacterSet* upper = [NSCharacterSet uppercaseLetterCharacterSet];
for (int i = 1; i < input.length; i++) {
if ([upper characterIsMember:[input characterAtIndex:i]] &&
[lower characterIsMember:[input characterAtIndex:i-1]])
{
NSString* soFar = [input substringToIndex:i];
NSString* left = [input substringFromIndex:i];
return [NSString stringWithFormat:#"%# %#", soFar, [self fromCamelCaseToSpaced:left]];
}
}
return input;
}
OK guys. Here is an all regex answer, which I consider the only true way:
Given:
NSString *MYSTRING = "foo_bar";
NSRegularExpression *_toCamelCase = [NSRegularExpression
regularExpressionWithPattern:#"(_)([a-z])"
options:NSRegularExpressionCaseInsensitive error:&error];
NSString *camelCaseAttribute = [_toCamelCase
stringByReplacingMatchesInString:MYSTRING options:0
range:NSMakeRange(0, attribute.length)
withTemplate:#"\\U$2"];
Yields fooBar.
Conversely:
NSString *MYSTRING = "fooBar";
NSRegularExpression *camelCaseTo_ = [NSRegularExpression
regularExpressionWithPattern:#"([A-Z])"
options:0 error:&error];
NSString *underscoreParsedAttribute = [camelCaseTo_
stringByReplacingMatchesInString:MYSTRING
options:0 range:NSMakeRange(0, attribute.length)
withTemplate:#"_$1"];
underscoreParsedAttribute = [underscoreParsedAttribute lowercaseString];
Yields: foo_bar.
\U$2 replaces second capture group with upper-case version of itself :D
\L$1 however, oddly, does not replace the first capture group with a lower-case version of itself :( Not sure why, it should work. :/