Create Generic Function For Database Selects - objective-c

I'm developing an iPhone app. I've got a function that reads data from a sqlite database and puts the results into an array. Everything works fine. Here is part of the function that fills the array:
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSString *aVar1 = [NSString stringWithUTF8String(char*)sqlite3_column_text(compiledStatement, 0)];
NSString *aVar2 = [NSString stringWithUTF8String(char*)sqlite3_column_text(compiledStatement, 1)];
NSArray *anArray = [NSArray arrayWithObjects:aVar1,aVar2,nil];
[returnArray addObject:anArray]
[anArray release];
}
//return the array
I want to make this function more generic so that it takes a sql statement string as a parameter, and returns a mutablearray of arrays, no matter how many columns are in the result set.
Is there a way to do this? The solution doesn't have to include arrays -- could be any collection object. I'm just looking for a way to make the function re-usable for other queries to the same database.

Couldn't you just do something like:
int numCols = sqlite3_column_count(compiledStatement);
NSMutableArray *result = [NSMutableArray array];
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < numCols; i++) {
[array addObject:
[NSString stringWithUTF8String:
(char *)sqlite3_column_text(compiledStatement, i)]];
}
[result addObject:array];
}

+(NSArray *)executeQueryAndReturnArray:(NSString *)query {
sqlite3_stmt *statement = nil;
const char *sql = [query UTF8String];
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) != SQLITE_OK) {
NSLog(#"[SQLITE] Error when preparing query!");
} else {
NSMutableArray *result = [NSMutableArray array];
while (sqlite3_step(statement) == SQLITE_ROW) {
NSMutableArray *row = [NSMutableArray array];
for (int i = 0; i < sqlite3_column_count(statement); i++) {
int colType = sqlite3_column_type(statement, i);
id value;
if (colType == SQLITE_TEXT) {`enter code here`
const unsigned char *col = sqlite3_column_text(statement, i);
value = [NSString stringWithFormat:#"%s", col];
} else if (colType == SQLITE_INTEGER) {
int col = sqlite3_column_int(statement, i);
value = [NSNumber numberWithInt:col];
} else if (colType == SQLITE_FLOAT) {
double col = sqlite3_column_double(statement, i);
value = [NSNumber numberWithDouble:col];
} else if (colType == SQLITE_NULL) {
value = [NSNull null];
} else {
NSLog(#"[SQLITE] UNKNOWN DATATYPE");
}
[row addObject:value];
}
[result addObject:row];
}
return result;
}
return nil;
}

Related

Sort NSmutablearray with two special keys

I have a tableview, its header is stored in a mutablearray, the array looks like
(2005 fall, 2005 spring, 2007 summer...)
When I output the tableview, I want the header in time ascending displayed.
2005 spring
2005 fall
2007 summer
I used the code here:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
[self.sectionKeys sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSString *key = [self.sectionKeys objectAtIndex:section];
return key;
}
It works fine with year. However, fall comes before spring and summer because of alphabetreason , what to do to fix it please?
Use a custom comparator to get a custom sort order:
NSMutableArray *array = [#[ #"2005 fall", #"2005 spring", #"2007 summer" ] mutableCopy];
NSArray *seasons = #[ #"spring", #"summer", #"fall", #"winter" ];
[array sortUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2) {
NSArray *parts1 = [str1 componentsSeparatedByString:#" "];
NSArray *parts2 = [str1 componentsSeparatedByString:#" "];
NSString *year1 = parts1[0];
NSString *year2 = parts2[0];
NSComparisonResult yearRes = [year1 compare:year2 options:NSNumericSearch];
if (yearRes == NSOrderedSame) {
NSString *season1 = parts1[1];
NSString *season2 = parts2[1];
NSUInteger index1 = [seasons indexOfObject:season1];
NSUInteger index2 = [seasons indexOfObject:season2];
if (index1 < index2) {
return NSOrderedAscending;
} else if (index1 > index2) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
} else {
return yearRes;
}
}];
Note - I might have the NSOrderedAscending and NSOrderedDescending backwards. Swap them if the sort of the seasons in the same year come out in the reverse order.
You need a lookup mechanism to define the ordering of the seasons
NSArray *seasons = #[#"spring", #"summer", #"fall", #"winter"];
NSArray *strings = #[#"2005 fall",#"2007 spring", #"2005 spring", #"2007 winter", #"2005 winter"];
strings = [strings sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
NSArray *string1Comps = [obj1 componentsSeparatedByString:#" "];
NSArray *string2Comps = [obj2 componentsSeparatedByString:#" "];
NSComparisonResult compareYearResult = [#([string1Comps[0] integerValue]) compare:#([string2Comps[0] integerValue]) ];
if (compareYearResult == NSOrderedSame) {
return [#([seasons indexOfObject:string1Comps[1]]) compare:#([seasons indexOfObject:string2Comps[1]])];
}
return compareYearResult;
}];
result
(
2005 spring,
2005 fall,
2005 winter,
2007 spring,
2007 winter
)
Another look up mechanism could be a block
NSNumber* (^lookUpSeason)(NSString *) = ^(NSString *seasonname){
static NSArray *seasons;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
seasons = #[#"spring", #"summer", #"fall", #"winter"];
});
return #([seasons indexOfObject:seasonname]);
};
This might look a bit cumbersome at first, but increases readability when used.
return [#([seasons indexOfObject:string1Comps[1]]) compare:#([seasons indexOfObject:string2Comps[1]])];
becomes
return [lookUpSeason(string1Comps[1]) compare:lookUpSeason(string2Comps[1])];
in both cases you could also give the lookup code into the comparator block, this will give you the opportunity to remove the same comparator with the lookup in other places.
like:
NSArray *strings = #[#"2005 fall", #"2007 spring", #"2005 spring", #"2007 winter", #"2005 winter", #"2005 summer", #"2000 hhh"];
NSComparisonResult (^yearAndSeasonComparator)(id,id) = ^NSComparisonResult(NSString *obj1, NSString *obj2) {
NSNumber* (^lookUpSeason)(NSString *) = ^(NSString *seasonname){
static NSArray *seasons;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
seasons = #[#"spring", #"summer", #"fall", #"winter"];
});
return #([seasons indexOfObject:seasonname]);
};
NSArray *string1Comps = [obj1 componentsSeparatedByString:#" "];
NSArray *string2Comps = [obj2 componentsSeparatedByString:#" "];
NSComparisonResult compareYearResult = [#([string1Comps[0] integerValue]) compare:#([string2Comps[0] integerValue]) ];
if (compareYearResult == NSOrderedSame) {
return [lookUpSeason(string1Comps[1]) compare:lookUpSeason(string2Comps[1])];
}
return compareYearResult;
};
strings = [strings sortedArrayUsingComparator:yearAndSeasonComparator];
The block assigned to yearAndSeasonComparator could now be reused in other places that would sort similar strings.
So you have an array with section keys. But the sections are not in order of the array, they need to be sorted. You will notice that cellForRowAtIndexPath: needs the exact same information. So sorting in this place is wrong.
What I do to handle this: I have a property "unsortedSectionKeys" and a property "sortedSectionKeys". sortedSectionKeys has a getter that checks for nil and stores a sorted copy of unsortedSectionKeys if it is nil. And whenever unsortedSectionKeys changes, you just set sortedSectionKeys to nil. (That solves at least some problems).
For your sorting, you need to write proper code. Use (void)sortUsingComparator:(NSComparator)cmptr to sort a mutable, or - (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr to get a sorted copy of an array.
Example:
[self.sectionKeys sortArrayUsingComparator:^NSComparisonResult(NSString* obj1, NSString* obj2) {
NSInteger year1 = obj1.integerValue;
NSInteger year2 = obj2.integerValue;
if (year1 < year2) return NSOrderedAscending;
if (year1 > year2) return NSOrderedDescending;
NSInteger season1 = 0;
if ([obj1 rangeOfString:#"spring" options:NSCaseInsensitiveSearch].location != NSNotFound)
season1 = 1;
if ([obj1 rangeOfString:#"summer" options:NSCaseInsensitiveSearch].location != NSNotFound)
season1 = 2;
if ([obj1 rangeOfString:#"fall" options:NSCaseInsensitiveSearch].location != NSNotFound)
season1 = 3;
if ([obj1 rangeOfString:#"winter" options:NSCaseInsensitiveSearch].location != NSNotFound)
season1 = 4;
NSInteger season2 = 0;
if ([obj2 rangeOfString:#"spring" options:NSCaseInsensitiveSearch].location != NSNotFound)
season2 = 1;
if ([obj2 rangeOfString:#"summer" options:NSCaseInsensitiveSearch].location != NSNotFound)
season2 = 2;
if ([obj2 rangeOfString:#"fall" options:NSCaseInsensitiveSearch].location != NSNotFound)
season2 = 3;
if ([obj2 rangeOfString:#"winter" options:NSCaseInsensitiveSearch].location != NSNotFound)
season2 = 4;
if (season1 < season2) return NSOrderedAscending;
if (season1 > season2) return NSOrderedDescending;
return NSOrderedSame;
}];
Your decision if winter is the first or last season in the year, since usually it's December to February.

Check if string is palindrome in objective c

I'm trying to check if a string is palindrome or not using objective c. I'm new to programming without any experience in other programming languages so bear with me please. I get stuck at my if condition I want it to say that if the first position in the string is equal to the last one the string is a palindrome.
What im a doing wrong?
int main (int argc, const char * argv[])
{
NSString *p = #"121" ;
BOOL palindrome = TRUE;
for (int i = 0 ; i<p.length/2+1 ; i++)
{
if (p[i] != p [p.Length - i - 1])
palindrome = false;
}
return (0);
}
You're trying to use an NSString as an NSArray (or probably, like a C string), which won't work. Instead, you need to use the NSString method characterAtIndex: to get the character to test.
Apart from the unbalanced braces, accessing a character from NSString is more complicated than using array notation. You need to use the method characterAtIndex: You can optimise your code, by breaking out of the loop if a palindrome is impossible and taking the length call outside of the for loop.
NSString *p = #"121";
NSInteger length = p.length;
NSInteger halfLength = (length / 2);
BOOL isPalindrome = YES;
for (int i = 0; i < halfLength; i++) {
if ([p characterAtIndex:i] != [p characterAtIndex:length - i - 1]) {
isPalindrome = NO;
break;
}
}
It may be desirable to check case insensitively. To do this, make the string be all lowercase before looping, using the lowercaseString method.
As pointed out by Nikolai in the comments, this would only work for strings containing 'normal' unicode characters, which is often not true — such as when using UTF8 for foreign languages. If this is a possibility, use the following code instead, which checks composed character sequences rather than individual characters.
NSString *p = #"121";
NSInteger length = p.length;
NSInteger halfLength = length / 2;
__block BOOL isPalindrome = YES;
[p enumerateSubstringsInRange:NSMakeRange(0, halfLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
NSRange otherRange = [p rangeOfComposedCharacterSequenceAtIndex:length - enclosingRange.location - 1];
if (![substring isEqualToString:[p substringWithRange:otherRange]]) {
isPalindrome = NO;
*stop = YES;
}
}];
var str: NSString = "123321"
var length = str.length
var isPalindrome = true
for index in 0...length/2{
if(str.characterAtIndex(index) != str.characterAtIndex(length-1 - index)){
print("\(index )not palindrome")
isPalindrome = false
break
}
}
print("is palindrome: \(isPalindrome)")
As it seems there's no answer yet that handles composed character sequences correctly I'm adding my two cents:
NSString *testString = #"\u00E0 a\u0300"; // "à à"
NSMutableArray *logicalCharacters = [NSMutableArray array];
[testString enumerateSubstringsInRange:(NSRange){0, [testString length]}
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
{
[logicalCharacters addObject:substring];
}];
NSUInteger count = [logicalCharacters count];
BOOL isPalindrome = YES;
for (NSUInteger idx = 0; idx < count / 2; ++idx) {
NSString *a = logicalCharacters[idx];
NSString *b = logicalCharacters[count - idx - 1];
if ([a localizedCaseInsensitiveCompare:b] != NSOrderedSame) {
isPalindrome = NO;
break;
}
}
NSLog(#"isPalindrome: %d", isPalindrome);
This splits the string into an array of logical characters (elements of a string that a normal user would call a "character").
#import Foundation;
BOOL isPalindrome(NSString * str)
{
if (!str || str.length == 0) return NO;
if (str.length == 1) return YES;
for(unsigned i = 0; i < str.length / 2; ++i)
if ([str characterAtIndex:i] != [str characterAtIndex:str.length - i - 1]) return NO;
return YES;
}
int main() {
#autoreleasepool {
NSLog(#"%s", isPalindrome(#"applelppa") ? "YES" : "NO");
} return 0;
}
Recursive
- (BOOL)isPaliRec:(NSString*)str :(int)start :(int)end{
if(start >= end)
return YES;
else if([str characterAtIndex:start] != [str characterAtIndex:end])
return NO;
else
return [self isPaliRec:str :++start :--end];
}
Non Recursive
- (BOOL)isPali:(NSString*)str{
for (int i=0; i<str.length/2; i++)
if([str characterAtIndex:i] != [str characterAtIndex:(str.length-i-1)])
return NO;
return YES;
}
you can call:
NSString *str = #"arara";
[self isPaliRec:str :0 :(int)str.length-1];
[self isPali:str];
Swift 3:
// Recursive
func isPaliRec(str: String, start: Int = 0, end: Int = str.characters.count-1) -> Bool {
if start >= end {
return true
} else if str[str.index(str.startIndex, offsetBy: start)] != str[str.index(str.startIndex, offsetBy: end)] {
return false
} else {
return isPaliRec(str: str, start: start+1, end: end-1)
}
}
// Non Recursive
func isPali(str: String) -> Bool {
for i in 0..<str.characters.count/2 {
let endIndex = str.characters.count-i-1
if str[str.index(str.startIndex, offsetBy: i)] != str[str.index(str.startIndex, offsetBy: endIndex)] {
return false
}
}
return true
}
// Using
let str = "arara"
isPaliRec(str: str)
isPali(str: str)
Also, you can use swift 3 methods like a string extension... It's more elegant. extension sample
NSString *str=self.txtFld.text;
int count=str.length-1;
for (int i=0; i<count; i++) {
char firstChar=[str characterAtIndex:i];
char lastChar=[str characterAtIndex:count-i];
NSLog(#"first=%c and last=%c",firstChar,lastChar);
if (firstChar !=lastChar) {
break;
}
else
NSLog(#"Pailndrome");
}
We can also do this using NSRange like this...
enter code NSString *fullname=#"123321";
NSRange rangeforFirst=NSMakeRange(0, 1);
NSRange rangeforlast=NSMakeRange(fullname.length-1, 1);
BOOL ispalindrome;
for (int i=0; i<fullname.length; i++) {
if (![[fullname substringWithRange:rangeforFirst] isEqualToString:[fullname substringWithRange:rangeforlast]]) {
NSLog(#"not match");
ispalindrome=NO;
return;
}
i++;
rangeforFirst=NSMakeRange(i, 1);
rangeforlast=NSMakeRange(fullname.length-i-1, 1);
}
NSLog(#"no is %#",(ispalindrome) ? #"matched" :#"not matched");
NSString *str1 = #"racecar";
NSMutableString *str2 = [[NSMutableString alloc] init];
NSInteger strLength = [str1 length]-1;
for (NSInteger i=strLength; i>=0; i--)
{
[str2 appendString:[NSString stringWithFormat:#"%C",[str1 characterAtIndex:i]]];
}
if ([str1 isEqual:str2])
{
NSLog(#"str %# is palindrome",str1);
}
-(BOOL)checkPalindromeNumber:(int)number{
int originalNumber,reversedNumber = 0,remainder;
originalNumber=number;
while (number!=0) {
remainder=number%10;
reversedNumber=(reversedNumber*10)+remainder;
number=number/10;
}
if (reversedNumber==originalNumber) {
NSLog(#"%d is Palindrome Number",originalNumber);
return YES;
}
else{
NSLog(#"%d is Not Palindrome Number",originalNumber);
return NO;
}
}

Split NSString with multiple delimiters?

For text bozo__foo!!bar.baz, how to split an NSString containing this into (bozo, foo, bar, baz)?
That is, separe it in components with strings (delimiters) __, !! and ..
You can split the strings using NSCharacterSet. Try this
NSString *test=#"bozo__foo!!bar.baz";
NSString *sep = #"_!.";
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:sep];
NSArray *temp=[test componentsSeparatedByCharactersInSet:set];
NSLog(#"temp=%#",temp);
I'm aware that this question has already been answered but this is a way to separate strings using multiple strings. This is a category to NSString.
- (NSArray<NSString *> *)componentsSeparatedByStrings:(NSArray<NSString *> *)separators
{
NSMutableArray<NSString *> *components = [[NSMutableArray alloc] init];
unichar buffer[self.length + 1];
NSInteger currentOrigin = 0;
NSInteger currentLength = 0;
[self getCharacters:buffer];
for(NSInteger i = 0; i < self.length; i++)
{
unichar currentChar = buffer[i];
currentLength++;
for(NSInteger n = 0; n < separators.count; n++)
{
NSString *currentDivider = [separators objectAtIndex:n];
if(currentDivider.length == 0)
{
return #[self];
}
else if(currentDivider.length > 1)
{
BOOL goodMatch = NO;
for(NSInteger x = 0; x < currentDivider.length; x++)
{
unichar charInDivider = [currentDivider characterAtIndex:x];
if(charInDivider == currentChar)
{
goodMatch = YES;
}
else
{
goodMatch = NO;
break;
}
if(goodMatch == YES && ((x + 1) != currentDivider.length))
{
i++;
currentLength++;
currentChar = buffer[i];
}
}
if(goodMatch == YES)
{
NSRange newComponentRange = NSMakeRange(currentOrigin, (currentLength - currentDivider.length));
NSString *newComponent = [self substringWithRange:newComponentRange];
currentOrigin = (i + 1);
currentLength = 0;
[components addObject:newComponent];
NSLog(#"%#", components);
}
}
else // If current divider is only one character long.
{
if([currentDivider characterAtIndex:0] == currentChar)
{
NSRange newComponentRange = NSMakeRange(currentOrigin, (currentLength - 1));
NSString *newComponent = [self substringWithRange:newComponentRange];
currentOrigin = (i + 1);
currentLength = 0;
[components addObject:newComponent];
break;
}
}
}
// Handle the end of the string.
if((i + 1) == self.length)
{
NSRange newComponentRange = NSMakeRange(currentOrigin, currentLength);
NSString *newComponent = [self substringWithRange:newComponentRange];
currentOrigin = 0;
currentLength = 0;
[components addObject:newComponent];
}
}
return components;
}
Example: "ABCD__EFGHI__JKLMNOP-QRST.UV_WXYZ"
NSLog(#"%#", [test componentsSeparatedByStrings:#[#"__", #"-", #"."]]);
Log Result: "(ABCD, EFGHI, JKLMNOP, QRST, "UV_WXYZ")"
NSString *text = #"bozo__foo!!bar.baz";
NSArray *split1 = [text componentsSeparatedByString:#"__"];
NSArray *split2 = [[split1 lastObject] componentsSeparatedByString:#"!!"];
NSArray *split3 = [[split2 lastObject] componentsSeparatedByString:#"."];
NSLog(#"%#, %#, %#, %#", split1[0], split2[0], split3[0], split3[1]);
More functional solution is to apply -componentsSeparatedByString: recursively, for each component, which was derived during previous separator application:
NSString Category
- (NSMutableArray<NSString *> *)gvr_componentsSeparatedByStrings:(NSArray<NSString *> *)separators {
if (separators.count == 0) {
return [NSMutableArray arrayWithObject:self];
}
NSString *separator = [separators firstObject];
NSArray *reducedSeparators = [separators gvr_arrayByRemovingFirstObject];
NSArray *components = [self componentsSeparatedByString:separator];
NSMutableArray *result = [NSMutableArray new];
for (NSString *component in components) {
NSMutableArray *subResult = [component gvr_componentsSeparatedByStrings:reducedSeparators];
[result addObjectsFromArray:subResult];
}
return result;
}
NSArray Category
- (NSArray *)gvr_arrayByRemovingFirstObject {
NSMutableArray *result = [NSMutableArray new];
for (NSInteger i = 1; i < self.count; i++) {
[result addObject:self[i]];
}
return [result copy];
}
I solved it for my project by looking for the longest separator, replacing the others with this one, then do the separation on the only one left.
Try this:
NSString *test = #"bozo__foo!!bar.baz";
test = [test stringByReplacingOccurrencesOfString:#"!!" withString:#"__"];
test = [test stringByReplacingOccurrencesOfString:#"." withString:#"__"];
NSArray<NSString *> *parts = [test componentsSeparatedByString:#"__"];

How to merge paths (NSString) in xcode?

Imagine you have two paths.
http://myserver.com/path1/path2 + /path1/path2/cache/image1.jpg = http://myserver.com/path1/path2/cache/image1.jpg
Both path strings could have more or less path components.
So what I'm asking is how to find the equal part in the strings and then remove that part from one of the strings?
You have no idea whether the "equal" parts are really equal or not. It's not uncommon to have, eg, paths like .../part1/part1/part1/...
For the mechanics of dealing with the paths, though, NSString has some nice methods -- lastPathComponent, stringByAppendindPathComponent, pathComponents, pathWithComponents, etc.
What about this:
- (NSString *)mergeStringsPrefix:(NSString *)prefix suffix:(NSString *)suffix
{
NSString *string = [NSString stringWithFormat:#"%#", prefix];
NSArray *prefixComponents = [prefix pathComponents];
NSArray *suffixComponents = [suffix pathComponents];
if ([prefixComponents count] == 0) return [string retain];
int rootIndex = [suffixComponents indexOfObject:#"/"];
int index = 1;
if (rootIndex == NSNotFound || rootIndex != 0) index = 0;
int startIndex = [prefixComponents indexOfObject:[suffixComponents objectAtIndex:index]];
if (startIndex == NSNotFound) return nil;
if ([suffixComponents count] - index < [prefixComponents count] - startIndex) return nil;
// fing length and check merge compatability
BOOL equalParts = YES;
for (int i=startIndex; i<[prefixComponents count] && equalParts; i++, index++)
{
NSString *el1 = [prefixComponents objectAtIndex:i];
NSString *el2 = [suffixComponents objectAtIndex:index];
if ([el1 compare:el2] != NSOrderedSame) equalParts = NO;
}
if (!equalParts) return nil;
// merge
for (int i=index; i<[suffixComponents count]; i++)
{
string = [string stringByAppendingFormat:#"/%#", [suffixComponents objectAtIndex:i]];
}
return [string retain];
}
This should do for you:
NSString* path1 = #"http://myserver.com/path1/path2";
NSString* path2 = #"/path1/path2/cache/image1.jpg";
NSMutableArray* path1Components = [NSMutableArray arrayWithArray:[path1 componentsSeparatedByString:#"/"]];
NSMutableArray* path2Components = [NSMutableArray arrayWithArray:[path2 componentsSeparatedByString:#"/"]];
[path2Components removeObjectAtIndex:0];
if ([path1Components containsObject:[path2Components objectAtIndex:0]]) {
NSUInteger objectIndex = [path1Components indexOfObject:[path2Components objectAtIndex:0]];
[path1Components removeObjectsInRange:NSMakeRange(objectIndex, [path1Components count]-objectIndex)];
[path1Components addObjectsFromArray:path2Components];
NSString* mergedPath = [path1Components componentsJoinedByString:#"/"];
NSLog(#"%#",mergedPath);
}

Remove only first instance of a character from a list of characters

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