iOS indexOfObjectIdenticalTo: issue - objective-c

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?

Related

Check duplicate property values of objects in NSArray

I have an NSArray containing objects with a size property.
How can I check if the NSArray has two objects with the same value for size?
Can I do something like:
int i = 0;
for (id item1 in myArray) {
NSDecimalNumber *size1 = [item1 size];
for (id item2 in myArray) {
NSDecimalNumber *size2 = [item2 size];
if ([size1 isEqual:size2]) {
i ++;
}
}
}
if (i > [myArray count]) {
NSLog(#"Duplicate Sizes Exist");
}
Or is there an easier way?
Try this code:
NSSet *myset = [NSSet setWithArray:[myarray valueForKey:#"size"]];
int duplicatesCount = [myarray count] - [myset count];
size here is the object property.
Use NSCountedSet. then add all your objects to the counted set, and use the countForObject: method to find out how often each object appears in your array.
You can check this link also how-to-find-duplicate-values-in-arrays
Hope it helps you
Probably simplest is to sort the array based on the size field and then step through the sorted list looking for adjacent dupes.
You could also "wrap" each object in one that exports the size as its key and use a set. But that's a lot of extra allocations.
But if you only want to know if dupes exist, and not which ones they are, create an NSNumber for each object's size and insert the NSNumbers in a set. The final size will tell you how many dupes.
NSArray *cleanedArray = [[NSSet setWithArray:yourArraywithDuplicatesObjects ] allObjects];
Use Sets this will remove all duplicates objects.Will return NSArrayNSCountedSet and use countForObject: method to find out how often each object appears how many times.

Adding items from 1 array 2 another filtered by dictionary key

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.

compaire NSArray items with every other item in the NSArray

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.

objective-c changing array contents inside for loop

I have an issue that (I think) might have to do with scope, but I'm not sure. I'm trying to do something that I think should be simple, but I am getting a strange result, and I could truly use some advice. I would say I'm an early-objective-c programmer, but not a complete newb.
I have written a function in objective-c that I would like to use to change the key-names in a mutable array of mutable dictionary objects. So, I want to pass in a mutable array of mutable dictionary objects, and return the same mutable array with the same dictionary objects, but with some of the key-names changed. Make sense?
I have tried several log statements in this code, which seem to indicate that everything I'm doing is working, except when the for loop is finished executing (when I try to test the values in the temp array), the array appears to contain only the LAST element in the source array, repeated [source count] times. Normally, this would lead me to believe I'm not writing the new values correctly, or not reading them correctly, or even that my NSLog statements aren't showing me what I think they are. But might this be because of scope? Does the array not retain its changes outside of the for loop?
I have put a fair amount of time into this function, and I have exhausted my bag of tricks. Can anyone help out?
-(NSMutableArray *)renameKeysIn:(NSMutableArray*)source {
/*
// Pre:
// The source array is an array of dictionary items.
// This method renames some of the keys in the dictionary elements, to make sorting easier later.
// - "source" is input, method returns a mutable array
*/
// copy of the source array
NSMutableArray *temp = [source mutableCopy];
// a temporary dictionary object:
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
// These arrays are the old field names and the new names
NSMutableArray *originalField = [NSMutableArray arrayWithObjects:#"text", #"created_at",nil];
NSMutableArray *replacedField = [NSMutableArray arrayWithObjects:#"title", #"pubDate", nil];
// loop through the whole array
for (int x =0; x<[temp count]; x++) {
// set the temp dictionary to current element
[dict setDictionary:[temp objectAtIndex:x]];
// loop through the number of keys (fields) we want to replace (created_at, text)... defined in the "originalField" array
for (int i=0; i<[originalField count]; i++)
{
// look through the NSDictionary item (fields in the key list)
// if a key name in the dictionary matches one of the ones to be replaced, then replace it with the new one
if ([dict objectForKey:[originalField objectAtIndex:i]] != nil) {
// add a new key/val pair: the new key *name*, and the old key *value*
[dict setObject:[dict objectForKey:[originalField objectAtIndex:i]]
forKey:[replacedField objectAtIndex:i]];
// remove the old key/value pair
[dict removeObjectForKey:[originalField objectAtIndex:i]];
}// end if dictionary item not null
}// end loop through keys (created_at, text)
[temp replaceObjectAtIndex:x withObject:dict];
}// end loop through array
// check array contents
for (int a=0; a<[temp count]; a++){
NSLog(#"Temp contents: ############ %#",[[temp objectAtIndex:a] objectForKey:#"pubDate"]);
}
return temp;
} // end METHOD
I think the issue is on the line with:
[dict setDictionary:[temp objectAtIndex:x]];
Since these things are almost all working in pointers (instead of copying contents), every element of your temp array will point to the dict dictionary, which is set to be whatever the latest key's dictionary is. I think setting the actual pointer will fix the issue.
dict = [temp objectAtIndex:x];

NSMutableArray cannot remove duplicates

I have duplicates in my array and i want to get rid of them, so i run this loop, however it doesn't work. Any one know why?
The array currently has 3 items, 2 duplicates and 1 unique.
for (int x = 0; x <= [array count]; x++) {
if(x > 0){
if([[array objectAtIndex:x - 1] isEqualToString:[array objectAtIndex:x]]){
[array removeObjectAtIndex:x];
}
}
}
You can't iterate over an object and modify it at the same time. Once you remove an object, the indexes of all the objects change. You can try copying the array first and iterate that and make the modifications in the original array, but you still might have to change some of your logic depending on what you're trying to accomplish.
Your algorithm only ever compares items that are next to each other in the array (the items at positions x and x-1). If the duplicates are in any other positions, they won't be found.
The naïve way to fix this is to do a double loop. Compare each item in the array with every item after it. This will start taking an extremely long time as your array becomes bigger.
The correct way to do this is to let the framework handle the operation. Convert your array to a set (which does not have duplicates by definition) and then back to an array:
NSSet * s = [NSSet setWithArray:array];
NSArray * dedupedArray = [s allObjects];
If you need to preserve the order, you'll have to do this in a slightly roundabout way, although this is still faster than the double-loop:
NSMutableSet * itemsSeen = [NSMutableSet set];
NSMutableArray * dedupedArray = [NSMutableArray array];
for( id item in array ){
if( ![itemsSeen containsObject:item] ){
[itemsSeen addObject:item];
[dedupedArray addObject:item];
}
}
I would suggest simply using NSSet ( or NSMutableSet ). It will automatically ensure you have only one of every object.
BUT - notice it is one of every OBJECT. It can have 2 objects that are different but have the same inner value.
If you want to ensure that there are no duplicates in your array, it would be better to use an NSMutableSet rather than an NSMutableArray.
NSMutableSet maintains the invariant that every object in the set is unique.
For example:
NSMutableSet* set = [NSMutableSet set];
NSString* data = #"Data";
[set addObject:data];
[set addObject:data];
The second call to addObject: will do nothing as data is already in the set.