Order an NSMutableArray with dates - ios

I have an NSMutableArray of file names, and I can access these files modification date:
NSMutableDictionary* dict = [[NSMutableDictionary alloc]init];
for (NSString* str in documentsArray) {
NSString* str2 = [DKStoreManager dateFileWasModifiedWithFileName:str inFolderNumber:folderNumber forUser:userID andType:type];
[dict setObject:str2 forKey:str];
}
NSArray * dateArray = [dict allValues];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"self" ascending:NO];
NSArray *descriptors = [NSArray arrayWithObject: descriptor];
NSArray *reverseOrder = [dateArray sortedArrayUsingDescriptors:descriptors];
NSMutableArray* arr2 = [[NSMutableArray alloc]init];
for (NSString * date in reverseOrder){
NSArray *temp = [dict allKeysForObject:date];
NSString * your_value = [dict valueForKey:[temp lastObject]];
[arr2 addObject:your_value];
}
return arr2;
documentsArray is simply a list of file names, like this:
"ale and 1.png",
"yyyy 1.png",
"the fact that 1.png",
I am trying to put together the name with the correspondent date in a dictionary, and then order the dictionary dates, which I set us objects, and finally get back the ode red list of file names. If I use the file names as object and the dates as key, some dates are equal, so they will only be accepted once. If on the other hand I use as objects the dates, then I can't get back to the keys...

