NSDictionary Search Time Complexity - objective-c

Is the search operation selector objectForKey: of the NSDictionary class of order 1 time complexity like a hashtable?

I hate to have a mostly-link answer, but everything you might want to know and more is here:
Exposing NSDictionary
This is the code suggested for objectForKey:
- (id)objectForKey:(id)aKey
{
NSUInteger sizeIndex = _szidx;
NSUInteger size = __NSDictionarySizes[sizeIndex];
id *storage = (id *)object_getIndexedIvars(dict);
NSUInteger fetchIndex = [aKey hash] % size;
for (int i = 0; i < size; i++) {
id fetchedKey = storage[2 * fetchIndex];
if (fetchedKey == nil) {
return nil;
}
if (fetchedKey == aKey || [fetchedKey isEqual:aKey]) {
return storage[2 * fetchIndex + 1];
}
fetchIndex++;
if (fetchIndex == size) {
fetchIndex = 0;
}
}
return nil;
}
As Bartosz Ciechanowski says:
Worse case performance is linear
Read the rest!
He proves that there is definitely object instance == checking before an isEqual test. And a lot more.

Related

Merge Sorting in Objective C

I am trying to implement merge sort in objective -C.
This is a similar question asked in the following link , did not find it answered so creating a new question.
Merge sort in Objective-C
This is what I have tried ,
-(NSArray *)mergeSort:(NSArray *)unsortedArray {
if ([unsortedArray count] < 2)
return unsortedArray;
long mid = [unsortedArray count] / 2;
NSRange left = NSMakeRange(0, mid);
NSRange right = NSMakeRange(mid, [unsortedArray count] - mid);
NSArray *rightArray = [unsortedArray subarrayWithRange:right];
NSArray *leftArray = [unsortedArray subarrayWithRange:left];
NSArray *resultArray = [self merge:leftArray andRight:rightArray];
return resultArray;
}
-(NSArray *)merge:(NSArray *)leftArray andRight:(NSArray *)rightArray {
NSMutableArray *result = [NSMutableArray array];
int right = 0;
int left = 0;
while (left < [leftArray count] && right < [rightArray count]) {
NSComparisonResult comparisonResult = [leftArray[left] compare:rightArray[right]];
if (comparisonResult != NSOrderedDescending) {
[result addObject:[leftArray objectAtIndex:left++]];
} else {
[result addObject:[rightArray objectAtIndex:right++]];
}
/*if ([[leftArray objectAtIndex:left] intValue] < [[rightArray objectAtIndex:right] intValue]) {
[result addObject:[leftArray objectAtIndex:left++]];
//left++;
} else {
[result addObject:[rightArray objectAtIndex:right++]];
//right++;
}*/
}
NSRange leftRange = NSMakeRange(left, [leftArray count] - left);
NSRange rightRange = NSMakeRange(right, [rightArray count] - right);
NSArray * newRight = [rightArray subarrayWithRange:rightRange];
NSArray * newLeft = [leftArray subarrayWithRange:leftRange];
newLeft = [result arrayByAddingObjectsFromArray:newLeft];
return [newLeft arrayByAddingObjectsFromArray:newRight];
}
Kindly let me know if anyone has any other approaches for merge sort.
I dont understand why do you people want the long way.. Even though there are already easy way of doing this...
I made one myself hope this will help you..
- (NSArray *)arrayMergeSort:(NSArray *)targetArray
{
if (targetArray.count < 2)
return targetArray;
long midIndex = targetArray.count/2;
NSArray *arrayLeft = [targetArray subarrayWithRange:NSMakeRange(0, midIndex)];
NSArray *arrayRight= [targetArray subarrayWithRange:NSMakeRange(midIndex, targetArray.count - midIndex)];
return [self arrayMerge: [self arrayMergeSort:arrayLeft] : [self arrayMergeSort:arrayRight]];
}
For arrange merge:
- (NSArray *)arrayMerge:(NSArray *)arrayLeft :(NSArray *)arrayRight
{
NSMutableArray *resultArray = [[NSMutableArray alloc] init];
int i = 0, j = 0;
while (i < arrayLeft.count && j < arrayRight.count)
[resultArray addObject:([arrayLeft[i] intValue] < [arrayRight[j] intValue]) ? arrayLeft[i++] : arrayRight[j++]];
while (i < arrayLeft.count)
[resultArray addObject:arrayLeft[i++]];
while (j < arrayRight.count)
[resultArray addObject:arrayRight[j++]];
return resultArray;
}
And using it like:
//Sample array
NSArray *activeArray = #[#101,#201,#301,#121,#11,#123,#21,#14,#32,#76,#89,#987,#65];
NSLog(#"arrayMergeSort %#",[self arrayMergeSort:activeArray]);
Output would be:
And also this bubble sort if you needed this:
- (NSArray *)arrayBubbleSort:(NSArray *)targetArray
{
NSMutableArray *resultArray = [targetArray mutableCopy];
for (int k = 0; k < resultArray.count; k++)
{
for (int l = 0; l < resultArray.count; l++)
{
if ([resultArray[k] intValue] < [resultArray[l] intValue])
{
[resultArray exchangeObjectAtIndex:k withObjectAtIndex:l];
}
}
}
return resultArray;
}
Hope i've helped you.. Cheers..
You've made a simple mistake. Merge sort works my splitting the array, sorting to the two halves, then merging the results.
Your mergeSort: method does the split, doesn't sort the two halves, and then calls merge: to merge the two (unfortunately unsorted) halves.
Before calling merge: you need to make recursive calls to mergeSort: to sort the two halves - this is the simple step you missed out.
I'm guessing this in a learning exercise, so no code, but you're almost there (fix it and it does work).
BTW Once you've fixed it you might want to think about why you don't need to create new arrays for the split part (but its far easier to create a new array for the merges).
HTH

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

Anagram algorithm objective C

i have written the following code to check anagram want to know is this perfect & is there any better way to implement the same in objective C
-(BOOL) findAnagram :(NSString *) string1 :(NSString *) string2
{
int len = string1.length;
if (len != string2.length)
{
return false;
}
for (int i=0; i < len; i++)
{
int h = 0;
int q = 0;
for (int k = 0; k < len ; k ++)
{
if ([string1 characterAtIndex:i] == [string1 characterAtIndex:k])
{
h++;
}
if ([string1 characterAtIndex:i] == [string2 characterAtIndex:k])
{
q++;
}
}
if (h!=q)
{
return false;
}
}
return TRUE;
}
A better performing version than yours, which is a O(n ^ 2) algorithm, is a O(n) algorithm:
BOOL anagrams(NSString *a, NSString *b)
{
if (a.length != b.length)
return NO;
NSCountedSet *aSet = [[NSCountedSet alloc] init];
NSCountedSet *bSet = [[NSCountedSet alloc] init];
for (int i = 0; i < a.length; i++)
{
[aSet addObject:#([a characterAtIndex:i])];
[bSet addObject:#([b characterAtIndex:i])];
}
return [aSet isEqual:bSet];
}
You want to know if two strings contain exactly the same characters? Easiest way would probably be to sort both of them and compare the sorted version.
Another way would be to count the number of appearances of each letter (how many As, how many Bs, and so forth), then compare those counts.
(Note: The second way is just a variation of the first one, it's one efficient way to sort a string)
It looks fine to me. But the code style is slightly odd. I would write it like this:
- (BOOL)isStringAnagram:(NSString *)string1 ofString:(NSString *)string2 {
int len = string1.length;
if (len != string2.length) {
return NO;
}
for (int i=0; i < len; i++) {
int h = 0;
int q = 0;
for (int k = 0; k < len; k++) {
if ([string1 characterAtIndex:i] == [string1 characterAtIndex:k]) {
h++;
}
if ([string1 characterAtIndex:i] == [string2 characterAtIndex:k]) {
q++;
}
}
if (h != q) {
return NO;
}
}
return YES;
}
The main issue I have is with the method name. While it's possible to have parameters that have nothing before them in the name, it is not advisable. i.e. you had findAnagram:: as the name whereas I've used isStringAnagram:ofString:.
This is an implementation on #zmbq suggestion of sorting and comparing.
You should consider the requirements of deleting spaces and being case insensitive.
- (BOOL)isAnagram:(NSString *)leftString and:(NSString *)rightString {
NSString *trimmedLeft = [[leftString stringByReplacingOccurrencesOfString:#" " withString:#""] lowercaseString];
NSString *trimmedRight = [[rightString stringByReplacingOccurrencesOfString:#" " withString:#""] lowercaseString];
return [[self stringToCharArraySorted:trimmedLeft] isEqual:[self stringToCharArraySorted:trimmedRight]];
}
- (NSArray *)stringToCharArraySorted:(NSString *)string {
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0 ; i < string.length ; i++) {
[array addObject:#([string characterAtIndex:i])];
}
return [[array sortedArrayUsingSelector:#selector(compare:)] copy];
}
called like this
BOOL isAnagram = [self isAnagram:#"A BC" and:#"cba"];
Check the following method which check Anagram strings.
-(BOOL)checkAnagramString:(NSString*)string1 WithAnotherString:(NSString*)string2{
NSCountedSet *countSet1=[[NSCountedSet alloc]init];
NSCountedSet *countSet2=[[NSCountedSet alloc]init];
if (string1.length!=string2.length) {
NSLog(#"NOT ANAGRAM String");
return NO;
}
for (int i=0; i<string1.length; i++) {
[countSet1 addObject:#([string1 characterAtIndex:i])];
[countSet2 addObject:#([string2 characterAtIndex:i])];
}
if ([countSet1 isEqual:countSet2]) {
NSLog(#"ANAGRAM String");
return YES;
} else {
NSLog(#"NOT ANAGRAM String");
return NO;
}
}
Another run of the mill algorithm:
- (BOOL) testForAnagramWithStrings:(NSString *)stringA andStringB: (NSString *)stringB{
stringA = [stringA lowercaseString];
stringB = [stringB lowercaseString];
int counter = 0;
for (int i=0; i< stringA.length; i++){
for (int j=0; j<stringB.length;j++){
if ([stringA characterAtIndex:i]==[stringB characterAtIndex:j]){
counter++;
}
}
}
if (counter!= stringA.length){
return false;
}
return true;
}

After 15000 iterations, getting "error: can't allocate region"-- no memory warning

Points: Using ARC;
full error is:
malloc: * mmap(size=2097152) failed (error code=12)
error: can't allocate region
** set a breakpoint in malloc_error_break to debug
four times.
The only code of any substance to the question is:
-(void)iterate:(NSString *)string{
frontString = NULL;
backString = NULL;
arrayOfNumbers = NULL;
backwardArrayOfNumbers = NULL;
nextString = NULL;
nextArrayOfNumbers = NULL;
nextArrayOfNumbers = [NSMutableArray new];
nextString = [NSMutableString new];
backwardArrayOfNumbers = [NSMutableArray new];
arrayOfNumbers = [NSMutableArray new];
frontString = [[NSMutableString alloc] initWithString:string];
backString = [NSMutableString new];
if (string.length > 1) {
iteration++;
for (unsigned long i = 0; i < string.length; ++i) {
NSString *sub = [string substringWithRange:(NSRange){i, 1}];
[arrayOfNumbers addObject:sub];
NSString *back = [string substringWithRange:(NSRange){string.length-(i+1), 1}];
[backwardArrayOfNumbers addObject:back];
[backString appendString:back];
sub = NULL;
back = NULL;
}
if ([frontString isEqualToString:backString]) {
[palindromicNumberTextView setText:string];
[iterationLabel setText:[NSString stringWithFormat:#"%ld", iteration]];
} else {
int carrier = 0;
for (long long j = arrayOfNumbers.count-1; j > -1; --j) {
int a = [[arrayOfNumbers objectAtIndex:j] intValue];
int b = [[backwardArrayOfNumbers objectAtIndex:j] intValue];
//NSLog(#"a = %i, b = %i", a, b);
int c = a+b+carrier;
if (c > 9) {
c = c-10;
carrier = 1;
} else {
carrier = 0;
}
[nextArrayOfNumbers addObject:[NSString stringWithFormat:#"%i", c]];
if (carrier == 1 && (nextArrayOfNumbers.count == arrayOfNumbers.count)) {
[nextArrayOfNumbers addObject:[NSString stringWithFormat:#"%i", carrier]];
}
//NSLog(#"nextArrayOfNumbers = %#", nextArrayOfNumbers);
}
for (int i = 0; i < nextArrayOfNumbers.count; ++i) {
NSString *back = [nextArrayOfNumbers objectAtIndex: nextArrayOfNumbers.count-(i+1)];
//NSLog(#"back = %#", back);
[nextString appendString:back];
back = NULL;
}
if (iteration%1000 == 0) {
NSLog(#"iteration %ld; count:%u", iteration, nextArrayOfNumbers.count);
}
//NSLog(#"iteration %ld", iteration);
[self iterate:nextString];
}
}
}
15,000+ iterations later, crash + error, with no memory warning:
- (void)didReceiveMemoryWarning
{
NSLog(#"Error near iteration %ld", iteration);
[super didReceiveMemoryWarning];
}
Any ideas what the problem might be? Thanks in advance!
EDIT: Converting between CString and NSString is a pain with ARC. So I went with #autoreleasepool{} inside the loops (and nil rather than NULL) and that has dramatically reduced memory usage. I'm up over 50k iterations.
So the program itself has some issues if it's using that much memory -- you can reduce memory usage of this algo significantly.
Anyways, you won't get a memory warning if this takes place on the main thread. The memory warning is made on the main thread, and you give it no chance to handle the warning because you exhaust memory before the main thread's run loop has another chance to do its usual work (such as handle memory warnings or handle other events).
Perhaps you would see something different if this happened on a secondary thread, but that would not solve the root of the problem.
You are solving a problem recursively which fills up memory.
Your computer has to keep track of where to go when it finishes each of 15,000 cycles. That can become a lot of space.
If it is possible to make this "tail recursive" you could save space. For instance:
"bad" recursion which saves in memory 7 + 6 + 5 + 4 + 3 + 2
int factorial (int x)
{
if (x > 1)
{
return (x + recursion (x - 1));
}
else return x;
}
//
good recursion start with y = 1
only saves in memory two integers, updating them every cycle.
int factorial (int x, int y)
{
if (x > 1)
{
return factorial (x - 1, (x * y));
}
else return x;
}

Optimizing a for in loop with NSMutableDictionary

I have an NSMutableDictionary that contains a MPMediaItem and a string of it's title for it's key. I currently have 1,777 items in the dictionary.
I am looping through the dictionary looking for a fuzzy match with a supplied NSString. How can I speed it up? It takes about 6 seconds every time it's run.
I'll just past in the loop itself
#autoreleasepool {
float currentFoundValue = 1000.0;
NSMutableArray *test;
MPMediaItemCollection *collection;
float match;
for(id key in artistDictionary)
{
NSString *thisArtist = key;
int suppliedCount = [stringValue length];
int keyCount = [thisArtist length];
if(suppliedCount > keyCount)
{
match = [StringDistance stringDistance:thisArtist :stringValue];
} else {
match = [StringDistance stringDistance:stringValue :thisArtist];
}
if(match < currentFoundValue)
{
currentFoundValue = match;
test = [artistDictionary objectForKey:thisArtist];
collection = [[MPMediaItemCollection alloc] initWithItems:test];
}
}
...
See -enumerateKeysAndObjectsWithOptions:usingBlock:, and use the NSEnumerationConcurrent option.
You have two performance bootle necks:
You potentially recreate the MPMediaItemCollection instance once for each iteration, when only the last one created is needed.
-[NSDictionary enumerateKeysAndObjectsWithOptions:usingBlock:] is much faster when both the key and value of the enumerated dictionary is needed.
Change into something like this:
float currentFoundValue = 1000.0;
NSMutableArray *test = nil;
MPMediaItemCollection *collection;
float match;
[artistDictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent
usingBlock:^(id key, id obj, BOOL *stop)
{
NSString *thisArtist = key;
int suppliedCount = [stringValue length];
int keyCount = [thisArtist length];
if(suppliedCount > keyCount)
{
match = [StringDistance stringDistance:thisArtist :stringValue];
} else {
match = [StringDistance stringDistance:stringValue :thisArtist];
}
if(match < currentFoundValue)
{
currentFoundValue = match;
test = obj;
}
}];
collection = [[MPMediaItemCollection alloc] initWithItems:test];