Simplest way to concatenate objective-C strings and achieve grammatical correctness (with 'and' conjunction) - objective-c

What is the easiest and best way in Objective-C to combine a list (NSArray) of NSStrings into a single NSString separated by commas, with the grammatically correct terminal conjunction ", and " before the final item of the list?
NSArray *anArray = [NSArray arrayWithObjects:#"milk", #"butter", #"eggs", #"spam", nil];
From this array, I want the NSString #"milk, butter, eggs, and spam".
More generally, if the list is more than two items long, I want ", " between every item except the last and second-to-last (which should have ", and "). If the list is two items long, I want just the ' and ' with no comma. If the list is one item long, I want the single string from the array.
I like something as simple as:
NSString *newString = [anArray componentsJoinedByString:#", "];
But this of course omits the 'and' conjunction.
Is there a simpler and/or faster Objective-C way than the following:
- (NSString *)grammaticallyCorrectStringFromArrayOfStrings:(NSArray *)anArray {
if (anArray == nil) return nil;
int arrayCount = [anArray count];
if (arrayCount == 0) return #"";
if (arrayCount == 1) return [anArray objectAtIndex:0];
if (arrayCount == 2) return [anArray componentsJoinedByString:#" and "];
// arrayCount > 2
NSString *newString = #"";
for (NSString *thisString in anArray) {
if (thisString != [anArray objectAtIndex:0] && thisString != [anArray lastObject]) {
newString = [newString stringByAppendingString:#", "];
}
else if (thisString == [anArray lastObject]) {
newString = [newString stringByAppendingString:#", and "];
}
newString = [newString stringByAppendingString:thisString];
}
return newString;
}

For the loop, I'd probably do something like
NSMutableString *newString = [NSMutableString string];
NSUInteger lastIndex = arrayCount - 1;
[anArray enumerateObjectsUsingBlock:^(NSString *thisString, NSUInteger idx, BOOL *stop) {
if (idx != 0)
[newString appendString:#","];
if (idx == lastIndex)
[newString appendString:#" and "];
[newString appendString:thisString];
}];
Though I guess that's not really less lines.

Related

Objective-C: How to find the most common string in an array?

I have an array of strings from an online database that I trying to determine the most commonly used word. The values inside the arrays will vary but I want to check the most common words of whatever collection or words I'm using. If theoretically I had an array of the following...
NSArray *stringArray = [NSArray arrayWithObjects:#"Duck", #"Duck", #"Duck", #"Duck", #"Goose"];
How do I iterate through this array to determine the most common string, which would obviously be "Duck"?
Simplest way is probably NSCountedSet:
NSCountedSet* stringSet = [[NSCountedSet alloc] initWithArray:strings];
NSString* mostCommon = nil;
NSUInteger highestCount = 0;
for(NSString* string in stringSet) {
NSUInteger count = [stringSet countForObject:string];
if(count > highestCount) {
highestCount = count;
mostCommon = string;
}
}
You can use the word as a key into a dictionary.
NSMutableDictionary *words = [NSMutableDictionary dictionary];
for (NSString *word in stringArray) {
if (!words[word]) {
[words setValue:[NSDecimalNumber zero] forKey:word];
}
words[word] = [words[word] decimalNumberByAdding:[NSDecimalNumber one]];
}
Now iterate through words and find the key with the highest value.
NSString *mostCommon;
NSDecimalNumber *curMax = [NSDecimalNumber zero];
for (NSString *key in [words allKeys]) {
if ([words[key] compare:curMax] == NSOrderedDescending) {
mostCommon = key;
curMax = word[key];
}
}
NSLog(#"Most Common Word: %#", mostCommon);
EDIT: Rather than looping through the array once then looping separately through the sorted dictionary, I think we can do better and do it all in a single loop.
NSString *mostCommon;
NSDecimalNumber *curMax = [NSDecimalNumber zero];
NSMutableDictionary *words = [NSMutableDictionary dictionary];
for (NSString *word in stringArray) {
if (!words[word]) {
[words setValue:[NSDecimalNumber zero] forKey:word];
}
words[word] = [words[word] decimalNumberByAdding:[NSDecimalNumber one]];
if ([words[word] compare:curMax] == NSOrderedDescending) {
mostCommon = word;
curMax = words[word];
}
}
NSLog(#"Most Common Word: %#", mostCommon);
This should be significantly faster than my answer pre-edit, though I don't know how it compares to using the NSCountedSet answer.
Try using NSPredicate.
NSUInteger count=0;
NSString *mostCommonStr;
for(NSString *strValue in stringArray) {
NSUInteger countStr=[[stringArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"self MATCHES[CD] %#, strValue]]count];
if(countStr > count) {
count=countStr;
mostCommonStr=strValue;
}
}
NSLog(#"The most commonstr is %#",mostCommonStr);

Spliting string to array by constant number

I'v been trying to split string to array of components by number, but have no idea how to do it. I know that each components lenght is 9 except the last one. But there is no separation between them. Maybe anyone would know how could i make this split possible?
string : E44000000R33000444V33441
And i'd like to get array with: E44000000 R33000444 V33441
in past I'v used this method, but i guess there should be a way to separate by constant number. Any ideas
NSArray *myWords = [message componentsSeparatedByString:#";"];
Please try the below code.
NSString *stringTest = #"E44000000R33000444V33441323";
NSMutableArray *arrayTest = [NSMutableArray array];
while([stringTest length] > 8) {
[arrayTest addObject:[NSString stringWithString:[stringTest substringToIndex:9]]];
stringTest = [stringTest substringFromIndex:9];
}
NSLog(#"arrayTest - %#", arrayTest);
Try this one..
NSString *mainString=#"E44000000R33000444V";
NSMutableArray *brokenString=[NSMutableArray new];
int start=0;
for (; start<mainString.length-9; start+=9) {
[brokenString addObject:[mainString substringWithRange:NSMakeRange(start, 9)]];
}
[brokenString addObject:[mainString substringFromIndex:start]];
NSLog(#"->%#",brokenString);
Output is :
->(
E44000000,
R33000444,
V
)
I investigated the NSString, and i didn't found any function like that. But you can create a category of NSString and put this function in that category and you can use as a NSString instance method.
- (NSArray *) componentSaparetedByLength:(NSUInteger) length{
NSMutableArray *array = [NSMutableArray new];
NSRange range = NSMakeRange(0, length);
NSString *subString = nil;
while (range.location + range.length <= self.length) {
subString = [self substringWithRange:range];
[array addObject:subString];
//Edit
range.location = range.length + range.location;
//Edit
range.length = length;
}
if(range.location<self.length){
subString = [self substringFromIndex:range.location];
[array addObject:subString];
}
return array;
}
You can get the substring upto the characters which you want in a loop(string length) & pass the next index for getting the next substring. After getting each substring you can add it to the array.
Used SubstringToIndex & SubstringFromIndex functions to get the substring.
Also not an requirement here, I want to propose a solution that is capable of handling characters from more sophisticated script systems, like surrogate pairs, base characters plus combining marks, Hangul jamo, and Indic consonant clusters.
#interface NSString (Split)
-(NSArray *)arrayBySplittingWithMaximumSize:(NSUInteger)size
options:(NSStringEnumerationOptions) option;
#end
#implementation NSString (Split)
-(NSArray *)arrayBySplittingWithMaximumSize:(NSUInteger)size
options:(NSStringEnumerationOptions) option
{
NSMutableArray *letterArray = [NSMutableArray array];
[self enumerateSubstringsInRange:NSMakeRange(0, [self length])
options:(option)
usingBlock:^(NSString *substring,
NSRange substringRange,
NSRange enclosingRange,
BOOL *stop) {
[letterArray addObject:substring];
}];
NSMutableArray *array = [NSMutableArray array];
[letterArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (idx%size == 0) {
[array addObject: [NSMutableString stringWithCapacity:size]];
}
NSMutableString *string = [array objectAtIndex:[array count]-1];
[string appendString:obj];
}];
return array;
}
#end
usage
NSArray *array = [#"E44000000R33000444V33441" arraysBySplittingWithMaximumSize:9
options:NSStringEnumerationByComposedCharacterSequences];
results in:
(
E44000000,
R33000444,
V33441
)

Check for substring in string with NSString from NSArray?

So pretty much I want to check if my NSString from my NSArray is a substring of my string named imageName.
So lets say this:
My Image name is: picture5of-batman.png
My Array contains strings and one of them is: Batman
So pretty much I want to eliminate the: picture5of- part of the image name and replace it with the NSString from the NSArray.
This is how I try to do it but it never makes it to the if statement. And no my Array is not nil either. Here is the code:
for (NSString *string in superheroArray) {
if ([string rangeOfString:imageName].location != NSNotFound) {
//Ok so some string in superheroArray is equal to the file name of the image
imageName = [imageName stringByReplacingOccurrencesOfString:#"" withString:string
options:NSCaseInsensitiveSearch range:NSMakeRange(0, string.length)];
}
}
Edit1: This still does not work
for (NSString *string in superheroArray) {
if ([imageName rangeOfString:string options:NSCaseInsensitiveSearch].location != NSNotFound) {
//Ok so some string in superheroArray is equal to the file name of the image
imageName = string;
//HOW ABOUT THAT FOR EFFICIENCY :P
}
}
[imageName rangeOfString:string options: NSCaseInsensitiveSearch]
I don't see why it's not working in your code, maybe split the NSString stuff from the NSRage test.
but this work here :
NSArray *ar = [NSArray arrayWithObjects:#"Batman", #"Maurice", nil];
__block NSString *imageName = #"picture5of-batman.png";
__block NSUInteger theIndex = -1;
[ar enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSRange r = [imageName rangeOfString: obj
options: NSCaseInsensitiveSearch];
if (r.location != NSNotFound)
{
theIndex = idx;
NSString *str = [imageName pathExtension];
imageName = [(NSString *)obj stringByAppendingPathExtension:str];
// you found it, so you can stop now
*stop = YES;
}
}];
if (theIndex != -1)
{
NSLog(#"The index is : %d and new imageName == %#", theIndex, imageName);
}
And here is the NSLog statement :
2011-12-10 23:04:28.967 testSwitch1[2493:207] The index is : 0 and new imageName == Batman.png

How to get values after "\n" character?

I want to take all values after a new line character \n from my string. How can I get those values?
Try this:
NSString *substring = nil;
NSRange newlineRange = [yourString rangeOfString:#"\n"];
if(newlineRange.location != NSNotFound) {
substring = [yourString substringFromIndex:newlineRange.location];
}
Take a look at method componentsSeparatedByString here.
A quick example taken from reference:
NSString *list = #"Norman, Stanley, Fletcher";
NSArray *listItems = [list componentsSeparatedByString:#", "];
this will produce a NSArray with strings separated: { #"Norman", #"Stanley", #"Fletcher" }
Here is similar function which splits the string by delimeter and return array with two trimmed values.
NSArray* splitStrByDelimAndTrim(NSString *string, NSString *delim)
{
NSRange range = [string rangeOfString: delim];
NSString *first;
NSString *second;
if(range.location == NSNotFound)
{
first = #"";
second = string;
}
else
{
first = [string substringToIndex: range.location];
first = [first stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
second = [string substringFromIndex: range.location + 1];
second = [second stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
}
return [NSArray arrayWithObjects: first, second, nil];
}

How do I convert an NSString from CamelCase to TitleCase, 'playerName' into 'Player Name'?

I'm looking for the easiest way to convert a string from camelback format to Title Case format.
How do I change 'playerName' into 'Player Name'?
NSString *str = #"playerName";
NSMutableString *str2 = [NSMutableString string];
for (NSInteger i=0; i<str.length; i++){
NSString *ch = [str substringWithRange:NSMakeRange(i, 1)];
if ([ch rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location != NSNotFound) {
[str2 appendString:#" "];
}
[str2 appendString:ch];
}
NSLog(#"%#", str2.capitalizedString);
Here's a simpler Swift version. I've chucked it into an extension
extension String {
func stringFromCamelCase() -> String {
var string = self
string = string.stringByReplacingOccurrencesOfString("([a-z])([A-Z])", withString: "$1 $2", options: NSStringCompareOptions.RegularExpressionSearch, range: Range<String.Index>(start: string.startIndex, end: string.endIndex))
string.replaceRange(startIndex...startIndex, with: String(self[startIndex]).capitalizedString)
return string
}
}
Usage:
var str = "helloWorld"
str = str.stringFromCamelCase()
Try using a regex replace
NSString *modified = [input stringByReplacingOccurrencesOfString:#"([a-z])([A-Z])"
withString:#"$1 $2"
options:NSRegularExpressionSearch
range:NSMakeRange(0, input.length)];
A little shorter, using NSCharacterSet:
__block NSString *str = #"myVerySpecialPlayerName" ;
// split at uppercase letters
NSArray *splitString = [str componentsSeparatedByCharactersInSet:
[NSCharacterSet uppercaseLetterCharacterSet]] ;
// get the uppercase letters
NSArray *upperCaseLetters = [str componentsSeparatedByCharactersInSet:
[[NSCharacterSet uppercaseLetterCharacterSet] invertedSet]] ;
// join with two spaces
str = [splitString componentsJoinedByString:#" "] ;
__block NSInteger offset = 0 ;
// replace each second space with the missing uppercase letter
[upperCaseLetters enumerateObjectsUsingBlock:^(NSString *character, NSUInteger idx, BOOL *stop) {
if( [character length] > 0 ) {
str = [str stringByReplacingCharactersInRange:NSMakeRange(idx+offset+1, 1) withString:character] ;
offset += 2 ;
}
}] ;
// & capitalize the first one
str = [str capitalizedString] ;
NSLog(#"%#", str) ; // "My Very Special Player Name"
Trying to be more unicode compliant
extension String {
func camelCaseToTitleCase() -> String {
return unicodeScalars.map(replaceCaptialsWithSpacePlusCapital).joined().capitalized
}
private func replaceCaptialsWithSpacePlusCapital(unichar: UnicodeScalar) -> String {
if CharacterSet.uppercaseLetters.contains(unichar) {
return " \(unichar)"
}
return "\(unichar)"
}
}
I think you can tackle this problem with some Regular Expressions. Check out this similar question: iPhone dev: Replace uppercase characters in NSString with space and downcase
Although a little long, but this category for NSString should do the trick. It passed all my tests:
- (NSString *)splitOnCapital
{
// Make a index of uppercase characters
NSRange upcaseRange = NSMakeRange('A', 26);
NSIndexSet *upcaseSet = [NSIndexSet indexSetWithIndexesInRange:upcaseRange];
// Split our camecase word
NSMutableString *result = [NSMutableString string];
NSMutableString *oneWord = [NSMutableString string];
for (int i = 0; i < self.length; i++) {
char oneChar = [self characterAtIndex:i];
if ([upcaseSet containsIndex:oneChar]) {
// Found a uppercase char, now save previous word
if (result.length == 0) {
// First word, no space in beginning
[result appendFormat:#"%#", [oneWord capitalizedString]];
}else {
[result appendFormat:#" %#", oneWord];
}
// Clear previous word for new word
oneWord = [NSMutableString string];
}
[oneWord appendFormat:#"%c", oneChar];
}
// Add last word
if (oneWord.length > 0) {
[result appendFormat:#" %#", oneWord];
}
return result;
}
I had a similar issue, the answers here helped me create a solution. I had an array that had a list of labels I wanted to display within a UITableView, one label per row.
My issue was I parsed these labels out of an XML returned by a SOAP action and I had not idea over the format of the strings.
Firstly I implemented webstersx answer into a method. This was great but some of these labels began with a capital letter and some where camel case (e.g. some strings where exampleLabel and others where ExampleLabel. So this meant the ones beginning with a capital had a space inserted in front of the string.
I overcame this by trimming whitespaces from the beggining and end of the string using NSString's stringByTrimmingCharactersInSet.
The next issue was any abbreviations used, such as "ID" or "PNR Status", where being displayed as "I D" and "P N R Status" as the capital letters where, and quite rightly, being picked up and a space inserted before it.
I overcame this issue by implementing a regex similar to emdog4's answer into my new method.
Here is my completed solution:
- (NSString *)formatLabel:(NSString *)label
{
NSMutableString *str2 = [NSMutableString string];
for (NSInteger i=0; i<label.length; i++){
NSString *ch = [label substringWithRange:NSMakeRange(i, 1)];
if ([ch rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location != NSNotFound) {
[str2 appendString:#" "];
}
[str2 appendString:ch];
}
NSString * formattedString = [str2 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].capitalizedString;
formattedString = [formattedString stringByReplacingOccurrencesOfString:#"([A-Z]) (?![A-Z][a-z])" withString:#"$1" options:NSRegularExpressionSearch range:NSMakeRange(0, formattedString.length)];
return formattedString;
}
I then simply call something like this, for example, that will return my nicely formatted string:
NSString * formattedLabel = [self formatLabel:#"PNRStatus"];
NSLog(#"Formatted Label: %#", formattedLabel);
Will output:
2013-10-10 10:44:39.888 Test Project[28296:a0b] Formatted Label: PNR Status
If anyone needs a Swift version:
func camelCaseToTitleCase(s: NSString) -> String {
var newString = ""
if s.length > 0 {
newString = s.substringToIndex(1).uppercaseString
for i in 1..<s.length {
let char = s.characterAtIndex(i)
if NSCharacterSet.uppercaseLetterCharacterSet().characterIsMember(char) {
newString += " "
}
newString += s.substringWithRange(NSRange(location: i, length: 1))
}
}
return newString
}
while technically shorter, more ineffecient
NSString *challengeString = #"playerName";
NSMutableString *rStr = [NSMutableString stringWithString:challengeString];
while ([rStr rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]].location != NSNotFound) {
[rStr replaceCharactersInRange:[rStr rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]] withString:[[NSString stringWithFormat:#" %#", [rStr substringWithRange:[rStr rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]]]] lowercaseString]];
}
NSLog(#"%#", rStr.capitalizedString);
Not sure this is much shorter than websterx, but I find using characterIsMember easier to read and understand. Also added a length check to fix the space before if the string starts with a capital.
NSString *str = #"PlayerNameHowAboutALongerString";
NSMutableString *str2 = [NSMutableString string];
for (NSInteger i=0; i<str.length; i++){
unichar ch = [str characterAtIndex:i];
if ( [[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:ch]) {
if (str2.length > 0 ) {
[str2 appendString:#" "];
}
}
[str2 appendString:[NSString stringWithCharacters:&ch length:1]];
}
NSLog(#"--%#--", str2.capitalizedString);
The accepted answer didn't work for me because it doesn't capitalize the first letter, and if the first letter is already capitalized, it adds an extraneous space at the beginning. Here is my improved version:
- (NSString *)titleFromCamelCaseString:(NSString *)input
{
NSMutableString *output = [NSMutableString string];
[output appendString:[[input substringToIndex:1] uppercaseString]];
for (NSUInteger i = 1; i < [input length]; i++)
{
unichar character = [input characterAtIndex:i];
if ([[NSCharacterSet uppercaseLetterCharacterSet] characterIsMember:character])
{
[output appendString:#" "];
}
[output appendFormat:#"%C", character];
}
return output;
}
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)")
}
NSString *input = #"playerName";
NSString *modified = [input stringByReplacingOccurrencesOfString:#"(?<!^)[A-Z]" withString:#" $0" options:NSRegularExpressionSearch range:NSMakeRange(0, input.length)].capitalizedString;
Another solution under Swift 2.2
extension String {
var stringFromCamelCase:String {
return (self as NSString).replacingOccurrences(
of: "([a-z])([A-Z])",
with: "$1 $2",
options: CompareOptions.regularExpressionSearch,
range: NSMakeRange(0, self.characters.count)
).uppercaseFirst
}
var uppercaseFirst: String {
return String(characters.prefix(1)).uppercased() + String(characters.dropFirst()).lowercased()
}
}
try using:
string.Split()
then use the cap letter as token