Some value of col in sqlite database:
a1b2
a2b3
a1b10
a10b1
if use order by in sql it will be:
a10b1
a1b10
a1b2
a2b3
I want it like NSNumericSearch of objc like this:
a1b2
a1b10
a2b3
a10b1
How do I write the SQL statements?
Start by creating a custom collating function:
int compare_natural(void *data, int len1, const void *str1, int len2, const void *str2) {
if (str1 && len1 > 0 && str2 && len2 > 0) {
NSString *s1 = [[NSString alloc] initWithBytesNoCopy:(void *)str1 length:len1 encoding:NSUTF8StringEncoding freeWhenDone:NO];
NSString *s2 = [[NSString alloc] initWithBytesNoCopy:(void *)str2 length:len2 encoding:NSUTF8StringEncoding freeWhenDone:NO];
// The use of NSNumericSearch is required for your need.
// The others are options depending on your needs
NSComparisonResult res = [s1 compare:s2 options:NSCaseInsensitiveSearch | NSNumericSearch | NSDiacriticInsensitiveSearch];
return res;
} else if (str1 && len1 > 0 && (!str2 || len2 == 0)) {
return -1; // empty strings to the end of the list (optional)
} else if (str2 && len2 > 0 && (!str1 || len1 == 0)) {
return 1; // empty strings to the end of the list (optional)
} else {
return 0;
}
}
Then register the custom collator. This needs to be done after you open the database.
// dbRef is your sqlite3 database reference
int rc = sqlite3_create_collation(dbRef, "BYNUMBER", SQLITE_UTF8, NULL, &compare_natural);
Then update your query so it ends with "COLLATE BYNUMBER"
SELECT some_col ORDER BY some_col COLLATE BYNUMBER;
Related
Localized.strings files may contain escaped chars like \n and \".
How do I efficiently unescape them?
I know I could write my own function that looks for a \ and removes it and then keeps searching at the second-next character (so that I don't turn \\ into nothing), but that won't handle special escape methods such as an octal character numbers (e.g. \012 for a LF) and possibly other forms.
I'd think that NSString would offer a function for that but I can't find any.
Actually, this appears to be a duplicate of NSString strip regular \ escape characters how?, but that question has a lot of bad answers and not a single correct answer. And the user is not active any more, meaning no one will ever accept a correct answer there. How shall that be handled on SO?
Here's a self-written ObjC version that works for most cases. It defines an unescaped method for NSString.
#import <Cocoa/Cocoa.h>
#interface NSString (BackslashUnescaping)
- (NSString*) unescaped;
#end
#implementation NSString (BackslashUnescaping)
- (NSString*) unescaped
{
NSString *text = self;
NSInteger charIndex = 0;
while (true) {
NSInteger textLen = text.length;
if (charIndex >= textLen) {
break;
}
NSRange remainingRange = NSMakeRange (charIndex, textLen-charIndex);
NSRange charRange = [text rangeOfString:#"\\"options:0 range:remainingRange];
if (charRange.length == 0) {
// no more backslashes -> done
break;
}
charIndex = charRange.location + 1;
if (charIndex >= textLen) {
// reached end of string -> exit loop
break;
}
// check char following the backslash
unichar nextChar = [text characterAtIndex:charIndex];
unichar replacementChar;
NSInteger skipLen = 1;
if (nextChar >= 'a' && nextChar <= 'z') {
if (nextChar == 'n') {
replacementChar = '\n'; // LF
} else if (nextChar == 'r') {
replacementChar = '\r'; // CR
} else if (nextChar == 't') {
replacementChar = '\t'; // TAB
} else if (nextChar == 'x') {
// A hex char code
const NSInteger xtraLen = 2;
if (charIndex+xtraLen >= textLen) break;
// Note: Does not make sure that both chars are valid hex chars
NSString *code = [text substringWithRange:NSMakeRange(charIndex+1, 2)];
char ch = strtol(code.UTF8String, NULL, 16);
replacementChar = ch;
skipLen += xtraLen;
} else if (nextChar == 'u') {
// A unicode char code
const NSInteger xtraLen = 4;
if (charIndex+xtraLen >= textLen) break;
// Note: Does not make sure that all four chars are valid hex chars
NSString *code = [text substringWithRange:NSMakeRange(charIndex+1, 4)];
unichar ch = strtol(code.UTF8String, NULL, 16);
replacementChar = ch;
skipLen += xtraLen;
} else {
// an unknown escape code - this should be fixed
NSAssert(false, #"There's a case missing for escaping \\%c", nextChar);
}
} else if (nextChar >= '0' && nextChar <= '9') {
unichar nextChar2 = 0;
if (charIndex > textLen) { // get the second octal char
nextChar2 = [text characterAtIndex:charIndex+1];
}
if (nextChar == '0' && (nextChar2 < '0' || nextChar2 > '9')) {
// A short NUL (\0) char
replacementChar = 0;
} else {
// An octal char code
const NSInteger xtraLen = 2;
if (charIndex+xtraLen >= textLen) break;
// Note: Does not make sure that the last char is a valid octal char
NSString *code = [text substringWithRange:NSMakeRange(charIndex, 3)];
char ch = strtol(code.UTF8String, NULL, 8); // https://stackoverflow.com/a/12820646/43615
replacementChar = ch;
skipLen += xtraLen;
}
} else {
// Handle all generic escapes, like for \\ and \"
replacementChar = nextChar;
}
#if 0 // Use string concatenation
charIndex += skipLen-1;
NSString *head = [text substringToIndex:charRange.location];
NSString *tail = [text substringFromIndex:charIndex+1];
text = [NSString stringWithFormat:#"%#%C%#", head, replacementChar, tail];
#else // Use a mutable string
if (text == self) {
text = text.mutableCopy;
}
NSRange replacedRange = NSMakeRange(charRange.location, skipLen+1);
NSString *replacement = [NSString stringWithCharacters:&replacementChar length:1];
[(NSMutableString*)text replaceCharactersInRange:replacedRange withString:replacement];
charIndex += 1;
#endif
}
return text;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSArray *testValues = #[
#"CR:\\rLF:\\n",
#"\\\"quoted\\\"",
#"Backslash: \\\\",
#"Octal x (\170):\\170",
#"Hex x (\x78):\\x78",
#"Unicode ะค (\u0424):\\u0424",
#"NUL char:\\0.",
#"Bad Hex:\\x7x", // this is not detected being invalid
#"Bad Hex:\\x7",
#"Incomplete :\\13"
];
for (NSString *s in testValues) {
NSString *s2 = [NSString stringWithFormat:#"Escaped: %#\nUnescaped: %#", s, s.unescaped];
printf("\n%s\n", s2.UTF8String);
}
}
return 0;
}
I have a database, and i do a S ELECT statement on SQL for 10 random rows.
It's for Iphone App, so Objective C.
How could i get back the information after the statement ?
...
const char *sql3 = [[NSString stringWithFormat:#"SELECT id FROM tabledesquestions ORDER BY RANDOM() LIMIT 10"] cStringUsingEncoding:NSUTF8StringEncoding];
sqlite3_stmt *sql1Statement;
if(sqlite3_prepare(database1, sql3, -1, &sql1Statement, NULL) != SQLITE_OK)
{
NSLog(#"Problem with prepare statement");
}
while (sqlite3_step(sql1Statement)==SQLITE_ROW) {
numeroqdonnee = sqlite3_column_int(sql1Statement, 0);
}
For now, i get back info just for the first row. How could i get back the info (id) for the others rows.
I would like something like that
numeroqdonnee2 =
numeroqdonnee3 =
numeroqdonnee4 =
...
Many thanks
You could add the results to some array:
const char *sql3 = [[NSString stringWithFormat:#"SELECT id FROM tabledesquestions ORDER BY RANDOM() LIMIT 10"] cStringUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *results = [NSMutableArray array];
sqlite3_stmt *sql1Statement;
int returnCode;
if(sqlite3_prepare(database1, sql3, -1, &sql1Statement, NULL) != SQLITE_OK)
{
NSLog(#"Problem with prepare statement: %s", sqlite3_errmsg(database1));
}
else
{
while ((returnCode = sqlite3_step(sql1Statement)) == SQLITE_ROW) {
numeroqdonnee = sqlite3_column_int(sql1Statement, 0);
[results addObject:#(numeroqdonnee)];
}
if (returnCode != SQLITE_DONE)
NSLog(#"Problem with step statement: %s", sqlite3_errmsg(database1));
sqlite3_finalize(sql1Statement);
}
// now do whatever you want with this results array
NSLog(#"results = %#", results);
// or
[results enumerateObjectsUsingBlock:^(NSNumber *obj, NSUInteger idx, BOOL *stop) {
NSLog(#"numeroqdonnee%d = %d", idx, [obj integerValue]);
}];
Or you could just log the results directly:
const char *sql3 = [[NSString stringWithFormat:#"SELECT id FROM tabledesquestions ORDER BY RANDOM() LIMIT 10"] cStringUsingEncoding:NSUTF8StringEncoding];
sqlite3_stmt *sql1Statement;
int returnCode;
if(sqlite3_prepare(database1, sql3, -1, &sql1Statement, NULL) != SQLITE_OK)
{
NSLog(#"Problem with prepare statement: %s", sqlite3_errmsg(database1));
}
else
{
while ((returnCode = sqlite3_step(sql1Statement)) == SQLITE_ROW) {
numeroqdonnee = sqlite3_column_int(sql1Statement, 0);
NSLog(#"numeroqdonnee = %d", numeroqdonnee);
}
if (returnCode != SQLITE_DONE)
NSLog(#"Problem with step statement: %s", sqlite3_errmsg(database1));
sqlite3_finalize(sql1Statement);
}
Note, I'd suggest you log the sqlite3_errmsg if you have any problems (otherwise you're just flying blind). I've also added the sqlite3_finalize (which your original code sample may have had, but I just wanted to make sure).
----I have two instances of NSString, and one of them is defined within a while loop, but the other one is after that. Xcode seems to think that since this first instance (we'll call string1) is in a while loop, it will not be defined. However, for the program to proceed out of the while loop IT WILL ALWAYS DEFINE STRING1. An NSString is in another while loop thats the same thing.
----Outside of both while loops, at the end, in the code I have a method of NSString done to both of them (isEqualtoString), but Xcode tells me that string1 and string two are not defined. The program should work, but the compiler stops me. Is there anything I can change to make string1 and string2 appear defined in Xcode's eyes.
----I'm using this for the registration page, and I need these in while loops because they need to cycle until the user enters in through the console a username that fits my requirements.
EDIT: Added in actual code.
int numb1, numb2;
char usercheck1[60];
char usercheck2[60];
//Registration
numb2 = 1;
while (numb2 == 1){
numb1 = 1;
while (numb1 == 1){
numb1 = 0;
NSLog(#"Username:");
fgets(usercheck1, sizeof usercheck1, stdin);
int c2;
while((c2 = getchar()) != '\n' && c2 != EOF);
if (usercheck1 [sizeof (usercheck1)-1] == '\n'){ // In case that the input string has 12 characters plus '\n'
usercheck1 [sizeof (usercheck1)-1] = '\0';} // Plus '\0', the '\n' isn't added and the if condition is false.
NSString* string1 = [NSString stringWithUTF8String: usercheck1];
//Makes sure string contains no spaces and string length is correct size.
if ([string1 length] > 12){
NSLog (#"Username must be 12 characters or less!");
numb1 = 1;}
if ([string1 length] < 5){
NSLog (#"Username must be 4 characters or more!");
numb1 = 1;}
if ([string1 rangeOfString:#" " ].location != NSNotFound){
NSLog(#"Username cannot contain spaces!");
numb1 = 1;}
}
numb1 = 1;
while (numb1 == 1){
numb1 = 0;
NSLog(#"Confirm Username:");
fgets(usercheck2, sizeof usercheck2, stdin);
int c2;
while((c2 = getchar()) != '\n' && c2 != EOF);
if (usercheck2 [sizeof (usercheck2)-1] == '\n'){ // In case that the input string has 12 characters plus '\n'
usercheck2 [sizeof (usercheck2)-1] = '\0';} // Plus '\0', the '\n' isn't added and the if condition is false.
NSString* string2 = [NSString stringWithUTF8String: usercheck2];
//Makes sure string contains no spaces and string length is correct size.
if ([string2 length] > 12){
NSLog (#"Username must be 12 characters or less!");
numb1 = 1;}
if ([string2 length] < 5){
NSLog (#"Username must be 4 characters or more!");
numb1 = 1;}
if ([string2 rangeOfString:#" " ].location != NSNotFound){
NSLog(#"Username cannot contain spaces!");
numb1 = 1;}
}
if ([string2 isEqualToString: string1] == YES){
NSLog(#"Usernames confirmed! Username:%s", string2);
numb2 = 0;}
else {NSLog(#"Usernames do not match. Try again");
numb2 = 1;}
}
}
As you can see, it would work if it actually compiled and ran, but the compiler just doesn't like me using string2 in the if statement for isEqualToString. It gives me the error :
"Use of undeclared identifier 'string2'"
Also, move that statement and the else statment outside the two sub-while statements, it gives me that error for BOTH string1 and string2.
XCode version is 4.6.3, I'm programming for the Mac OS X on 10.8.4
You can't access variables outside of the scope in which they are declared. Since string1 and string2 are declared within the two while blocks, you can't use them outside of the while blocks.
There are many things that could be improved in this code. Try something like this:
NSString *username1;
NSString *username2;
while (1) {
while (1) {
NSLog(#"Username:");
char usercheck[60];
fgets(usercheck, sizeof usercheck1, stdin);
int c2;
while ((c2 = getchar()) != '\n' && c2 != EOF);
if (usercheck [sizeof (usercheck) - 1] == '\n') { // In case that the input string has 12 characters plus '\n'
usercheck[sizeof (usercheck)-1] = '\0';
} // Plus '\0', the '\n' isn't added and the if condition is false.
NSString *string1 = [NSString stringWithUTF8String:usercheck];
// Makes sure string contains no spaces and string length is correct size.
if ([string1 length] > 12) {
NSLog(#"Username must be 12 characters or less!");
} else if ([string1 length] < 5) {
NSLog(#"Username must be 4 characters or more!");
} else if ([string1 rangeOfString:#" "].location != NSNotFound) {
NSLog(#"Username cannot contain spaces!");
} else {
username1 = string1;
break; // username is good
}
}
while (1) {
NSLog(#"Confirm Username:");
char usercheck[60];
fgets(usercheck, sizeof usercheck, stdin);
int c2;
while ((c2 = getchar()) != '\n' && c2 != EOF);
if (usercheck[sizeof (usercheck) - 1] == '\n') { // In case that the input string has 12 characters plus '\n'
usercheck[sizeof (usercheck) - 1] = '\0';
} // Plus '\0', the '\n' isn't added and the if condition is false.
NSString *string2 = [NSString stringWithUTF8String:usercheck];
//Makes sure string contains no spaces and string length is correct size.
if ([string2 length] > 12) {
NSLog (#"Username must be 12 characters or less!");
} else if ([string2 length] < 5) {
NSLog (#"Username must be 4 characters or more!");
} else if ([string2 rangeOfString:#" "].location != NSNotFound) {
NSLog(#"Username cannot contain spaces!");
} else {
username2 = string2;
break;
}
}
if ([username1 isEqualToString:username2]) {
NSLog(#"Usernames confirmed! Username:%#", username1);
break;
} else {
NSLog(#"Usernames do not match. Try again");
}
}
I need to check if a char is digit or not.
NSString *strTest=#"Test55";
char c =[strTest characterAtIndex:4];
I need to find out if 'c' is a digit or not. How can I implement this check in Objective-C?
Note: The return value for characterAtIndex: is not a char, but a unichar. So casting like this can be dangerous...
An alternative code would be:
NSString *strTest = #"Test55";
unichar c = [strTest characterAtIndex:4];
NSCharacterSet *numericSet = [NSCharacterSet decimalDigitCharacterSet];
if ([numericSet characterIsMember:c]) {
NSLog(#"Congrats, it is a number...");
}
In standard C there is a function int isdigit( int ch ); defined in "ctype.h". It will return nonzero (TRUE) if ch is a digit.
Also you can check it manually:
if(c>='0' && c<='9')
{
//c is digit
}
There is a C function called isdigit.
This is actually quite simple:
isdigit([YOUR_STRING characterAtIndex:YOUR_POS])
You may want to check NSCharacterSet class reference.
You can think of writing a generic function like the following for this:
BOOL isNumericI(NSString *s)
{
NSUInteger len = [s length];
NSUInteger i;
BOOL status = NO;
for(i=0; i < len; i++)
{
unichar singlechar = [s characterAtIndex: i];
if ( (singlechar == ' ') && (!status) )
{
continue;
}
if ( ( singlechar == '+' ||
singlechar == '-' ) && (!status) ) { status=YES; continue; }
if ( ( singlechar >= '0' ) &&
( singlechar <= '9' ) )
{
status = YES;
} else {
return NO;
}
}
return (i == len) && status;
}
Greetings,
I am new to objective c, and I have the following issue:
I have a NSString:
"There are seven words in this phrase"
I want to divide this into 3 smaller strings (and each smaller string can be no longer than 12 characters in length) but must contain whole words separated by a space, so that I end up with:
String1 = "There are" //(length is 9 including space)
String2 = "seven words"// (length is 11)
String3 = "in this" //(length is 7), with the word "phrase" ignored as this would exceed the maximum length of 12..
Currently I am splitting my original array into an array with:
NSArray *piecesOfOriginalString = [originalString componentsSeparatedByString:#" "];
Then I have multiple "if" statements to sort out situations where there are 3 words, but I want to make this more extensible for any array up to 39 (13 characters * 3 line) letters, with any characters >40 being ignored. Is there an easy way to divide a string based on words or "phrases" up to a certain length (in this case, 12)?
Something similar to this? (Dry-code warning)
NSArray *piecesOfOriginalString = [originalString componentsSeparatedByString:#" "];
NSMutableArray *phrases = [NSMutableArray array];
NSString *chunk = nil;
NSString *lastchunk = nil;
int i, count = [piecesOfOriginalString count];
for (i = 0; i < count; i++) {
lastchunk = [[chunk copy] autorelease];
if (chunk) {
chunk = [chunk stringByAppendingString:[NSString stringWithFormat:#" %#", [piecesOfOriginalString objectAtIndex:i]]];
} else {
chunk = [[[piecesOfOriginalString objectAtIndex:i] copy] autorelease];
}
if ([chunk length] > 12) {
[phrases addObject:lastchunk];
chunk = nil;
}
if ([phrases count] == 3) {
break;
}
}
well, you can keep splitting the string as you're already doing, or you could check out whether NSScanner suits your needs. In any case, you're going to have to do the math yourself.
Thanks McLemore, that is really helpful! I will try this immediately. My current solution is very similar, but less refined, as I hard coded the loops and use individual variable to hold the sub strings (called them TopRow, MidRow, and BottomRow), that and the memory management issue is overlooked... :
int maxLength = 12; // max chars per line (in each string)
int j=0; // for looping, j is the counter for managing the words in the "for" loop
TopRow = nil; //1st string
MidRow = nil; //2nd string
//BottomRow = nil; //third row string (not implemented yet)
BOOL Row01done = NO; // if YES, then stop trying to fill row 1
BOOL Row02done = NO; // if YES, then stop trying to fill row 2
largeArray = #"Larger string with multiple words";
tempArray = [largeArray componentsSeparatedByString:#" "];
for (j=0; j<[tempArray count]; j=j+1) {
if (TopRow == nil) {
TopRow = [tempArray objectAtIndex:j];
}
else {
if (Row01done == YES) {
if (MidRow == nil) {
MidRow = [tempArray objectAtIndex:j];
}
else {
if (Row02done == YES) {
//row 3 stuff goes here... unless I can rewrite as iterative loop...
//will need to uncommend BottomRow = nil; above..
}
else {
if ([MidRow length] + [[tempArray objectAtIndex:j] length] < maxLength) {
MidRow = [MidRow stringByAppendingString:#" "];
MidRow = [MidRow stringByAppendingString:[tempArray objectAtIndex:j]];
}
else {
Row02done = YES;
//j=j-1; // uncomment once BottowRow loop is implemented
}
}
}
}
else {
if (([TopRow length] + [[tempArray objectAtIndex:j] length]) < maxLength) {
TopRow = [TopRow stringByAppendingString:#" "];
TopRow = [TopRow stringByAppendingString:[tempArray objectAtIndex:j]];
}
else {
Row01done = YES;
j=j-1; //end of loop without adding the string to TopRow, subtract 1 from j and start over inside Mid Row
}
}
}
}