How to count words within a text string? - ios

On iOS, how can I count words within a specific text string?

A more efficient method than splitting is to check the string character by character.
int word_count(NSString* s) {
CFCharacterSetRef alpha = CFCharacterSetGetPredefined(kCFCharacterSetAlphaNumeric);
CFStringInlineBuffer buf;
CFIndex len = CFStringGetLength((CFStringRef)s);
CFStringInitInlineBuffer((CFStringRef)s, &buf, CFRangeMake(0, len));
UniChar c;
CFIndex i = 0;
int word_count = 0;
Boolean was_alpha = false, is_alpha;
while (c = CFStringGetCharacterFromInlineBuffer(&buf, i++)) {
is_alpha = CFCharacterSetIsCharacterMember(alpha, c);
if (!is_alpha && was_alpha)
++ word_count;
was_alpha = is_alpha;
}
if (is_alpha)
++ word_count;
return word_count;
}
Compared with #ennuikiller's solution, counting a 1,000,000-word string takes:
0.19 seconds to build the string
0.39 seconds to build the string + counting using my method.
1.34 seconds to build the string + counting using ennuikiller's method.
The big disadvantage of my method is that it's not a one-liner.

[[stringToCOunt componentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceCharacterSet] count]

I think this method is better:
__block int wordCount = 0;
NSRange range = {0,self.text.length };
[self.text enumerateSubstringsInRange:range options:NSStringEnumerationByWords usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
wordCount++;
}];
As a reference check the video of the session 215 of the WWDC 2012: Text and Linguistic Analysis by Douglas Davidson

One liner accurate solution:
return [[self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]].count;
This solution handles consecutive spaces correctly.

Related

Converting very large NSDecimal to string eg. 400,000,000,000 -> 400 T and so forth

