I have an NSObject with NSStrings inside. How do I add objects with unique obj.name only to an NSMutableArray? I tried NSOrderedSet but it only works if you add NSString to an array and not objects with NSString inside.
Example.
##interface MyObject : NSObject
#property (strong, nonatomic) NSString *name;
#end
NSMutableArray *array = {MyObject.name,MyObject.name,MyObject.name};
How do I make sure that no two MyObjects have the same name?
Use NSPredicate for seraching object in NSMutableArray if not present then add it to NSMutableArray.
Try this.
NSArray * filtered = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"name = %#", #"MyObject.name"]];
if(![array count])
[array addObject:MyObject ];
All NSSet classes use isEqual: in combination with hash: to compare equality.
Because you have not redefined these simply storing two objects with the same name in a set will be possible as the NSObject implementation of isEqual: and hash: will be used.
The documentation of NSObject Protocol talks about overriding isEqual and hash.
This previous answer on Stackoverflow details how to implement hash and isEqual correctly.
In your own implementation of hash you can use NSString's hash method.
Example
- (NSUInteger) hash {
NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [super hash];
result = prime * result + self.name == nil ? 0 : [self.name hash];
return result;
}
- (bool) isEqual:(id)other {
if (other == self) {
return YES;
}
if (!other || ![other isKindOfClass:[self class]]) {
return NO;
}
return [self.name isEqualToString:other.name];
}
Personally, I would use a NSMutableDictionary with MyObject.name as the key. That way all you would have to do is this:
if( myDictionary[MyObject.name] == nil )
{
myDictionary[MyObject.name] = MyObject;
}
Much more efficient than using a regular NSMutableArray based on the number of additions you are doing. Plus if you want to get access to an array of all the values, all you have to do is:
NSArray *array = [myDictionary allValues];
The Big-O Runtime for NSPredicate is O(n) where the dictionary method is O(1).
Related
NSMutableArray containsObject returns true even the address and data is different.
I've seen this post NSMutableArray containsObject returns true, but it shouldnt
already but still I'm not finding my solution:
Below is my scenario:
NSMutableArray *destClasses = [NSMutableArray array];
id sourceClasses = [dict objectForKey:#"Classes"];
if ([sourceClasses isKindOfClass:[NSArray class]]) {
for (NSDictionary *class in sourceClasses) {
MyClass *a = [[MyClass alloc] init];
[a arrangeClassWithDictionary:classDict]; //this methods assigns value to a from classDict
if (![destClasses containsObject:a]) {
[destClasses addObject:a];
}
}
}
In the first iteration destClasses adds an MyClass object and on the second iteration [destClasses containsObject:a] returns true even though the a has different address and different values assigned.
What I'm doing wrong here. Please help.
I got the answer.
containsObject: which sends the isEqual: message to every object it
contains with your object as the argument. It does not use == unless
the implementation of isEqual: relies on ==.
I've to override the isEqual: method to provide equality checking for my object fields like below,
- (BOOL)isEqual:(id)object
{
BOOL result = NO;
if ([class isKindOfClass:[self class]]) {
MyClass *otherObject = object;
result = [self.name isEqualToString:[otherObject name]];
}
return result;
}
NSArray *objects = #[object1, object2, object3, object1, object5, object1, object6, object7];
How would be best to get the ranges delimited by object1 like this:
(0-2 3-4 5-7)
I know of this method to create a NSIndexSet from an array, but im not sure how i could use it to achieve the above index set.
NSIndexSet*indexSet = [objects indexesOfObjectsPassingTest:^(id object, NSUInteger idx, BOOL *stop) {
return ; //Do some checks
}];
Using objects.count may also be useful for the final range, but I am unsure of the best way of doing this.
An NSIndexSet won't really help, since you are after ranges, not indices.
You could simply iterate over the array looking for your sentinel object object1, adding instances of NSRange to an array.
Except, that NSRange is a struct, so you can't add instances directly to NSMutableArray. There are a few options:
(And this would be my first choice): Use Swift instead of Objective-C
Create a custom object to wrap NSRange
Use NSValue to wrap NSRange
Use a C array rather than NSArray
I am going to use option 2, since option 3 involves messing around with the raw bytes of the underlying struct.
Range.h
#interface Range : NSObject
#property (readonly) NSRange range;
- (id) initWithRange: (NSRange) range
#end
Range.m
import "Range.h"
#implementation Range
- (id) initWithRange: (NSRange) range {
if (self = [super init]) {
_range = range;
}
return self;
}
#end
YourCode.m
-(NSArray *) getRanges:(NSArray *)objects delimeter: (NSObject *)object1
{
int rangeStart = -1;
NSMutableArray *ranges = [[NSMutableArray alloc]init];
int index;
for (index=0; index<objects.count;index++) {
if (objects[index] == object1) {
if (rangeStart != -1) {
Range *range = [[Range alloc] initWithRange:NSMakeRange(rangeStart,index-rangeStart)];
[ranges addObject:range];
}
rangeStart = index;
}
}
if (rangeStart != -1) {
Range *range = [[Range alloc] initWithRange:NSMakeRange(rangeStart,index-rangeStart)];
[ranges addObject:range];
}
return ranges;
}
I have an NSArray of Object Classes which consist of two textfields. I would like to sort these objects in ascending order, I have done this with NSDictionary objects before however I have now changed then to an Object Class that I have made so I dont really know how to compare the values to get the sorted array.
The object variables are NSNumbers but contain only number values, which I think will effect things.
This is how I was sorted the NSDictionary value with my old code.
NSArray *tempSortedItemsArray = [installArray sortedArrayUsingDescriptors:
#[[NSSortDescriptor
sortDescriptorWithKey:#"first" ascending:YES],
[NSSortDescriptor
sortDescriptorWithKey:#"second" ascending:YES]]];
sortedItemsArray = [tempSortedItemsArray mutableCopy];
tempSortedItemsArray = nil;
So if I have an array of object like this
(first / second)
2 1
0 0
1 0
1 1
2 2
2 0
3 0
if would sort like this
0 0
1 0
1 1
2 0
2 1
2 2
3 0
any help adjusting this for NSObject class with NSNumber variables first and second would be greatly appreicated.
[arr sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString *first=[NSString stringWithFormat:#"%d%d",obj1.num1.intValue,obj1.num2.intValue];
NSString *second=[NSString stringWithFormat:#"%d%d",obj2.num1.intValue,obj2.num2.intValue];
return [first compare:second options:NSNumericSearch];
}];
You can use the other sort methods of NSArray such as:
sortedArrayUsingFunction:context:
sortedArrayUsingSelector:
sortedArrayUsingComparator:
Given an Object that works like this:
#interface Object : NSObject
#property (copy) NSNumber *first;
#property (copy) NSNumber *second;
+(Object *)objectWithWithFirst:(NSNumber *)first second:(NSNumber *)second;
#end
#implementation Object
+(Object *)objectWithWithFirst:(NSNumber *)first second:(NSNumber *)second {
Object *object = [[Object alloc] init];
object.first = first;
object.second = second;
return object;
}
-(NSString *)description {
return [NSString stringWithFormat:#"%# %#", _first, _second];
}
#end
Your could sort with
NSArray *sorted = [array sortedArrayUsingComparator:^NSComparisonResult(Object *obj1, Object *obj2) {
NSComparisonResult result = [obj2.second compare:obj2.second];
if (result == NSOrderedSame)
result = [obj1.first compare:obj2.first];
return result;
}];
Example:
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *array = #[
[Object objectWithWithFirst:#2 second:#1],
[Object objectWithWithFirst:#0 second:#0],
[Object objectWithWithFirst:#1 second:#0],
[Object objectWithWithFirst:#1 second:#1],
[Object objectWithWithFirst:#2 second:#2],
[Object objectWithWithFirst:#2 second:#0],
[Object objectWithWithFirst:#3 second:#0]
];
NSLog(#"%#", array);
NSArray *sorted = [array sortedArrayUsingComparator:^NSComparisonResult(Object *obj1, Object *obj2) {
NSComparisonResult result = [obj2.second compare:obj2.second];
if (result == NSOrderedSame)
result = [obj1.first compare:obj2.first];
return result;
}];
NSLog(#"%#", sorted);
}
return 0;
}
Which will work perfectly on your sample data. Of course, you could put the comparator code directly into your Object class - cleaning thins up even more.
If your custom class has the properties first and second then the code you've posted will just work.
NSNumbers already know how to compare themselves to other numbers. NSSortDesvriptors work with any object that implements the reading part of key-value coding. All NSObject subclasses automatically do so for any property that follows ordinary conventions. #propertys follow ordinary conventions.
NSArray *arrClient = [[NSArray alloc] initWithObjects:#"record 1", #"record 2", nil];
NSArray *arrServer = [[NSArray alloc] initWithObjects:#"record 1", #"record 3", nil];
On arrServer I would like to apply predicates to filter only those entries that DON'T already exist in arrClient. e.g. in this case record 1 exist in both arrays and shall be ignored, hence only an array with one entry with the "record 3" string shall be returned.
Is this possible?
UPDATE
The answers below are great. I believe I need to give a real example to verify if what I am doing makes sense after all. (I am still giving a compact version below)
Now the clientItems will be of type FTRecord (Core Data)
#interface FTRecord : NSManagedObject
...
#property (nonatomic) NSTimeInterval recordDate;
#end
#implementation FTRecord
...
#dynamic recordDate;
#end
This class below is a holder for parsing json from a REST service. Hence the serverItems we mentioned earlier will be of this type.
#interface FTjsonRecord : NSObject <JSONSerializable>
{
}
#property (nonatomic) NSDate *recordDate;
#implementation FTjsonRecord
- (NSUInteger)hash
{
return [[self recordDate] hash];
}
- (BOOL)isEqual:(id)object
{
if ([object isKindOfClass:[FTjsonRecord self]]) {
FTjsonRecord *other = object;
return [[self recordDate] isEqualToDate:[other recordDate]];
}
else if ([object isKindOfClass:[FTRecord self]]) {
FTRecord *other = object;
return [[self recordDate] isEqualToDate:[NSDate dateWithTimeIntervalSinceReferenceDate:[other recordDate]]];
}
else {
return NO;
}
}
Going with Wain's example, this seems to work fine. Now is this feasible?
Keep in mind that serverItems are just temporary and only used for syncing with server, and will be thrown away. clientItems is the one that remains in place.
UPDATE 2:
This time I am trying Manu's solution:
I have created this method on my Client DBStore, which is called by the predicate.
The reason I can't use containsObject is because the class types in serverItems and clientItems are not the same type.
-(BOOL)recordExistsForDate:(NSDate *)date
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"recordDate == %#", date];
NSArray *arr = [allRecords filteredArrayUsingPredicate:predicate];
if (arr && [arr count] > 0) {
return YES;
} else {
return NO;
}
}
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(FTjsonRecord *evaluatedObject, NSDictionary *bindings) {
return ![store recordExistsForDate:[evaluatedObject recordDate]];
}];
NSSet *set = [[serverRecords items] filteredSetUsingPredicate:predicate];
What worries me about this solution though, is the linear read from my clientItems (allRecords). I am not sure how efficient it is using the predicate on the array, wonder if there is a better way to achieve this.
You can use NSSet to get the union, intersection and difference (minus) with other sets. This more accurately matches what you're trying to do.
NSMutableSet *serverItems = [[NSMutableSet alloc] init];
[arrServerItems addObjectsFromArray:arrServer];
NSSet *clientItems = [[NSSet alloc] init];
[clientItems addObjectsFromArray:arrClient];
[arrServerItems minus:clientItems];
This does remove the ordering information though.
For predicates you can use:
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:#"NOT (SELF IN %#)", arrClient];
depend to the predicate that you want to use:
you can use an array of arguments using this
[NSPredicate predicateWithFormat:<#(NSString *)#> argumentArray:<#(NSArray *)#>];
and build your predicate using the objects in the array
or use a predicate with block
[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
<#code#>
}]
and in the block evluate the object comparing it with the object in your array
a possible check you can do is:
return ![arrClient containsObject:evaluatedObject];
will exclude objects contained in arrClient
containsObject: use 'isEqual:' to compare the objects
Supposing NSArray has several objects which belong to two classes,
#interface FWNewsObj:NSObject
{
NSString *newsTitle;
NSDate *newsTime;
}
#end
#interface FWPhotoObj:NSObject
{
NSString *photoTitle;
NSDate *photoTime;
}
#end
I'd like to sort the NSArray with object's title (or time). However the title variable in each class has different names.
Then How can I do the sort?
Thanks.
What is instantly coming to my mind is(If I am understanding the Q correctly) :
1st create a dictionary, with titleNames as the keys, and value as the objects
Sort the keys array simply
Then create a sorted array of objects pulling them out of the dictionary, on basis of this sorted keys array
You have to write a custom compare method that both your classes implement. It must take one object as a parameter and return an NSComparisonResult (NSOrderedAscending, NSOrderedDescending or NSOrderedSame)
You can then use sortedArrayUsingSelector: with your own comparison method.
Example:
In FWNewsObj:
- (NSComparisonResult)compareTitle:(id)obj
{
NSAssert([obj isKindOfClass:[FWNewsObj class]] || [obj isKindOfClass:[FWPhotoObj class]], #"Don't know how to compare %# to %#", self, obj);
if ([obj isKindOfClass:[FWPhotoObj class]]) {
return [newsTitle compare:[(FWPhotoObj *)obj photoTitle]];
} else {
return [newsTitle compare:[(FWNewsObj *)obj newsTitle]];
}
}
In FWPhotoObj:
- (NSComparisonResult)compareTitle:(id)obj
{
NSAssert([obj isKindOfClass:[FWNewsObj class]] || [obj isKindOfClass:[FWPhotoObj class]], #"Don't know how to compare %# to %#", self, obj);
if ([obj isKindOfClass:[FWPhotoObj class]]) {
return [photoTitle compare:[(FWPhotoObj *)obj photoTitle]];
} else {
return [photoTitle compare:[(FWNewsObj *)obj newsTitleTitle]];
}
}
It would actually be easier to just define a title method in both classes that wraps either the photoTitle or the newsTitle. Then you can just use NSSortDescriptor with title as the key.