I have created simple XCTest to test distinctUnionOfObjects. All the test cases are passing except one which is isKindOfClass (Last XCTAssertTrue). Any idea why it's changing the class when you do distinctUnionOfObjects.
- (void)testUsersPredicate
{
NSArray *usersBeforePredicate = [[self userData] users];
XCTAssertEqual([usersBeforePredicate count] , 34u, #"We need 34");
XCTAssertTrue([[usersBeforePredicate lastObject] isKindOfClass:[ICEUsersModelObject class]], #"Object is not ICEUsersModelObject class");
NSString *distinctUsersKeyPath = [NSString stringWithFormat:#"#distinctUnionOfObjects.%#", #"userName"];
NSArray* usersAfterPredicate = [usersBeforePredicate valueForKeyPath:distinctUsersKeyPath];
XCTAssertEqual([usersAfterPredicate count] , 30u, #"We need 30");
XCTAssertTrue([[usersAfterPredicate lastObject] isKindOfClass:[ICEUsersModelObject class]], #"Object is not ICEUsersModelObject class");
}
As the right key path on your distinctUnionOfObjects is userName, the -valueForKeyPath: call will return an NSArray of distinct userNames (not user objects).
From Apple's KVC Programming Guide:
The #distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
Change the last test case to check for [NSString class] and it should pass.
Alternatives
Using equality:
If the userNameproperty is supposed to serve as a unique identifier, you could enforce that by overriding -isEqual: and -hashon the user object to reflect this:
- (BOOL)isEqual:(id)object {
return ([object isKindOfClass:self.class] && [object.userName isEqual:self.userName]);
}
- (NSUInteger)hash {
return self.userName.hash;
}
This can benefit your overall model design and opens up a lot of additional options, like this one that obtains a collection of distinct users reg. userName in one line - NSSet is very fast when used for this:
NSArray *uniqueUsers = [[NSSet setWithArray:users] allObjects];
Note: I re-used the hashing function of NSString for the user hash, which has a subtle pitfall; -[NSString hash] only guarantees uniqueness for strings of up to 96 characters! This is not in the docs and took me almost a day to track down in production code. (see Apple's implementation of CFString.c - search for __CFStrHashCharacters)
Using NSPredicate:
Here's a, let's say, 'creative' solution that uses a predicate. However, some kind of iteration is needed, because the predicate condition would otherwise have to be a function of its own result:
NSMutableArray *__uniqueUsers = [NSMutableArray array];
[[users valueForKeyPath:#"#distinctUnionOfObjects.userName"] enumerateObjectsUsingBlock:^(id name, NSUInteger idx, BOOL *stop) {
NSArray *uniqueUser = [users filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id user, NSDictionary *bindings) {
return [user.userName isEqual:name];
}]];
if (uniqueUser.count > 0)
[__uniqueUsers addObject:uniqueUser.lastObject];
}];
NSArray *uniqueUsers = [NSArray arrayWithArray:__uniqueUsers];
It obtains a collection of unique userNames, iterates over it, selects exactly one user for each name and adds that to the output array.
You test has no sense. You can't use distinct in this way because as #mvanellen told you, you should change the class in NSString due the fact that you are searching for distinct username.
You instead are trying to get the list of objects considering distinct username, but it is conceptually wrong. You should try to get the list of the username if you want, but not of the entire objects.
Consider to have in your array:
ITEM 1: aav / otherValue1
ITEM 2: aav / otherValue2
ITEM 3: matteo / otherValue3
for sure the function would return ITEM 3, but being a distinct query, which from ITEM 1 and ITEM 2 should it takes?
Think about this ;)
Related
I have recently moved to Realm from Coredata. In my app I am showing 50K + contacts .
The contact object is in the format:
Contact: firstName, lastName ,company
I am trying to fetch all the contacts in the Realm , and I am trying to display those contacts similar to the native contacts app in iPhone.
First I am creating the section header titles based on the contact first name:
-(NSArray *)getSectionTitleBasedOn:(NSString*)sortBy{
RLMResults *results = [self getMainDataSetFromRealm];
ContactSource *contactSource = results.firstObject;
NSMutableDictionary *nameDic = [NSMutableDictionary dictionary];
for (RealmContact *contact in contactSource.contacts){
if (contact.firstName.length>0) {
if ([sortBy isEqualToString:#"FirstName"]) {
[nameDic setObject:#"firstletter" forKey:[contact.firstName substringToIndex:1]];
}
}
}
NSLog(#"dic %#",nameDic);
return [[nameDic allKeys]sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
}
This gets me an array of letters which represent the title of section.
Now I am preparing the datasource for each section, so for section A, I am fetching all the contacts that begin with letter 'A'
-(void)prepareDataSource:(NSArray *)titleArr{
RLMResults *results = [self getMainDataSetFromRealm];
ContactSource *contactSource = results.firstObject;
__block NSMutableDictionary *dataSource = [NSMutableDictionary dictionary];
[titleArr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *sectionHeader = obj;
RLMResults *contactResults = [contactSource.contacts objectsWhere:[NSString stringWithFormat:#"firstName BEGINSWITH '%#'",sectionHeader]];
NSMutableArray *contactRowArr = [NSMutableArray array];
for (Contact *contact in contactResults){
[contactRowArr addObject:contact];
}
[dataSource setObject:contactRowArr forKey:sectionHeader];
}];
_dataSource = [dataSource copy];
[self.tableView reloadData];
}
This works really well, but takes 3-5 seconds to load table which is fine but I am looking for ways to improve this data fetch .
Realm works on a principle of lazy-loading, where objects and their properties aren't loaded until you actually 'touch' them for the first time.
As a result, if you do any operations where you're manually iterating through all Realm objects in a results set at once, or manually copying specific objects to an array, you're going to incur a performance hit that will increase the more objects you persist in Realm.
The best way to minimize the performance hit is to try and mitigate how many times you iterate through the results sets and avoid copying objects out of the array as much as possible. RLMResults behaves like an array, so for most scenarios, you can usually just use that object instead.
In the prepareDataSource method, instead of looping through each object and passing them to that NSMutableArray, instead you could consider passing the RLMResults object itself instead.
The method getSectionTitleBasedOn: also seems quite inefficient since you're iterating through every single object in order to check if an entry with a particular first character exists. Instead, you could create an index of the alphabet, and then do a Realm query for entries that start with each letter, and then check to see if the resulting RLMResults object has a positive count (Though I'm not sure if this will actually be any faster).
But in the end, sometimes when you're doing complex sorting like this, where there's no 'clever' way to avoid iterating through each object in a database (Even Realm has to internally load each object when performing a sort), performance hits are unavoidable, in which case you should also make sure your UI has provisions to show a 'working' indicator to the user.
Without unintentionally killing performance, does this appear at first glance to be acceptable for perhaps 200 guid strings in one list compared for equality with 100 guid strings from another list to find the matching indexes.
I have a method signature defined like so...
-(NSArray*)getItemsWithGuids:(NSArray*)guids
And I wanted to take that passed in array of guids and use it in conjunction with this array...
NSArray *allPossibleItems; // Has objects with a property named guid.
... to obtain the indexes of the items in allPossibleItems which have the matching guids from guids
My first instinct was to try indexesOfObjectsPassingTest but after putting together the block, I wondered whether the iOS framework already offers something for doing this type of compare more efficiently.
-(NSArray*)getItemsWithGuids:(NSArray*)guids
{
NSIndexSet *guidIndexes = [allPossibleItems indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
SomeObjWithGuidProperty *someObject = obj;
for (NSString *guid in guids) {
if ([someObject.guid isEqualToString:guid]) {
return YES;
}
}
return NO;
}];
if (guidIndexes) {
// Have more fun here.
}
}
Since you're working with Objective-C (not Swift) check out YoloKit. In your case, you can do something like:
guids.find(^(NSString *guid){
return [someObject.guid isEqualToString:guid];
});
My thought would be to use a set -
-(NSArray*)getItemsWithGuids:(NSArray*)guids inAllObjects:(NSArray *)allObjects
{
NSSet *matchGuids=[NSSet setWithArray:guids];
NSMutableArray *matchingObjects=[NSMutableArray new];
for (SOmeObjectWithGuidProperty *someObject in allObjects) {
if ([matchGuids contains:someObject.guid]) {
[matchingObjects addObject:someObject];
}
}
return [matchingObjects copy];
}
Your code looks like it would have O(n^2) performance, which is bad. I think the solution of converting guids to an NSSet and then using NSSet's containsObject would likely be much more performant. You could rewrite your indexesOfObjectsPassingTest code to use an NSSet and containsObject pretty easily.
If order doesn't matter much, I would suggest to change data structure here. Instead of using NSArray, consider to use NSDictionary with guid as key and someObject as value. In this case, you should use -[NSDictionary objectsForKeys:notFoundMarker:] method to obtain objects.
It will work much faster, than enumeration trough 2 arrays. If the NSDictionary key have a good hash function, accessing an element, setting an element, and removing an element all take constant time. NSString has good hash.
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSArray *objectsAndNulls = [allPossibleItemsDictionary objectsForKeys:guids notFoundMarker:[NSNull null]];
if (objectsAndNulls) {
// Have more fun here.
// You should check that object in objectsAndNulls is not NSNull before using it
}
return objectsAndNulls;
}
UPD Unfortunately, there is no way to pass nil as notFoundMarker. If you can't provide usable notFoundMarker value and don't want to perform additional checks, you can query objects one by one and fill NSMutableArray. In this case you will avoid pass trough array to remove NSNulls:
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:guids.count];
for (NSString *guid in guids) {
SomeObjWithGuidProperty *object = allPossibleItemsDictionary[guid];
if (nil != object) {
[objects addObject:object];
}
}
if (nil != objects) {
// Have more fun here.
}
return object;
}
I have the following scenario:
I have two collection classes (could be NSArray, NSMutableArray, NSSet, NSOrderedSet or whatever would be best suited for this case), which hold unique objects of the same type (unique in the sense that for all objects in the collections for no two elements the isEqual method would return true).
Lets say the first collection instance holds the following objects (1,2,3,4,5) and the second one (2,3,4,6,7). Now I need a method that returns the difference between the two collections, with the extra info what exactly the difference from each collection was.
An example result for the example would be: (1,5) was removed from the first collection and (6,7) added two the second collection.
I know if I use the NSMutableArray with a sorted list and decide which list has more elements than the other, I could use removeObjectsInArray to get a list of the different objects (like described in Compare two arrays with the same value but with a different order or in How to compare and remove common objects( NSDictionaries) from 2 NSMutableArray?), but don't really know which objects was in which collection. I could create a temporary collection and put the result of removeObjectsInArray in that array and compare the other two initial arrays with the temporary array. Seems little verbose though. Is there a better way that I don't know of?
I found a much slicker way for you to do what you want by using NSPredicate. When I run the following code:
NSArray *firstArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7", nil];
NSArray *secondArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"8",nil];
NSArray *itemsMissingFromSecondArray = [firstArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT SELF IN %#", secondArray]];
NSArray *itemsMissingFromFirstArray = [secondArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT SELF IN %#", firstArray]];
NSLog(#"itemsMissingFromFirstArray=%#\nitemsMissingFromSecondArray=%#", itemsMissingFromFirstArray, itemsMissingFromSecondArray);
I get the following output showing what was missing from each array that was in the other array:
itemsMissingFromFirstArray=(
8
)
itemsMissingFromSecondArray=(
4,
5,
6,
7
)
Less code than sorting and merging, doesn't use a bunch of temporary arrays, and simple enough to read.
NOTE: If someone also wants to know the items that are in both arrays, the solution is similarly simple:
NSArray *itemsFoundInBothArrays = [firstArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF IN %#", secondArray]];
I think if you want to know the difference, you can make use of NSMutableSet and the minusSet function.
[set1 minusSet:set2];
will give you the elements in set1 but not in set2 straight away. So you don't need any temp collection and compare with original collection again.
Otherwise, if you only want to remove the elements, you can make use of NSArray and do sth like:
[secondArray removeObjectsInArray:firstArray];
Edited:
To find all the diff in one shot:
[ [set1 unionSet:set2] minusSet: [set1 intersectSet:set2] ];
+ (NSArray *) removeObjectsFromArray :(NSArray *)arrayToRemoveFrom thatAreAlsoIn:(NSArray *)arrayOfItemsToRemove
{
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:arrayToRemoveFrom];
[newArray removeObjectsInArray:arrayOfItemsToRemove];
return newArray;
}
+(void) findArrayDifferences
{
NSArray *bigArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7", nil];
NSArray *smallArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"8",nil];
NSArray *itemsInBigArrayThatAreNotInSmallArray = [self removeObjectsFromArray:bigArray thatAreAlsoIn:smallArray];
NSArray *itemsThatAreInBothArrays = [self removeObjectsFromArray:bigArray thatAreAlsoIn:itemsInBigArrayThatAreNotInSmallArray];
NSArray *itemsInSmallArrayThatAreNotInBigArray = [self removeObjectsFromArray:smallArray thatAreAlsoIn:itemsThatAreInBothArrays];
NSLog(#"itemsInBigArrayThatAreNotInSmallArray=%#\nitemsThatAreInBothArrays=%#\nitemsInSmallArrayThatAreNotInBigArray=%#", itemsInBigArrayThatAreNotInSmallArray, itemsThatAreInBothArrays, itemsInSmallArrayThatAreNotInBigArray);
}
This results in the following output:
itemsInBigArrayThatAreNotInSmallArray=(
4,
5,
6,
7
)
itemsThatAreInBothArrays=(
1,
2,
3
)
itemsInSmallArrayThatAreNotInBigArray=(
8
)
I have an NSArray of custom objects and would like to filter down that array to be unique on a specific key. Most of the things I've seen while searching for an answer involve using valueForKey:, valueForKeyPath: or #distinctUnionOfObjects but those return arrays of values for that key. I want the whole object instead.
The objects are subclassed PFObjects from Parse so they are KVC compliant, and I would like them to be filtered on the objectId key.
Put this in a category on NSArray:
-(NSArray*)arrayFilteredForUniqueValuesOfKeyPath:(NSString*)keyPath
{
NSMutableSet* valueSeen = [NSMutableSet new];
return [self filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
id value = [evaluatedObject valueForKeyPath:keyPath];
if(![valueSeen containsObject:value])
{
[valueSeen addObject:value];
return true;
}
else
{
return false;
}
}]];
}
Of course, the concept is kind of flawed since you really have no way of determining which of the n objects that have any give value for the keyPath you really wanted (in this case you get the first one)
Let's say I have an NSArray of NSDictionaries that is 10 elements long. I want to create a second NSArray with the values for a single key on each dictionary. The best way I can figure to do this is:
NSMutableArray *nameArray = [[NSMutableArray alloc] initWithCapacity:[array count]];
for (NSDictionary *p in array) {
[nameArray addObject:[p objectForKey:#"name"]];
}
self.my_new_array = array;
[array release];
[nameArray release];
}
But in theory, I should be able to get away with not using a mutable array and using a counter in conjunction with [nameArray addObjectAtIndex:count], because the new list should be exactly as long as the old list. Please note that I am NOT trying to filter for a subset of the original array, but make a new array with exactly the same number of elements, just with values dredged up from the some arbitrary attribute of each element in the array.
In python one could solve this problem like this:
new_list = [p['name'] for p in old_list]
or if you were a masochist, like this:
new_list = map(lambda p: p['name'], old_list)
Having to be slightly more explicit in objective-c makes me wonder if there is an accepted common way of handling these situations.
In this particular case Cocoa is not outdone in succinctness :)
NSArray *newArray = [array valueForKey:#"name"];
From the NSArray documentation:
valueForKey:
Returns an array containing the
results of invoking valueForKey: using
key on each of the receiver's objects.