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

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;
}

Related

How to add more strings to the combined string if the length is less than prescribed length?

In iOS I am using two strings to combine and to form a single string. But the combined string must be of 16 characters . So if two strings are small and if we combine both and if it is less than 16 characters I must add few more characters to make it to 16 characters. How to achieve this?
NSString *combined = [NSString stringWithFormat:#"%#%#", stringURL, stringSearch];
This is the code I am using. So if I combine and it is less than 16 characters how to calculate it and add more characters to make it 16 characters?
Something like below
NSString *combined = [NSString stringWithFormat:#"%#%#", stringURL, stringSearch];
if (combined.length < 16)
{
NSString *newCombined = [NSString stringWithFormat:#"%#%#", combined, #"Some new string"];
}
You can use substringWithRange: method from NSString. You can take the below code as an example and modify it as per your requirements.
if (combined.length > 25)
{
NSString *beginning = [combined substringWithRange:NSMakeRange(0, 15)];
NSString *fromEnd = [combined substringWithRange:NSMakeRange(startPoint, combined.length-startPoint)];
}
You could make use of stringWithFormat - basically of printf if you want to pad with just a single char. Below I give some examples which I have constructed to illustrate, so it won't run out the box, but you only need to comment out the ones you do not want to make it work.
// To get 50 spaces
NSString * s50 = [NSString stringWithFormat:#"%*s", 50, ""];
// Pad with these characters, select only 1
// This will pad with spaces
char * pad = "";
// This will pad with minuses - you need enough to fill the whole field
char * pad = "-------------------------------------------------------";
// Some string
NSString * s = #"Hi there";
// Here back and front are just int's. They must be, but they can be calculated,
// e.g. you could have this to pad to 50
int back = 50 - s.length; if ( back < 0 ) back = 0;
// Pad s at the back
int back = 20;
NSString * sBack = [NSString stringWithFormat:#"%#%*s", s, back, pad];
// Pad s in front
int front = 10;
NSString * sFront = [NSString stringWithFormat:#"%*s%#", front, pad, s];
// Pad s both sides
NSString * sBoth = [NSString stringWithFormat:#"%*s%#%*s", front, pad, s, back, pad];
Note that the amounts here are parameterised. I use e.g. 50 in the first line but that could just as well be n as long as n is an int and you can use that to then perform calculations, store it in n and pad. There is an example in the code.
Here is a sample of the output
2020-11-04 08:16:22.908828+0200 FormatSpecifiers[768:15293] [Hi there-------------------------------------------------------]
2020-11-04 08:16:22.908931+0200 FormatSpecifiers[768:15293] [-------------------------------------------------------Hi there]
2020-11-04 08:16:22.908992+0200 FormatSpecifiers[768:15293] [-------------------------------------------------------Hi there-------------------------------------------------------]
I just show how to pad the combined string. To combine the string of course just use stringByAppendingString e.g.
NSString * s = [a stringByAppendingString:b];
and then you can do calcs based on s.length e.g. as shown in the example.

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 do i perform an arabic search in SQL Lite ignoring diacritics?

I'm trying to extract words that are stored in my SQL Lite database with ignoring diacritics but it always return an empty result. my database contains arabic words with diacritics and i would to make a search with words which are don't contain diacritics.
NSString *queryStatement = [[NSString alloc ]initWithFormat:#"SELECT ID,ARABIC, ARABICMEANING, FRENCHINARABIC, FRENCH, BEGINARABIC,BEGINFRENCH,ISFAVORITE FROM DictionaryDB WHERE FRENCHINARABIC LIKE \"%%%#%%\"",searchedWord];
For example searchedWord can be #"أكل" and can be with diacritics #"أَكَلَ".
How can i resolve this problem?
I solved such a thing by creating my own SQLite function that does this.
The basic idea is your query becomes:
NSString *queryStatement = [[NSString alloc] initWithFormat:#"SELECT ID, ARABIC, ARABICMEANING, FRENCHINARABIC, FRENCH, BEGINARABIC, BEGINFRENCH, ISFAVORITE FROM DictionaryDB WHERE contains(FRENCHINARABIC, '%#')", searchedWord];
where contains will be your custom function.
First you need to write a C function that implements the contains SQL function:
void contains(sqlite3_context *context, int argc, sqlite3_value **argv) {
BOOL res = NO;
if (argc < 2) {
res = NO;
} else {
char *textstr = (char *)sqlite3_value_text(argv[0]);
char *substr = (char *)sqlite3_value_text(argv[1]);
if (textstr && substr) {
NSString *text = [NSString stringWithCString:textstr encoding:NSUTF8StringEncoding];
NSString *sub = [NSString stringWithCString:substr encoding:NSUTF8StringEncoding];
// Adjust the options to suit your needs
NSRange range = [text rangeOfString:sub options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch | NSWidthInsensitiveSearch];
if (range.location != NSNotFound) {
res = YES;
}
}
}
sqlite3_result_int(context, res ? 1 : 0);
}
When you open your database connection you need to register this function:
// dbRef is your database reference
int res = sqlite3_create_function(dbRef, "contains", 2, SQLITE_UTF8, NULL, &contains, NULL, NULL);
if (res != SQLITE_OK) {
NSAssert1(0, #"Error: failed to create function in the database: '%s'.", sqlite3_errmsg(dbRef));
}
Side note - it's a bad idea to use stringWithFormat: to create your query. You should really consider using the sqlite3_bind_xxx functions to properly bind a value to a query. Using stringWithFormat: will fail if the value has any quotes or other special values. Using the sqlite3_bind_xxx functions takes care of properly quoting and escaping values.
I solved the problem by replacing the diacritics
here my correct query
#"SELECT ID,ARABIC, ARABICMEANING, FRENCHINARABIC, FRENCH, BEGINARABIC,BEGINFRENCH,ISFAVORITE FROM DictionaryDB WHERE
replace(replace(replace(replace(replace(replace(replace (replace(ARABICMEANING, 'ِ', ''),'ٍ',''),'ْ',''),'ّ','' ),'ٌ',''),'ُ',''),'ً',''),'َ','')LIKE \"%%%#%%\"",#"أكل"];

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 to count words within a text string?

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.

Resources