I have an NSArray of NSStrings and would like to know how to compare each item in the array with every other item in the array to see if there is any strings different from the rest.
I have seen a c++ example
for (int i = 0; i < list.size(); i++) {
for (int j = i+1; j < list.size(); j++) {
// compare list.get(i) and list.get(j)
}
}
but was woundering if there is a better easier way in objective C? also the other thing I need to do is make sure the item doesn't compare itself while it loops through.
Any help or examples would be greatly appreciated.
UPDATE ** BOLD is the updated part of the question **
If I read your question correctly, you want the strings that only appear once in the list, correct?
NSCountedSet *counted = [NSCountedSet setWithArray:list];
for (NSString *string in counted) {
NSUInteger count = [counted countForObject:string];
if (count == 1) {
// process "string", it appears in the list just once
}
}
If you just want to know if there is more than one different value in the list then do this:
NSSet *set = [NSSet setWithArray:list];
if (set.count == 1) {
// There is only one distinct value in the list
} else {
// There is more than one distinct value in the list
}
I'd use an NSMutableDictionary. This is very similar to "merging two lists into unique values", the apple docs actually explain the complicated way somewhere. I forgot where I found it, but the easy way is here: Merge two arrays while preserving the original array order
So what you'd do is loop through everything, see if there's a key (set to the string), if not, add one via the setObject: forKey: method, then enumerate through the dictionary or just grab the allKeys value after.
Use two sets. If the string goes into the first set without conflict, add it to the second set. If the string encounters a conflict in the first set, remove it from the second set. When you've processed all the strings the second set contains the unique ones.
Related
I have a list of contacts retrieved from Address book stored inside a MutableArray contactList. Each contact is an object which has properties like "contactName, contactImage.... etc".
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
//getAllContacts is a method which returns a Mutable array of Objects
self.contactList = [NSMutableArray arrayWithArray:[instance getAllContacts]];
//groupLetterToLoad could be "DEF"
for(int j=0; j<self.groupLetterToLoad.length;j++) {
//1st iteration D, 2nd iteration E and 3rd iteration F
NSString *testChar = [NSString stringWithFormat:#"%c",[self.groupLetterToLoad characterAtIndex:j]];
//check D,E,F with contact name property's first letter of the contact list array
for(int i=0;i<self.contactList.count;i++) {
NSString *firstChar =[[[self.contactList objectAtIndex:i] contactName] substringToIndex:1];
if([testChar isEqualToString: firstChar]) {
pos=i; //retrieve the index of the matched position
break;
}
}
if(pos!=-1) break;
}
});
Now this has two for loops (Time O(n^2)).. The disadvantage here is, if the groupLetterToLoad is "WXYZ", then comparison will start from W with A to W with Z.. How can I optimise it?
Ordering your array by contactName and performing a half interval search will reduce your complexity greatly if can avoid sorting every time you search (hint: keep [instance getAllContacts] sorted).
http://rosettacode.org/wiki/Binary_search#Objective-C - that's a starting point. you could replace the compare: with your first character comparison.
This isn't an algorithmic improvement, but the way you're handling characters is about the slowest way possible. If your group letters are really ASCII letters as you indicate, try this (I include the "if" in my answer because doing correct comparison of non-ASCII is really best left up to NSString):
1) Instead of using -substringToIndex to get the first character, use -characterAtIndex:0 and store a unichar
2) Instead of using +stringWithFormat:#"%c" to make a single character string, just use -characterAtIndex: and store it in a unichar
3) Instead of using -isEqualToString:, use == on the unichars
Unrelated, I'm pretty suspicious of the thread-safety of this. Are all those properties on self and instance you're accessing really not accessed on any other queue or thread?
I've been trying to logically think about this for a while now and usually I can solve it by writing it out or thinking about it while doing other stuff not associated with programming. However everything I try isn't working. So basically I have 2 NSArrays, which are both populated from 2 different plists. Each array is filled with all dictionaries: all have the same keys, and all have the same identical structure, but some may have information associated with a key that some don't. I basically need to check each item in array1 and see if it exists in array2 and if it does NOT exist in array2 then add it to array1 (or array3 seeing how you can't mutate an array while iterating through it). I need it to see if it exists by a specific key "name_key".
So... In short my end result needs to be an NSArray with all objects from both arrays without having objects with duplicate names (from the dictionaries name_key).
Below is my actual code that doesn't work.
IN CODE BELOW: originalWhiskiesListArray = array1
newWhiskiesListArray = array2
combinedWhiskiesListArray = array3 (because you can't mutate an array while iterating through it.
BOOL whiskyExists = YES;
for (NSDictionary *newWhisky in newWhiskiesListArray) {
for (NSDictionary *originalWhisky in originalWhiskiesListArray) {
NSString * newWhiskyNameString = [[newWhisky objectForKey:NAME_KEY] lowercaseString];
NSString * origWhiskyNameString = [[originalWhisky objectForKey:NAME_KEY] lowercaseString];
//Compare lowercase strings and if they don't match then add them to the original plist array.
if ([newWhiskyNameString isEqualToString:origWhiskyNameString]) {
whiskyExists = YES;
break;
} else {
whiskyExists = NO;
break;
//NSLog(#"Whisky names do not match. New Whisky: %# Old Whisky: %#",[newWhisky objectForKey:NAME_KEY],[originalWhisky objectForKey:NAME_KEY]);
//doesn't match so add it
}
}
if (whiskyExists == NO) {
[combinedWhiskiesListArray addObject:newWhisky];
NSLog(#"newWhisky added");
whiskyExists = YES;
}
}
Can either of the whiskey name strings be nil? If so then this breaks the isEqualToString comparison because you can always message nil which returns NO.
Also I believe that the breaks are wrong. You only need to exit the inner loop in case you encounter a match. If not you have to keep going until the end of the inner loop.
If I'm understanding you correctly, you can just add all the values from each dictionary to an NSMutableSet, which won't add an item if it already exists in the set. You can then convert the set back to an array with the NSSet method allObjects.
This is a question about iOS programming with Objective C.
I have an NSMutableArray of strings "csvContent", that were parsed from a CSV file that contained a pseudo-database of questions, answers, and keywords. The contents of the CSV file were as follows:
ID#, "Here is the question I am asking?", "[question, key, words]", "This is the answer to your question."
There are about 2,000 of these questions and related keywords and answers, and I have successfully parsed them into the array, line by line so that each element contains everything in the example you see above.
My question is that if I want to have a user ask a question in a UITextField and then compare the UserQuestion and find the most similar question in my array of strings and then return its answer, what would be the best way to go about doing so? I've looked through documentation about Levenshtein distance and think that that would be a good option, but don't know how to exactly implement it and have it iterate through my entire CSVContent array. I'm not looking for exact code, but an ideal answer would contain some pseudocode or methodology on how to go about this.
To summarize:
Array of strings, CSVContent, of appearance: [id,"question",("question keywords"),"answer"].
I have a UITextField where I can parse a user's entered question into a string UserQuestion.
I want to use a fast comparison algorithm (Levenshtein?) to compare UserQuestion to the elements inside CSVContent and find the appropriate question and related answer, then return the answer.
When user hits the Search button, pass textField.text to this method:
- (int)matchingIDForString:(NSString *)userSuppliedText {
NSInteger bestLevDistanceSoFar = 9999999;
int indexOfMatch=-1;
// having to search the entire array each time is, of course, scary.
for ( int j=0; j<[myMutableArray count]; j++ ) {
NSString *candidateAnswer = [myMutableArray objectAtIndex:j];
// if candidateAnswer is a string, just use it. Else extract the member you want...
NSInteger *levDistance = [self myLevensteinDistanceMethod:candidateAnswer
forstring:userSuppliedText];
if ( levDistance < bestLevDistanceSoFar ) {
indexOfMatch = j;
bestLevDistanceSoFar = levDistance;
}
}
return indexOfMatch; // called should test for <0 meaning no match
}
You'll need to implement this method also:
- (NSInteger *)myLevensteinDistanceMethod:(NSString *)string1 forString:(NSString *)string2 {
// calculate the lev distance, and return it as an NSInteger *.
}
I have got an array, I know how to count its elements, but I need to count elements until a specific word:
NSMutableArray *whatBondInFrame;
whatBondInFrame=[NSMutableArray arrayWithObjects:#"red",#"red",#"red",#"gray",#"red",#"ran",#"gray",#"gray",nil];
I know [ whatBondInFrame count] but, let's say I want to know how many elements I have till the first gray or from the word "ran".
How would I get that?
This isn't tested but it should work:
int loc = 0;
for (loc; loc < [array count]; loc++) {
NSString *str = [array objectAtIndex:loc];
if ([str isEqualToString:#"ran"])
break;
}
int length = array.count-loc;
this gives you the count from the first element named ran.
If you want to know how many elements there are before (till) the word 'ran' then replace the last line with
int length = loc
The NSArray method:
- (NSUInteger)indexOfObject:(id)anObject
Will return the index of the first occurrence on an object, so you can do:
NSUInteger firstRanIndex = [whatBondInFrame indexOfObject:#"ran"];
There is a companion method:
- (NSUInteger)indexOfObject:(id)anObject inRange:(NSRange)range
Which restricts the search to a given range of the array. There is no method to find the last occurrence, for that you must loop with the above methods.
In conjunction with the count method you can get the numbers you want.
I have two arrays, each containing strings. The first array is a list of words, the second array contains alternatives to those words in different languages.
The arrays are matched such that the word at index n in the second array is a translation of the word at index n in the first array.
The words and their translations are displayed in a table view. The user can filter the table view by entering text in a search field. When this is done, I create a filtered array from the first array like this:
- (void)filterContentForSearchText:(NSString*)searchText
[self.filteredarray removeAllObjects];
[firstarray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])] == NSOrderedSame)
{
idx= [firstarray indexOfObjectIdenticalTo:obj];
NSUInteger maxindex = idx + 50;
for (idx ; (idx < [firstarray count] && idx <= maxindex && idx!= NSNotFound); idx ++)
{
[self.filteredarray addObject:[firstarray objectAtIndex: idx]];
}
*stop = YES;
}
}];
Then, when I am displaying the values in my table view, I use the following code. This is an exerpt from my cellForRowAtIndexPath method. I am trying to get the index from the original array using the object that has been added to the filtered array.
contentForThisRow = [self.filteredarray objectAtIndex:row];
NSUInteger index = [self.firstarray indexOfObjectIdenticalTo:contentForThisRow];
contentForThisRow2 = [self.secondarray objectAtIndex:index];
This works on the simulator, but on the device I will sometimes get repeats of the same entry from the second array. For example, my first array contains the word "hello" three consecutive times, at indexes x, y and z. My second array contains "hei", "heisan" and "hoppsan", which are all translations of "hello", at indexes x, y and z.
On the simulator, I get three cells, each with a different translation. On the device, I get three cells, all with "hei", the first translation. This does not happen for all repeated translations.
Why is this happening, and how can I get around it?
I think the problem is that iOS (on the device) may be using a slightly different optimisation to the emulator somewhere, either in NSString or NSArray. That is a guess.
indexOfObjectIdenticalTo: returns the index of the first object that has the same memory address as the object you are passing in. On the phone it appears to have re-used the identical string objects in your first array when building the filtered array (possibly even when building firstArray), so you are getting the same index value back each time.
A better solution would be to build your filtered array as an array of dictionaries, storing the values from the correct indexes of firstArray and secondArray at that point. You can then use these values directly when populating the cell instead of searching through both arrays again. This should also have some performance benefits.
You would achieve this using the following code. First, inside your loop when you are building the filtered array, instead of adding the object from firstarray, do this:
[self.filteredArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:[firstarray objectAtIndex:idx],#"english",[secondarray objectAtIndex:idx],#"translated",nil];
Then, in your cellForRowAtIndexPath, to get your two content variables:
NSDictionary *rowData = [self.filteredarray objectAtIndex:row];
contentForThisRow = [rowData objectForKey:#"english"];
contentForThisRow2 = [rowData objectForKey:#"translated"];
An even better solution would be to hold your data like this in the first place, and not try to keep two separate arrays synchronised. I imagine if you want to add or alter anything in your two separate files you could quickly get them out of step. However, I feel I've done enough for the day...
else
contentForThisRow = [self.firstarray objectAtIndex:row];
contentForThisRow2 = [self.secondarray objectAtIndex:row];
You see anything wrong with that?