I would suggest you create your own model object and use that instead of using NSDictionary. For example:
#interface Model : NSObject
#property (nonatomic, strong) NSDate *someDate;
#property (nonatomic, copy) NSString *someString;
- (instancetype)initWithString:(NSString *)string date:(NSDate *)date;
#end
#implementation Model
- (instancetype)initWithString:(NSString *)string date:(NSDate *)date
{
self = [super init];
if (self) {
_someString = [string copy];
_someDate = date;
}
return self;
}
#end
Then if you have an NSArray of these Models you could use KVC to get an array of the particular key and do whatever you need, for example:
Model *model1 = [[Model alloc] initWithString:#"Model 1"
date:[NSDate dateWithTimeIntervalSinceNow:10]];
Model *model2 = [[Model alloc] initWithString:#"Model 2"
date:[NSDate dateWithTimeIntervalSinceNow:20]];
NSArray *array = #[model1, model2];
NSLog(#"stringArray:%#", [array valueForKey:#"someString"]);
NSLog(#"dateArray:%#", [array valueForKey:#"someDate"]);

Related

Sort array in ascending order and remove duplicate values in objective- c

I have a horizontally and vertically scrollable table. I get the data for the header and first column from the web service(json). I want to sort the data in ascending order and remove duplicate data from both header and the first column. For removing duplicate values I used the following code:
-(void) requestFinished: (ASIHTTPRequest *) request
{
NSString *theJSON = [request responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSMutableArray *jsonDictionary = [parser objectWithString:theJSON error:nil];
headData = [[NSMutableArray alloc] init];
NSMutableArray *head = [[NSMutableArray alloc] init];
leftTableData = [[NSMutableArray alloc] init];
NSMutableArray *left = [[NSMutableArray alloc] init];
rightTableData = [[NSMutableArray alloc]init];
for (NSMutableArray *dictionary in jsonDictionary)
{
Model *model = [[Model alloc]init];
model.cid = [[dictionary valueForKey:#"cid"]intValue];
model.iid = [[dictionary valueForKey:#"iid"]intValue];
model.yr = [[dictionary valueForKey:#"yr"]intValue];
model.val = [dictionary valueForKey:#"val"];
[mainTableData addObject:model];
[head addObject:[NSString stringWithFormat:#"%ld", model.yr]];
[left addObject:[NSString stringWithFormat:#"%ld", model.iid]];
}
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:head];
headData = [[orderedSet array] mutableCopy];
// NSSet *set = [NSSet setWithArray:left];
// NSArray *array2 = [set allObjects];
// NSLog(#"%#", array2);
NSOrderedSet *orderedSet1 = [NSOrderedSet orderedSetWithArray:left];
NSMutableArray *arrLeft = [[orderedSet1 array] mutableCopy];
//remove duplicate enteries from header array
[leftTableData addObject:arrLeft];
NSMutableArray *right = [[NSMutableArray alloc]init];
for (int i = 0; i < arrLeft.count; i++)
{
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int j = 0; j < headData.count; j++)
{
/* NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.iid == %ld", [[arrLeft objectAtIndex:i] intValue]];
NSArray *filteredArray = [mainTableData filteredArrayUsingPredicate:predicate];*/
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.iid == %ld AND SELF.yr == %ld", [[arrLeft objectAtIndex:i] intValue], [[headData objectAtIndex:j] intValue]];
NSArray *filteredArray = [mainTableData filteredArrayUsingPredicate:predicate];
if([filteredArray count]>0)
{
Model *model = [filteredArray objectAtIndex:0];
[array addObject:model.val];
}
}
[right addObject:array];
}
[rightTableData addObject:right];
}
How will I sort the arrays in ascending order?
Please help.
OK, so you have a model object that looks something like this...
#interface Model: NSObject
#property NSNumber *idNumber;
#property NSNumber *year;
#property NSString *value;
#end
Note, I am intentionally using NSNumber and not NSInteger for reasons that will become clear.
At the moment you are trying to do a lot all in one place. Don't do this.
Create a new object to store this data. You can then add methods to get the data you need. Seeing as you are displaying in a table view sectioned by year and then each section ordered by idNumber then I'd do something like this...
#interface ObjectStore: NSObject
- (void)addModelObject:(Model *)model;
// standard table information
- (NSInteger)numberOfYears;
- (NSInteger)numberOfIdsForSection:(NSinteger)section;
// convenience methods
- (NSNumber *)yearForSection:(NSInteger)section;
- (NSNumber *)idNumberForSection:(NSInteger)section row:(NSInteger)row;
- (NSArray *)modelsForSection:(NSInteger)section row:(NSInteger)row;
// now you need a way to add objects
- (void)addModelObject:(Model *)model;
#end
Now to implement it.
We are going to store everything in one dictionary. The keys will be years and the objects will be dictionaries. In these dictionaries the keys will be idNumbers and the objects will be arrays. These array will hold the models.
So like this...
{
2010 : {
1 : [a, b, c],
3 : [c, d, e]
},
2013 : {
1 : [g, h, u],
2 : [e, j, s]
}
}
We'll do this with all the convenience methods also.
#interface ObjectStore: NSObject
#property NSMutableDictionary *objectDictionary;
#end
#implementation ObjectStore
+ (instancetype)init
{
self = [super init];
if (self) {
self.objectDictionary = [NSMutableDictionary dictionary];
}
return self;
}
+ (NSInteger)numberOfYears
{
return self.objectDictionary.count;
}
+ (NSInteger)numberOfIdsForSection:(NSinteger)section
{
// we need to get the year for this section in order of the years.
// lets create a method to do that for us.
NSNumber *year = [self yearForSection:section];
NSDictionary *idsForYear = self.objectDictionary[year];
return idsForYear.count;
}
- (NSNumber *)yearForSection:(NSInteger)section
{
// get all the years and sort them in order
NSArray *years = [[self.obejctDictionary allKeys] sortedArrayUsingSelector:#selector(compare:)];
// return the correct year
return years[section];
}
- (NSNumber *)idNumberForSection:(NSInteger)section row:(NSInteger)row
{
// same as the year function but for id
NSNumber *year = [self yearForSection:section];
NSArray *idNumbers = [[self.objectDictionary allKeys]sortedArrayUsingSelector:#selector(compare:)];
return idNumbers[row];
}
- (NSArray *)modelsForSection:(NSInteger)section row:(NSInteger)row
{
NSNumber *year = [self yearForSection:section];
NSNumber *idNumber = [self idForSection:section row:row];
return self.objectDictionary[year][idNumber];
}
// now we need a way to add objects that will put them into the correct place.
- (void)addModelObject:(Model *)model
{
NSNumber *modelYear = model.year;
NSNumber *modelId = model.idNumber;
// get the correct storage location out of the object dictionary
NSMutableDictionary *idDictionary = [self.objectDictionary[modelYear] mutableCopy];
// there is a better way to do this but can't think atm
if (!idDictionary) {
idDictionary = [NSMutableDictionary dictionary];
}
NSMutableArray *modelArray = [idDictionary[modelId] mutableCopy];
if (!modelArray) {
modelArray = [NSMutableArray array];
}
// insert the model in the correct place.
[modelArray addObject:model];
idDictionary[modelId] = modelArray;
self.objectDictionary[modelYear] = idDictionary;
}
#end
With all this set up you can now replace your complex function with this...
-(void) requestFinished: (ASIHTTPRequest *) request
{
NSString *theJSON = [request responseString];
SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *jsonDictionary = [parser objectWithString:theJSON error:nil];
for (NSDictionary *dictionary in jsonDictionary)
{
Model *model = [[Model alloc]init];
model.cid = [dictionary valueForKey:#"cid"];
model.idNumber = [dictionary valueForKey:#"iid"];
model.year = [dictionary valueForKey:#"yr"];
model.val = [dictionary valueForKey:#"val"];
[self.objectStore addModelObject:model];
}
}
To get the models out for a particular row then just use...
[self.objectStore modelsForSection:indexPath.section row:indexPath.row];
To get the number of sections in the tableview delegate method...
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.objectStore numberOfYears];
}
No messing around with the model in the view controller.
Welcome to the MVC pattern.
There's a crap ton of code here but by placing all the code here you can remove all the complex code from your VC.
NSSet keeps only non-duplicate objects within themselves so to keep only unique objects in array you can use NSSet as -
Suppose you have array with duplicate objects
NSArray *arrayA = #[#"a", #"b", #"a", #"c", #"a"];
NSLog(#"arrayA is: %#", arrayA);
//create a set with the objects from above array as
//the set will not contain the duplicate objects from above array
NSSet *set = [NSSet setWithArray: arrayA];
// create another array from the objects of the set
NSArray *arrayB = [set allObjects];
NSLog(#"arrayB is: %#", set);
The output from the above looks like:
arrayA is: (
a,
b,
a,
c,
a
)
arrayB is: {(
b,
c,
a
)}
and to sort a mutable array in ascending order you can use NSSortDescriptor and sortUsingDescriptors:sortDescriptors. Also you need to provide the key on the basis of which array will be sorted.
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"key" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[array sortUsingDescriptors:sortDescriptors];
[sortDescriptor release];
Here you will get what you want.
//sort description will used to sort array.
NSSortDescriptor *descriptor=[[NSSortDescriptor alloc] initWithKey:#"iid" ascending:YES];
NSArray *descriptors=[NSArray arrayWithObject: descriptor];
NSArray *reverseOrder=[arrLeft sortedArrayUsingDescriptors:descriptors];
reverseOrder is your desire output.
there is another way you can sort objects that followed model.
NSArray *someArray = /* however you get an array */
NSArray *sortedArray = [someArray sortedArrayUsingComparator:^(id obj1, id obj2) {
NSNumber *rank1 = [obj1 valueForKeyPath:#"iid"];
NSNumber *rank2 = [obj2 valueForKeyPath:#"iid"];
return (NSComparisonResult)[rank1 compare:rank2];
}];
here sortedArray is our output.
you can replace same things for yr key as well.
This is what I did to sort the header data in ascending order and to remove duplicates from both header and leftmost column. Hope this will help others
NSOrderedSet *orderedSet3 = [NSOrderedSet orderedSetWithArray:head3];
headData3 = [[orderedSet3 array] mutableCopy];
[headData3 sortUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2)
{
return [str1 compare:str2 options:(NSNumericSearch)];
}];
NSOrderedSet *orderedSet4 = [NSOrderedSet orderedSetWithArray:left3];
NSMutableArray *arrLeft3 = [[orderedSet4 array] mutableCopy];
[leftTableData3 addObject:arrLeft3];

Filter NSArray based on another array using predicate

Consider the arrays below. The arrays contain objects of type 'Alpha'. We only care about the property username which is of type NSString.
NSArray *some_usernames = #[ <multiple values of type Alpha> ]
NSArray *all_usernames = #[ <multiple values of type Alpha> ]
I basically want a list of all the usernames that are not in the array some_usernames, i.e.
NSArray *remaining_usernames = #[ <all_usernames but not in some_usernames> ];
The way I would intend to do is:
NSPredicates *predicates;
for (Alpha *alpha in some_usernames)
{
predicate = [predicate with #"username != %#", alpha.username];
predicates.add(predicate)
}
create compound predicate
filter all_usernames
But this feels like a bad way of doing this. Is there a way to do this in two lines? I have seen it before but I can't point to the code reference anymore.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"not (self.username IN %#)", [some_usernames valueForKey:#"username"]];
NSArray *remaining_usernames = [all_usernames filteredArrayUsingPredicate:predicate];
complete example
#interface Alpha : NSObject
#property (nonatomic, copy) NSString *username;
-(instancetype) initWithUsername:(NSString *)username;
#end
#implementation Alpha
-(instancetype) initWithUsername:(NSString *)username
{
self = [super init];
if (self) {
self.username = username;
}
return self;
}
-(NSString *)description{
return [NSString stringWithFormat:#"%#: %#", NSStringFromClass([self class]), self.username];
}
#end
NSArray *all_usernames = #[[[Alpha alloc] initWithUsername:#"a"], [[Alpha alloc] initWithUsername:#"b"], [[Alpha alloc] initWithUsername:#"z"], [[Alpha alloc] initWithUsername:#"f"], [[Alpha alloc] initWithUsername:#"e"]];
NSArray *some_usernames = #[[[Alpha alloc] initWithUsername:#"b"], [[Alpha alloc] initWithUsername:#"f"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"not (self.username IN %#)", [some_usernames valueForKey:#"username"]];
NSArray *remaining_usernames = [all_usernames filteredArrayUsingPredicate:predicate];
NSLog(#"%#", remaining_usernames);
prints
(
"Alpha: a",
"Alpha: z",
"Alpha: e"
)
I want to add another answer:
If the ordering of the objects isn't needed (and — most likely — equal objects unwanted) you could instead of using predicate filtering on arrays use Sets and set arithmetic. To do so we must teach Alpha what equality means and provide a hash method. In this case we just use NSStrings implementation:
#implementation Alpha
-(instancetype) initWithUsername:(NSString *)username
{
self = [super init];
if (self) {
self.username = username;
}
return self;
}
-(NSString *)description{
return [NSString stringWithFormat:#"%#: %#", NSStringFromClass([self class]), self.username];
}
-(BOOL)isEqual:(id)object
{
return [self.username isEqual:[object username]];
}
-(NSUInteger)hash
{
return [self.username hash];
}
#end
NSArray *all_usernames = #[[[Alpha alloc] initWithUsername:#"a"],
[[Alpha alloc] initWithUsername:#"b"],
[[Alpha alloc] initWithUsername:#"z"],
[[Alpha alloc] initWithUsername:#"f"],
[[Alpha alloc] initWithUsername:#"e"]];
NSArray *some_usernames = #[[[Alpha alloc] initWithUsername:#"b"],
[[Alpha alloc] initWithUsername:#"f"]];
NSSet *allSet = [NSSet setWithArray:all_usernames];
NSSet *someSet = [NSSet setWithArray:some_usernames];
NSMutableSet *remainingSet = [allSet mutableCopy];
[remainingSet minusSet:someSet];
NSLog(#"%#", remainingSet);
prints
{(
Alpha: z,
Alpha: e,
Alpha: a
)}
This code should be much faster for more data. Please watch WWDC 2013: Designing Code for Performance

Sort NSArray of custom objects by inherited property when adding new object

Class "Person" is inherited by "Employee" which is inherited by "EmployeeList"
In my main model I initialize a new Employee object, setting the firstName and lastName, and then addEmployee to an EmployeeList.
Inside the addEmployee method I want to sort the NSArray of Employee objects by lastName
Here is what I've tried but it's not sorting the list.
#import "EmployeeList.h"
#interface EmployeeList()
#property (strong, nonatomic) NSMutableArray *employees; // of Employee
#end
#implementation EmployeeList
- (NSMutableArray *)employees
{
if (!_employees) _employees = [[NSMutableArray alloc] init];
return _employees;
}
- (void)addEmployee:(Employee *)employee
{
[self.employees addObject:employee];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"lastName"
ascending:YES
selector:#selector(caseInsensitiveCompare:)];
[self.employees sortedArrayUsingDescriptors:#[sortDescriptor]];
_numberEmployees++;
}
Main model:
EmployeeList *mgrList = [[EmployeeList alloc] init];
Employee *employee = [[Employee alloc] init];
employee.firstName = object[#"firstName"];
employee.lastName = object[#"lastName"];
employee.phoneNumber = object[#"phoneNumber"];
[mgrList addEmployee:employee];
Change this:
[self.employees sortedArrayUsingDescriptors:#[sortDescriptor]];
to:
[self.employees sortUsingDescriptors:#[sortDescriptor]];
Ok your code need a refactor:
#import "EmployeeList.h"
#interface EmployeeList()
#property (strong, nonatomic) NSMutableArray *employees; // of Employee
#end
#implementation EmployeeList
//This is a more elegant solution to init
- (NSMutableArray *)employees
{
dispatch_token_t token;
dispatch_once(&token, ^{
_employees = [NSMutableArray new];
});
return _employees;
}
- (void)addEmployee:(Employee *)employee
{
[self.employees addObject:employee];
NSSortDescriptor *sortDescriptor =
[NSSortDescriptor sortDescriptorWithKey:#"lastName"
ascending:YES
selector:#selector(caseInsensitiveCompare:)];
//This return an array so if you don't take it you don't have any change.
NSArray *newArray = [[self.employees copy] sortedArrayUsingDescriptors:#[sortDescriptor]];
//Then you can remove and add again all the object to the NSMutableArray
[self.employees removeAllObjects];
[self.employees addObjectsFromArray:newArray];
//This is not needed because you can access to this information by
//[self.employees count]
//_numberEmployees++;
}
Be careful because you self.employees is mutable.

How to initialize a dictionary from NSString to NSArray

I am trying to create a dictionary (Not sure whether it should be NSDictionary or NSMutableDictionary) from NSString to an array (Not sure whether it should be NSArray or NSMutableArray).
property:
#property(nonatomic, readonly) NSMutableDictionary * categories;
implementation:
#synthesize categories = _categories;
- (NSMutableDictionary *)categories{
if(! _categories) {
for(PFObject * each in self.products) {
NSString * currentcategory = [each valueForKey:#"subtitle"];
NSArray * currentlist = [_categories objectForKey:currentcategory];
if(! currentlist) {
currentlist = [[NSArray alloc] init];
}
NSMutableArray * newArray = [currentlist mutableCopy];
[newArray addObject:each];
NSArray * newlist = [NSArray arrayWithArray:newArray];
[_categories setObject:newlist forKey:currentcategory];
}
}
NSLog(#"After constructor the value of the dictionary is %d", [_categories count]);
return _categories;
}
From the debug NSLog I realize that the dictionary is empty after the construction. What is wrong here and how shall I change it?
After code line
if(! _categories) {
add
_categories = [NSMutableDictionary new];
If you did not initialize _category array somewhere in code then.
you must instantiate it inside
if(!_categories)
Your NSMutableArray _categories instance is not allocated and initialized yet.
To create instance of NSMutableArray just add
_categories = [NSMutableArray arrayWithCapacity:0];

Add object to NSMutableArray in another class

Hi I have a grouped tableview the first section contains a list of emails and the second section just has two rows which are add email manually and select email from contacts.
The log in ManualEmail.m keeps logging 0 for the count and the array in EmailViewController is never modified, but I can't figure out what's wrong
This is my current set up
EmailViewController.h
#property (nonatomic, retain) IBOutlet NSMutableArray *dataArray;
EmailViewController.m
#synthesize dataArray;
- (void)viewDidLoad {
[super viewDidLoad];
dataArray = [[NSMutableArray alloc] init];
NSMutableArray *listItems = [[NSMutableArray alloc] initWithObjects:nil];
[listItems addObject:[ObjectArrays productWithType:#"test" Eemail:#"test#website.com" Eselected:YES]];
NSDictionary *firstItemsArrayDict = [NSDictionary dictionaryWithObject:listItems forKey:#"data"];
[dataArray addObject:firstItemsArrayDict];
NSArray *secondItemsArray = [[NSArray alloc] initWithObjects:#"Add Email Address From Contacts", #"Add Email Address Manually", nil];
NSDictionary *secondItemsArrayDict = [NSDictionary dictionaryWithObject:secondItemsArray forKey:#"data"];
[dataArray addObject:secondItemsArrayDict];
[tableView reloadData];
}
ManualEmail.m
EmailViewController *emailPVC = [[EmailViewController alloc] init];
NSDictionary *dictionary = [emailPVC.dataArray objectAtIndex:0];
NSArray *array = [dictionary objectForKey:#"data"];
NSMutableArray *emailArray = [NSMutableArray arrayWithArray:array];
[emailArray addObject:[ObjectArrays productWithType:name.text Eemail:email.text Eselected:YES]];
[emailPVC.dataArray removeObjectAtIndex:0];
NSDictionary *firstItemsArrayDict = [NSDictionary dictionaryWithObject:emailArray forKey:#"data"];
[emailPVC.dataArray insertObject:firstItemsArrayDict atIndex:0];
NSLog(#"%d", [emailPVC.dataArray count]);
In ManualEmail.m, you're creating the EmailViewController in code. But you aren't ever calling anything that would call its -viewDidLoad method, so the dataArray isn't ever getting created or filled out. You need to call emailPVC.view = <some view you created>; in order for -viewDidLoad to get called.

Resources