Sort NSArray of NSDictionary objects using NSSortDescriptor - ios

I have an array of dictionaries that contain information about high scores. I am trying to figure out how to sort them by the different values in the dictionaries but cannot get it to work.
An example shown below attempts to sort by "Score":
NSDictionary *highScoreDictionary1 = #{#"Score" : #52, #"Duration" : #230 , #"Date" : [NSDate date]};
NSDictionary *highScoreDictionary2 = #{#"Score" : #23, #"Duration" : #230 , #"Date" : [NSDate date]};
NSDictionary *highScoreDictionary3 = #{#"Score" : #35, #"Duration" : #230 , #"Date" : [NSDate date]};
NSArray *highScoresArray = #[highScoreDictionary1, highScoreDictionary2, highScoreDictionary3];
NSSortDescriptor *highScoreSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"Score" ascending:YES]; // Sort by Score
NSArray *sortDescriptorArray = [NSArray arrayWithObject:highScoreSortDescriptor];
[highScoresArray sortedArrayUsingDescriptors:sortDescriptorArray];
The output I get from NSLog(#"sorted array of dictionaries: %#", highScoresArray); is:
sorted array of dictionaries: (
{
Date = "2014-09-01 19:38:00 +0000";
Duration = 230;
Score = 52;
},
{
Date = "2014-09-01 19:38:00 +0000";
Duration = 230;
Score = 23;
},
{
Date = "2014-09-01 19:38:00 +0000";
Duration = 230;
Score = 35;
}
)
How do I remedy this? Am I missing something here because it seems that the dictionaries are not being sorted by score.

highScoresArray = [highScoresArray sortedArrayUsingDescriptors:sortDescriptorArray];

You're trying to sort an NSArray, which is immutable. You need to use the sort function to create a mutable array, i.e.
replace your:
[highScoresArray sortedArrayUsingDescriptors:sortDescriptorArray];
with:
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:[highScoresArray sortedArrayUsingDescriptors:#[highScoreSortDescriptor]]];
I have tested this and it seems to work.

Try this approach
NSArray *sortedArray;
sortedArray = [highScoresArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSDictionary *first = (NSDictionary*)a;
NSDictionary *second = (NSDictionary*)b;
int firstScore = [first objectForKey:#"score"];
int secondScore = [second objectForKey:#"score"];
if(firstScore > secondScore)
{
return NSOrderedDescending;
}
else if (firstScore < secondScore)
{
return NSOrderedAscending;
}
return NSOrderedSame;
}];
Got the code from here

If you would like, here's my own sorting method which I implemented manually, in your case just use it like this
// you could also pass "DESC" for descending order
NSMutableArray* copiedArray = [[NSMutableArray alloc] initWithArray:highScoresArray];
[self sortArray:copiedArray inOrder:#"ASC" basedOnField:#"Score" args:-1];
// Now copiedArray contains a sorted array :)
Here's the full code (2 methods, one main and one helper), copy these to some class so the above code would work.
/*
* This method sorts a given array based on the given field name and the given sorting order
* fieldName could be nil if the comparison shall happen directly on the array items
* args contain the array index of the value to compare if "field name" pointed to an array or -1
*/
- (void)sortArray:(NSMutableArray*)array
inOrder:(NSString*)sortingOrder
basedOnField:(NSString*)fieldName
args:(int)args {
for (int i = 1; i < array.count; i++) {
// Start the insertion sort algorithm
int j = i;
// Get the current value and one before it
id currentValue = [self itemInArray:array
atIndex:j
fieldName:fieldName
args:args];
id previousValue = [self itemInArray:array
atIndex:j-1
fieldName:fieldName
args:args];
// Set the comparison result based on the user request
NSComparisonResult requiredResult = NSOrderedDescending;
if ([sortingOrder compare:#"ASC"] == NSOrderedSame) {
requiredResult = NSOrderedAscending;
}
while ((j > 0) && ([previousValue compare:currentValue] == requiredResult)) {
// Swap the current and previous objects
id temp = array[j];
array[j] = array[j-1];
array[j-1] = temp;
// Get back one step and get the new current and previous values
j--;
if (j == 0) {
break;
}
currentValue = [self itemInArray:array
atIndex:j
fieldName:fieldName
args:args];
previousValue = [self itemInArray:array
atIndex:j-1
fieldName:fieldName
args:args];
}
}
}
// This method gets an item from the array based on the given index and the field name if the item is an object, as well as a specific member of that item if it's an array (index is passed in args)
- (id)itemInArray:(NSArray*)array
atIndex:(int)index
fieldName:(NSString*)fieldName
args:(int)args {
// Get the item at the given index
id value = array[index];
// Get the sepcific field from it if it's an object
if (fieldName != nil) {
value = [value valueForKey:fieldName];
}
// Get the specific value if the field is an array
if ([value isKindOfClass:[NSArray class]]) {
value = value[args];
}
return value;
}

Related

Filter data based on id Objective-C

I have the following shared.orderItems. I am using sharedData.orderITems's id is used to construct shared.comboItem. I wonder how could I able to filter sharedData.orderItems data where there is no comboItemId equals id.
For example, in the following example, there are two items id are 71 and 72 are used in shared.ComboItems. I want to filter 71 and 72 on shared.orderItems and only keep the object id 1.
(lldb) po sharedData.orderItems
<__NSArrayM 0x174050860>(
{
Note = "";
Quantity = 1;
id = 72;
},
{
Note = "";
Quantity = 1;
id = 71;
},
{
Note = "";
Quantity = 2;
id = 1;
}
)
(lldb) po sharedData.comboItems
<__NSArrayM 0x174247620>(
{
Note = "";
Quantity = 1;
comboItemId = 72;
id = 3;
},
{
Note = "";
Quantity = 1;
comboItemId = 71;
id = 19;
},
{
Note = "";
Quantity = 1;
comboItemId = 72;
id = 20;
},
{
Note = "";
Quantity = 1;
comboItemId = 72;
id = 21;
}
)
I think I understand the question to mean you want to exclude orderItems that are referred to by the comboItemId attribute in the comboItems array. First collect those:
NSMutableSet *excludedIds = [NSMutableSet set];
for (NSDictionary *item in sharedData.comboItems) {
[excludedIds addObject:item[#"comboItemId"]]; // EDIT
}
Arrays can be filtered by predicates, and those can be specified with format strings (see here). So you need:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NOT (id IN %#)", excludedIds];
NSArray *filtered = [sharedData.orderItems filteredArrayUsingPredicate:predicate];
Have you tried predicate?
Filter shared.comboItems to get all comboItemId into an Array.
Use Predicate with NOT IN to filter sharedData.orderItems
Solution:
NSArray *unique = [shared.comboItems valueForKeyPath:#"#distinctUnionOfObjects.comboItemId"];
NSPredicate *nonePredicate = [NSPredicate predicateWithFormat: #"NOT(id IN %#)", unique];
NSArray *filtered = [sharedData.orderItems filteredArrayUsingPredicate:nonePredicate];
NSLog(#"Filtered: %#", filtered);
You can use Key Value Coding - Collection Operator to get ids from Combo items.
#distinctUnionOfObjects: Returns an array of the objects in the property specified in the key path to the right of the operator
NSArray *compoIds = [sharedData.CompoItems valueForKeyPath:#"#distinctUnionOfObjects.comboItemId"];
NSPredicate *dupIdsPredicate = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind) {
NSDictionary *orderItemDictionary = (NSDictionary *) obj;
return ![compoIds containsObject:[orderItemDictionary valueForKey:#"id"]];
}];
NSArray *unionOfIDs = [sharedData.orderItems filteredArrayUsingPredicate:dupIdsPredicate];
NSLog(#"%#", unionOfIDs);

Find index of value which is stored into NSDictionary and NSDictionary stored into NSMutableArray

I have NSMutableArray which stores NSDictionary. Consider following array which contain NSDictionary.
<__NSArrayM 0x7f9614847e60>(
{
"PARAMETER_KEY" = 1;
"PARAMETER_VALUE" = ALL;
},
{
"PARAMETER_KEY" = 2;
"PARAMETER_VALUE" = ABC;
},
{
"PARAMETER_KEY" = 3;
"PARAMETER_VALUE" = DEF;
},
{
"PARAMETER_KEY" = 4;
"PARAMETER_VALUE" = GHI;
},
{
"PARAMETER_KEY" = 5;
"PARAMETER_VALUE" = JKL;
}
)
I can find index of specific NSDictionary using following code.
int tag = (int)[listArray indexOfObject:dictionary];
But If I have PARAMETER_VALUE = GHI and using this value I want to find that dictionary index into array. I don't want to use for loop. Can I get index without for loop?
You can use indexOfObjectPassingTest method of NSArray:
[listArray indexOfObjectPassingTest:^BOOL(NSDictionary* _Nonnull dic, NSUInteger idx, BOOL * _Nonnull stop) {
return [dic[#"PARAMETER_VALUE"] isEqualToString:#"GHI"];
}];
Also, please consider using indexesOfObjectsPassingTest if you can have multiple dictionaries with the same PARAMETER_VALUE
You can add a category on NSArray like this (this does a type safety check as well; only array of dictionaries are processed):
- (NSInteger)indexOfDictionaryWithKey:(NSString *)iKey andValue:(id)iValue {
NSUInteger index = [self indexOfObjectPassingTest:^BOOL(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
if (![dict isKindOfClass:[NSDictionary class]]) {
*stop = YES;
return false;
}
return [dict[iKey] isEqual:iValue];
}];
return index;
}
And then simply call indexOfDictionaryWithKey:andValue: directly on your array object to get the index.
Just in case if you want to get the dictionary object out of that array, add one more category in NSArray:
- (NSDictionary *)dictionaryWithKey:(NSString *)iKey andValue:(id)iValue {
NSUInteger index = [self indexOfDictionaryWithKey:iKey andValue:iValue];
return (index == NSNotFound) ? nil : self[index];
}
You can use NSPredicate for this purpose:
// Creating predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.PARAMETER_VALUE MATCHES %#",#"GHI"];
// Filtering array
NSArray *filteredArr = [arr filteredArrayUsingPredicate:predicate];
// If filtered array count is greater than zero (that means specified object is available in the array), checking the index of object
// There can be multiple objects available in the filtered array based on the value it holds (In this sample code, only checking the index of first object
if ([filteredArr count])
{
NSLog(#"Index %d",[arr indexOfObject:filteredArr[0]]);
}
Well, one has to enumerate in a way. Taking your requirement literally (no for loop), you can use fast enumeration. However, the task can be run concurrently, because you only need read access:
__block NSUInteger index;
[array enumerateObjectsWithOptions: NSEnumerationConcurrent
usingBlock:
^(NSDictionary *obj, NSUInteger idx, BOOL *stop)
{
if( [obj valueForKey:#"PARAMETER_VALUE" isEqualToString:#"GHI" )
{
index = idx;
*stop=YES;
}
}

Adding Missing Dates in NSArray

I have an array that has the past 5 days. It is built like this:
(
"2015-01-27",
"2015-01-26",
"2015-01-25",
"2015-01-24",
"2015-01-23",
)
I have a second NSArray from a FetchRequest
(
{
daySectionIdentifier = "2015-01-24";
sumValue = 2500;
},
{
daySectionIdentifier = "2015-01-25";
sumValue = 1487;
},
{
daySectionIdentifier = "2015-01-27";
sumValue = 750;
}
)
What I want is the dates that match my first array get a value in the first array, the missing dates get no value.
So the final result will look like this:
(
{
daySectionIdentifier = "2015-01-23";
sumValue = 0;
},
{
daySectionIdentifier = "2015-01-24";
sumValue = 2500;
},
{
daySectionIdentifier = "2015-01-25";
sumValue = 1000;
},
{
daySectionIdentifier = "2015-01-26";
sumValue = 0;
},
{
daySectionIdentifier = "2015-01-27";
sumValue = 750;
}
)
Anybody have an idea how to do this? Thanks in advance
Ok so this didn't turn out to be too hard, hopefully this is what you are after:
Firstly thinking about the problem, the issue somewhat here is getting the data in the right format to be able to analyse, so first of all I changed it from an array filled with dictionaries to an array of arrays with each array containing the information (I know not the most elegant solution but one that works none the less)
// Here is our array of past dates
NSArray * pastDateDays = #[#"2015-01-27", #"2015-01-26", #"2015-01-25", #"2015-01-24", #"2015-01-23"];
// Here is our array from the request, this is full of dictionaries
NSArray * fetchRequest = #[#{#"daySectionIdentifier" : #"2015-01-24", #"sumValue": #2500}, #{#"daySectionIdentifier" : #"2015-01-25", #"sumValue": #1487}, #{#"daySectionIdentifier" : #"2015-01-27", #"sumValue": #750}];
// Here is a mutable array we will be adding to
NSMutableArray * request = [NSMutableArray arrayWithArray:fetchRequest];
So now we are ready to start getting the information into a slightly nicer format.
// This function gets the array in an array of arrays where each array has a date and a value
fetchRequest = [self fetchRequestToArray:fetchRequest];
// Not too complicated just taking it out of one and putting it in another
- (NSArray *)fetchRequestToArray: (NSArray *)array {
NSMutableArray * tempArray = [NSMutableArray new];
for (NSDictionary * dict in array) {
NSArray * temp = #[[dict objectForKey:#"daySectionIdentifier"], [dict objectForKey:#"sumValue"]];
[tempArray addObject:temp];
}
return [NSArray arrayWithArray:tempArray];
}
Next we loop through a mutable array of the dates in our date array and if they match in our requested array we remove them:
NSMutableArray * tempDates = [NSMutableArray arrayWithArray:pastDateDays];
for (NSArray * array in fetchRequest) {
NSString * date = array.firstObject;
for (NSString * string in pastDateDays) {
if ([date isEqualToString:string]) {
[tempDates removeObject:string];
}
}
}
This leaves us with an array of dates which are included in our date array but are not included in our requested data. These are the dates we need to add a zero value for.
Again this is relatively simple:
for (NSString * date in tempDates) {
NSDictionary * dict = [NSDictionary dictionaryWithObjects:#[date, #0]
forKeys:#[#"daySectionIdentifier", #"sumValue"]];
[request addObject:dict];
}
This returns us with the desired array.
The only thing that might need to be added is that this array isn't in date order. This can be easily sorted with a number of methods. I found and added this on in a few seconds but you could choose a more complicated one if you need it:
NSSortDescriptor * sortByDate = [NSSortDescriptor sortDescriptorWithKey:#"daySectionIdentifier"
ascending:YES];
NSArray * sortDescriptors = [NSArray arrayWithObject:sortByDate];
NSArray * sortedArray = [request sortedArrayUsingDescriptors:sortDescriptors];
This will output the date in the format:
The final array is the array called request and is a mutableArray
<__NSArrayI 0x7fade848f480>(
{
daySectionIdentifier = "2015-01-23";
sumValue = 0;
},
{
daySectionIdentifier = "2015-01-24";
sumValue = 2500;
},
{
daySectionIdentifier = "2015-01-25";
sumValue = 1487;
},
{
daySectionIdentifier = "2015-01-26";
sumValue = 0;
},
{
daySectionIdentifier = "2015-01-27";
sumValue = 750;
}
)
Which I think is the desired output.
Things to note:
- The values are NSNumbers and not integers as we can't store integers in an NSdictionary
This is not the most elegant solution - I have used a lot of arrays and i am sure it could be refactored. This code though does work and so can be worked with to build understanding - this code should work when copied straight in but there may be a few things needing tweaking as it is a long answer copied from my XCode
The strings need to be in exactly this format for it to work, if they are not then this solution will need to be tweaked.
I hope this helps

How do i find indexOfObject in an array that has dictionaries

Here is my array :
<__NSCFArray 0x7b6ca390>(
{
name = kumar;
no = 158;
},
{
name = rajdeep;
no = 338;
},
{
name = smitha;
no = 361;
},
{
name = uma;
no = 422;
}
)
This is what I am trying to do
NSInteger selectedIndexpath=[self.arrLoadCell indexOfObject:_txtfield.text];
where
_txtfield.text = #"rajdeep"
and i get some random junk value like 2147483647 stored in selectedIndexPath.
Am i missing something? Or is there anyother way to handle it?
This "junk value" seems to be NSNotFound, which you should check against when using this method. Long story short, your array does not contain the value you are looking for. It does not work, because you are looking for a string but the array contains dictionaries, so you would have too search for e.g. { "name" : "rajdeep", "no" : #338 }.
Alternatively, to make this work with strings only, use NSPredicate for filtering, e.g.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", valueFromTextbox];
NSArray result = [array filteredArrayUsingPredicate: predicate];
If your array is very short you can also make a loop for comparison.
Update to get the index, use the result of the predicate as input, e.g.
NSInteger selectedIndexpath = [self.arrLoadCell indexOfObject:result.firstObject];
indexOfObject: requires the actual object that is stored in the array. You are providing only the value of a key for one of the Dictionaries.
One of the ways you could achieve what you want, is to loop through the array and for each dictionary, test the value.
The "junk value" you got is NSNotFound, which means that the object was not found.
That's because your array contains Dictionaries, not Strings. You cannot compare your String (textfield.text) with the dictionaries that are in your array.
I think what you want is:
int idx = NSNotFound;
for (int i=0; i<self.arrLoadCell.count; i++) {
NSDictionary *dict = [self.arrLoadCell objectAtIndex:i];
if ([[dict objectForKey:#"name"] isEqualToString:_txtfield.text]) {
idx = i;
break;
}
}
NSInteger selectedIndexpath=idx;
NSUInteger indexPath = 0;
for (NSDictionary *dic in arrLoadCell){
if ([[dic objectForKey:#"name"] isEqualToString: _txtfield.text]) {
indexPath=[arrLoadCell indexOfObject:dic];
break;
}
}
NSLog(#"%lu is the indexPATH",(unsigned long)indexPath);
Assuming that your array of dictionaries is arrLoadCell this should work.

Remove object from dictionary if a value is not found in an array

I am trying to manipulate an array of dictionaries based on an array of values.
for example:
arrayOfDicts =
(
{
caption = a;
urlRep = "12";
},
{
caption = b;
urlRep = "34";
},
{
caption = c;
urlRep = "56";
}
)
Array of values:
urlReps = (12,56);
outcome I am trying to achieve:
(
{
caption = a;
urlRep = "12";
},
{
caption = c;
urlRep = "56";
}
)
The code I have now that adds to it based on the array is this:
NSMutableArray *arrayOfDicts;
NSMutableSet *urlReps;
[urlReps minusSet:[NSSet setWithArray:[arrayOfDicts valueForKey:#"urlRep"]]];
// merge new dicts to the original array
for (id urlRep in urlReps)
{
[arrayOfDicts addObject:#{ #"urlRep" : urlRep, #"caption" : #"" }];
}
This adds to my array of dicts if there are more urls in the array but I need to also remove if there are less urls in the array compared to the dict
Try something Like this using NSPredicate to filter the array:
NSArray *arrayOfDicts = .... //your existing data
NSArray *filteredURLParams = #[#"12",#"56"];
NSPredicate *urlPredicate = [NSPredicate predicateWithFormat:#"urlRep IN %#",filteredURLParams];
NSArray *filteredDicts = [arrayOfDicts filteredArrayUsingPredicate:urlPredicate];
Here's some old fashioned, straightforward, and completely untested code :-)
// Your data
NSMutableArray* arrayOfDicts = [...];
NSMutableSet* urlReps = [...];
// Will receive those dictionaries that have a matching urlRep
NSMutableArray* filteredArrayOfDicts = [NSMutableArray arrayWithCapacity:0];
// Initially contains all urlReps, but we will successively
// eliminate those urlReps that we encountered
NSMutableSet* urlRepsNotSeen = [NSMutableSet setWithCapacity:0];
[urlRepsNotSeen addObjects:[urlReps allObjects]];
for (NSDictionary* dict in arrayOfDicts)
{
NSString* urlRep = [dict valueForKey:#"urlRep"];
if ([urlReps containsObject:urlRep])
[
[filteredArrayOfDicts addObject:dict];
// Not sure what happens if urlRepsNotSeen does not contain the
// urlRep (because we eliminated it earlier). If it crashes, add
// this check:
// if ([urlRepsNotSeen containsObject:urlRep])
[urlRepsNotSeen removeObject:urlRep];
}
arrayOfDicts = filteredArrayOfDicts;
for (NSString urlRepNotSeen in urlRepsNotSeen)
{
[arrayOfDicts addObject:#{ #"urlRep" : urlRepNotSeen, #"caption" : #"" }];
}

Resources