I am making a game that requires me to use very large numbers. I believe I am able to store very large numbers with NSDecimal. However, when displaying the numbers to users I would like to be able to convert the large number to a succinct string that uses characters to signify the value eg. 100,000 -> 100k 1,000,000 -> 1.00M 4,200,000,000 -> 4.20B and so forth going up to extremely large numbers. Is there any built in method for doing so or would I have to use a bunch of
NSDecimalCompare statements to determine the size of the number and convert?
I am hoping to use objective c for the application.
I know that I can use NSString *string = NSDecimalString(&NSDecimal, _usLocale); to convert to a string could I then do some type of comparison on this string to get the result I'm looking for?
Use this method to convert your number into a smaller format just as you need:
-(NSString*) suffixNumber:(NSNumber*)number
{
if (!number)
return #"";
long long num = [number longLongValue];
int s = ( (num < 0) ? -1 : (num > 0) ? 1 : 0 );
NSString* sign = (s == -1 ? #"-" : #"" );
num = llabs(num);
if (num < 1000)
return [NSString stringWithFormat:#"%#%lld",sign,num];
int exp = (int) (log(num) / 3.f); //log(1000));
NSArray* units = #[#"K",#"M",#"G",#"T",#"P",#"E"];
return [NSString stringWithFormat:#"%#%.1f%#",sign, (num / pow(1000, exp)), [units objectAtIndex:(exp-1)]];
}
Some sample examples:
NSLog(#"%#",[self suffixNumber:#99999]); // 100.0K
NSLog(#"%#",[self suffixNumber:#5109999]); // 5.1M
Source
Solved my issue: Can only be used if you know that your NSDecimal that you are trying to format will only be a whole number without decimals so make sure you round when doing any math on the NSDecimals.
-(NSString *)returnFormattedString:(NSDecimal)nsDecimalToFormat{
NSMutableArray *formatArray = [NSMutableArray arrayWithObjects:#"%.2f",#"%.1f",#"%.0f",nil];
NSMutableArray *suffixes = [NSMutableArray arrayWithObjects:#"k",#"M",#"B",#"T",#"Qa",#"Qi",#"Sx",#"Sp",#"Oc",#"No",#"De",#"Ud",#"Dud",#"Tde",#"Qde",#"Qid",#"Sxd",#"Spd",#"Ocd",#"Nvd",#"Vi",#"Uvi",#"Dvi",#"Tvi", nil];
int dick = [suffixes count];
NSLog(#"count %i",dick);
NSString *string = NSDecimalString(&nsDecimalToFormat, _usLocale);
NSString *formatedString;
NSUInteger characterCount = [string length];
if (characterCount > 3) {
NSString *trimmedString=[string substringToIndex:3];
float a;
a = 100.00/(pow(10, (characterCount - 4)%3));
int remainder = (characterCount-4)%3;
int suffixIndex = (characterCount + 3 - 1)/3 - 2;
NSLog(#"%i",suffixIndex);
if(suffixIndex < [suffixes count]){
NSString *formatSpecifier = [formatArray[remainder] stringByAppendingString:suffixes[suffixIndex]];
formatedString= [NSString stringWithFormat:formatSpecifier, [trimmedString floatValue] / a];
}
else {
formatedString = #"too Big";
}
}
else{
formatedString = string;
}
return formatedString;
}

How to read binary data stored in NSData

I am getting data from a web call. Here is data.
00000001 00045043 4c4e0000 00023744 92f4cd44 92f4cd44 92f4cd44 92f4cd3c a3d70a00 00014de2 b767a044 93800044 93800044 93800044 93800040 a0000000 00014de2 bdd04044 94400044 94400044 93db3344 94170a3f a6666600 00014de2 c8ccc044 93ddc344 93ddc344 93ddc344 93ddc33e 99999a00 00014de2 ce4b0044 93bd1f44 943d7144 9394cd44 93eccd41 e8e14800 00014de2 d01fc044 93eb3344 93eb3344 93b00044 93b00040 c0000000
The data format is fixed (We know where it stores int / float etc.
Question is how do I read and store it in individual variables
For example the first 4 bytes here are specifying the number of symbols returned (1)
NSMutableString *stringHexForm = [NSMutableString string];
for(int i = startLocation ; i < (startLocation + nCharsToReadIn) ; i++)
{
unsigned char byte;
[dataIn getBytes : &byte range : NSMakeRange(i, 1)];
NSString *tempStr=[NSString stringWithFormat:#"%02x",byte];
[stringHexForm appendString:[NSString stringWithFormat:#"%#",tempStr]];
}
unsigned result = 0;
NSScanner *scanner = [NSScanner stringHexForm];
[scanner scanHexInt:&result];
return result;
NSData has the method func getBytes(buffer: UnsafeMutablePointer<Void>, range: NSRange) that should help you. You can grab each thing in your struct by specifying the range.
So you can do:
var length: Int = 0
myData.getBytes(&length, range: NSMakeRange(0, 4))
and so on for each type, range that you need

Truncate delimited NSString without removing delimiters

I have some data in an NSString, separated by colons:
#"John:Doe:1970:Male:Dodge:Durango"
I need to limit the total length of this string to 100 characters. But I also need to ensure the correct number of colons are present.
What would be a reasonable to way to truncate the string but also add the extra colons so I can parse it into the correct number of fields on the other side?
For example, if my limit was 18, you would end up with something like this:
#"John:Doe:1970:Ma::"
Here's an updated version of my own latest pass at this. Uses #blinkenlights algorithm:
+ (NSUInteger)occurrencesOfSubstring:(NSString *)substring inString:(NSString *)string {
// http://stackoverflow.com/a/5310084/878969
return [string length] - [[string stringByReplacingOccurrencesOfString:substring withString:#""] length] / [substring length];
}
+ (NSString *)truncateString:(NSString *)string toLength:(NSUInteger)length butKeepDelmiter:(NSString *)delimiter {
if (string.length <= length)
return string;
NSAssert(delimiter.length == 1, #"Expected delimiter to be a string containing a single character");
int numDelimitersInOriginal = [[self class] occurrencesOfSubstring:delimiter inString:string];
NSMutableString *truncatedString = [[string substringToIndex:length] mutableCopy];
int numDelimitersInTruncated = [[self class] occurrencesOfSubstring:delimiter inString:truncatedString];
int numDelimitersToAdd = numDelimitersInOriginal - numDelimitersInTruncated;
int index = length - 1;
while (numDelimitersToAdd > 0) { // edge case not handled here
NSRange nextRange = NSMakeRange(index, 1);
index -= 1;
NSString *rangeSubstring = [truncatedString substringWithRange:nextRange];
if ([rangeSubstring isEqualToString:delimiter])
continue;
[truncatedString replaceCharactersInRange:nextRange withString:delimiter];
numDelimitersToAdd -= 1;
}
return truncatedString;
}
Note that I don't think this solution handles the edge case from CRD where the number of delimiters is less than the limit.
The reason I need the correct number of colons is the code on the server will split on colon and expect to get 5 strings back.
You can assume the components of the colon separated string do not themselves contain colons.
Your current algorithm will not produce the correct result when one or more of the characters among the last colonsToAdd is a colon.
You can use this approach instead:
Cut the string at 100 characters, and store the characters in an NSMutableString
Count the number of colons, and subtract that number from the number that you need
Starting at the back of the string, replace non-colon characters with colons until you have the right number of colons.
I tend towards #dasblinkenlight, it's just an algorithm after all, but here's some code. Few modern shorthands - used an old compiler. ARC assumed. Won't claim it's efficient, or beautiful, but it does work and handles edge cases (repeated colons, too many fields for limit):
- (NSString *)abbreviate:(NSString *)input limit:(NSUInteger)limit
{
NSMutableArray *fields = [[input componentsSeparatedByString:#":"] mutableCopy];
NSUInteger colonCount = fields.count - 1;
if (colonCount >= limit)
return [#"" stringByPaddingToLength:limit withString:#":" startingAtIndex:0];
NSUInteger nonColonsRemaining = limit - colonCount;
for (NSUInteger ix = 0; ix <= colonCount; ix++)
{
if (nonColonsRemaining > 0)
{
NSString *fieldValue = [fields objectAtIndex:ix];
NSUInteger fieldLength = fieldValue.length;
if (fieldLength <= nonColonsRemaining)
nonColonsRemaining -= fieldLength;
else
{
[fields replaceObjectAtIndex:ix withObject:[fieldValue substringToIndex:nonColonsRemaining]];
nonColonsRemaining = 0;
}
}
else
[fields replaceObjectAtIndex:ix withObject:#""];
}
return [fields componentsJoinedByString:#":"];
}

Chinese character to ASCII or Hexadecimal

Im struggling to covert chinese word/characters to ascii or hexadecimal and all the values I've got up until now is not what I was suppose to get.
Example of conversion is the word 手 to hex is 1534b.
Methods Ive followed till now are as below, and I got varieties of results but the one I was looking for,
I really appreciate if you can help me out on this issue,
Thanks,
Mike
- (NSString *) stringToHex:(NSString *)str{
NSUInteger len = [str length];
unichar *chars = malloc(len * sizeof(unichar));
[str getCharacters:chars];
NSMutableString *hexString = [[NSMutableString alloc] init];
for(NSUInteger i = 0; i < len; i++ )
{
[hexString appendFormat:#"%02x", chars[i]]; //EDITED PER COMMENT BELOW
}
free(chars);
return hexString;}
and
const char *cString = [#"手" cStringUsingEncoding:NSASCIIStringEncoding];
below is the similar code in Java for Android, Maybe it helps
public boolean sendText(INotifiableManager manager, String text) {
final int codeOffset = 0xf100;
for (char c : text.toCharArray()) {
int code = (int)c+codeOffset;
if (! mConnection.getBoolean(manager, "SendKey", Integer.toString(code))) {
}
Your Java code is just doing this:
Take each 16-bit character of the string and add 0xf100 to it.
If you do the same thing in your above Objective-C code you will get the result you want.

How do I compare characters in custom sqlite collation in objective-c?

I went through lots of questions here on SO (like this one) but I still need some assistance.
I need my sqlite select to order by slovenian alphabet (letter č comes after c, letter š after s and letter ž after z).
Here is the code I use:
static int sqlite3SloCollate(void * foo, int ll, const void *l, int rl,
const void *r){
NSString *left = [NSString stringWithCharacters:l length:ll];
NSString *right = [NSString stringWithCharacters:r length:rl];
//THIS IS WHERE I DON'T KNOW HOW TO COMPARE CHARACTERS
NSComparisonResult rs = [left compare:right options:NSForcedOrderingSearch];
return rs;
}
sqlite3_create_collation(database, "SLOCOLLATE", SQLITE_UTF8, NULL, &sqlite3SloCollate);
querySQL = [NSString stringWithFormat: #"SELECT s.id FROM lyrics l INNER JOIN song s ON (l.idSong=s.id) WHERE content LIKE '%%%#%%' GROUP BY s.id ORDER BY s.title COLLATE SLOCOLLATE;",searchString];
Which NSOrdering type should I use? Or do I have to write my own compare function (can you give me an example)?
I think that this function might help you :
- (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)range locale:(id)locale
(From Apple documentation).
You can create a locale using :
- (id)initWithLocaleIdentifier:(NSString *)string
(From Apple NSLocale Class Documentation).
This code should do the trick :
NSRange range = NSMakeRange(0, [left length]);
id locale = [[NSLocale alloc] initWithLocaleIdentifier:#"sl_SI"];
NSComparisonResult rs = [left compare:right options:NSForcedOrderingSearch range:range locale:locale];
I hope this will help.
The #DCMaxxx answer has most of it. Plus the comment that you need to use stringWithUTF8String. But there's some more issues.
1) stringWithUTF8String uses null-terminated c-strings, whilst sqlite is suppling strings with just a length and no null termination.
2) For the number of characters to compare, we need to take the shortest length, not just the left length.
3) When the comparison is equal for the compare, we then need to consider which string is longer.
Full code here. I use an NSMutableData object to convert length coded strings to null terminated strings. It's probably quicker and easier to do it with straight c code, if you are that way inclined.
static int sqlite3SloCollate(void * foo, int ll, const void *l, int rl,
const void *r){
NSMutableData* ld = [NSMutableData dataWithBytes:l length:ll+1];
[ld resetBytesInRange:NSMakeRange(ll, 1)];
NSString *left = [NSString stringWithUTF8String:[ld bytes]];
NSMutableData* rd = [NSMutableData dataWithBytes:r length:rl+1];
[rd resetBytesInRange:NSMakeRange(rl, 1)];
NSString *right = [NSString stringWithUTF8String:[rd bytes]];
NSRange range = NSMakeRange(0, MIN([left length],[right length]));
id locale = [[NSLocale alloc] initWithLocaleIdentifier:#"sl_SI"];
NSComparisonResult result = [left compare:right options:0 range:range locale:locale];
if (result==NSOrderedSame) {
if (ll>rl) {
result = NSOrderedDescending;
} else if (ll<rl) {
result = NSOrderedAscending;
}
}
// NSLog(#"Comparison:%# - %# - %li",left,right,(long)result);
return result;
}

Resources