I'm just getting through tons of research and tutorials on ios app development.
Making my first app now and using array with tableviews. My question is now that I have populated arrays with custom objects, I want to query it.
Group By with Sum... things of that nature.
In my research I've found predicates for some types of filtering. So far I have successfully returned a new array where on property in my class is equal to "x".
Predicate worked for that part.
But I'm not sure how to expand to groupby/sum. I've been reading core data might accomplish this but is probably overkill.
Can someone please help me out with some options to research?
Thanks!!!
example
Person Class
name age salary
how can I group by name, and at the same time sum up the salaries?
I found this also ... is this an efficient way to tackle problem
// Get all the airline names with no duplicates using the KVC #distinctUnionOfObjects collection operator
NSArray *airlineNames = [arrayMealRating valueForKeyPath:#"#distinctUnionOfObjects.Airline"];
// Loop through all the airlines
for (NSString *airline in airlineNames) {
// Get an array of all the dictionaries for the current airline
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(Airline == %#)", airline];
NSArray *airlineMealRating = [arrayMealRating filteredArrayUsingPredicate:predicate];
// Get the sum of all the ratings using KVC #sum collection operator
NSNumber *rating = [airlineMealRating valueForKeyPath:#"#sum.Rating"];
NSLog(#"%#: %#", airline, rating);
}
You probably want to use a dictionary so you can efficiently look up the running total for each person by name. Since you're new at Objective-C, I'll spell everything out:
NSMutableDictionary *totalSalaryForName = [NSMutableDictionary dictionary];
for (Person *person in people) {
NSString *name = [person name];
int total = [person salary];
NSNumber *priorTotal = [totalSalaryForName objectAtIndex:name];
if (priorTotal != nil) {
total += [priorTotal intValue];
}
NSNumber *newTotal = [NSNumber numberWithInt:total];
[totalSalaryForName setObject:newTotal forKey:name];
}
However, modern Objective-C provides some “syntactic sugar” you can use to shorten the code:
NSMutableDictionary *totalSalaryForName = [NSMutableDictionary dictionary];
for (Person *person in people) {
NSString *name = person.name;
int total = person.salary;
NSNumber *priorTotal = totalSalaryForName[name];
if (priorTotal != nil) {
total += priorTotal.intValue;
}
totalSalaryForName[name] = #(total);
}
Now you have a dictionary that maps each person's name to total salary.
Related
I want to do kind of a weird dictionary sort. I have non-unique values and keys and get something like this
NSArray *counts = [#"1",#"2",#"2",#"3",#"6",#"10"];
NSArray *names =[#"Jerry",#"Marge",#"Jerry",#"Marge",#"Jen",#"Mark"];
The output that I want is an descending ordered list by counts with unique names. I don't want lower values of the same person in my outputted arrays. The output should be.
sortedNames=[#"Mark",#"Jen",#"Marge",#"Jerry"]
sortedCounts=[#"10",#"6",#"3",#"2"];
I would really appreciate some help on this.
NSMutableArray *userNameArray = [[NSMutableArray alloc] init];
NSMutableArray *countArray = [[NSMutableArray alloc] init];
for (NSDictionary *dict in bigDick) {
NSString *nameString =[dict objectForKey:#"Name"];
NSString *countString =[dict objectForKey:#"Count"];
NSInteger countInt = [countString integerValue];
NSNumber *countNumber =[NSNumber numberWithInt:countInt];
[userNameArray addObject:nameString];
[countArray addObject:countNumber];
}
NSArray *namesAscending =[[userNameArray reverseObjectEnumerator]allObjects];
NSArray *countsAscending=[[countArray reverseObjectEnumerator]allObjects];
// Put the two arrays into a dictionary as keys and values
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:countsAscending forKeys:namesAscending];
// Sort the first array
NSArray *sortedCountArray = [[dictionary allValues] sortedArrayUsingSelector:#selector(compare:)];
// Sort the second array based on the sorted first array
// NSArray *sortedNameArray= [dictionary objectsForKeys:sortedCountArray notFoundMarker:[NSNull null]];
NSMutableArray *nameArray =[[NSMutableArray alloc] init];
for (int i=1; i<sortedCountArray.count; i++) {
NSString *name = [dictionary allKeysForObject:sortedCountArray[i]];
if (sortedCountArray[i]!=sortedCountArray[i-1]) {
[nameArray addObject:name];
}
}
an old method is to manual sort the array with numbers, by searching on every iteraton for the biggest value, and when you find the max value take the name from the other vector at index of the max number and move it in new vector...
max = counts[0];
counter = 0;
for (int i=0;i<counts.count;i++)
{
temp = counts[i];
if (max<temp)
max = temp;
counter = i;
}
[new_names addObject: [names objectAtIndex:counter]];
[new_numbers addObject: max];
[numbers removeObjectAtIndex: counter];
[names removeObjectAtIndex:counter];
Try something like this. It should work if you do it this way.
Important! do not remove elements in for from array that you count for the for length.
Your problem is in your algorithm design, if you step through it a line at a time in the debugger you should see what it does and where it goes wrong.
We're not here to write you code, but let's see if we can go through one step of an algorithm to help you one your way:
Useful fact: If you lookup a key in a dictionary and that key does not exist the return value will be nil.
From this: you can use a dictionary to keep track of the names you have seen paired with the highest score so far. You obtain a name,score pair, lookup the name in the dictionary - if you get nil its a new name with a new high score. If it's not nil its the currently known high score, so you can compare and update.
That's a rough algorithm, let's try it. Before we start rather than using literal strings for keys everywhere let's define some constants. This has the advantage that we won't mistype the strings, the compiler will spot if we mistype the constant names. These can be defined at the file level or within a method:
const NSString *kName = #"Name";
const NSString *kCount = #"Count";
Now to the code, in a method somewhere, we'll need a dictionary:
NSMutableDictionary *highScores = [NSMutableDictionary new]; // a single dictionary rather than your two arrays
Now start your loop as before:
for (NSDictionary *dict in bigDict) // same loop as your code
{
and extract the two values as before:
NSString *nameString = dict[kName]; // same as your code, but using modern syntax
NSInteger countInt = [dict[kCount] integerValue]; // condense two lines of your code into one
Now we can lookup the name in our dictionary:
NSNumber *currentScore = highScores[nameString]; // get current high score for user, if any
If the name exists as a key this will return the current associated value - the score in this case, if there is no matching key this will return nil. We can test for this in a single if:
if (currentScore == nil // not seen user before, no high score
|| currentScore.integerValue < countInt) // seen user, countInt is greater
{
The above condition will evaluate to true if we either need to add the name or update its score. Adding & updating a key/value pair is the same operation, so we just need the line:
highScores[nameString] = #(countInt); // add or update score for user
and a couple of braces to terminate the if and for:
}
}
Let's see what we have:
NSLog(#"Output: %#", highScores);
This outputs:
Output: {
Jen = 6;
Jerry = 2;
Marge = 3;
Mark = 10;
}
Which is a step in the right direction. (Note: the dictionary is not sorted, NSLog just displays the keys in sorted order.)
Make sure you understand why that works, copy the code and test it. Then try to design the next phase of the algorithm.
If you get stuck you can ask a new question showing the algorithm and code you've developed and someone will probably help. If you do this you should include a link to this question so people can see the history (and know you're not trying to get an app written for you through multiple questions!)
HTH
Try this.
sortedArray = [yourArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
After sort your array then remove duplicates using following.
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray: sortedArray];
NSArray *arrayWithoutDuplicates = [orderedSet array];
im currently having issue with the logical of array and object, hope everyone can cure my dumbness, thanks!
Here's what im having:
NSArray data = #[#[#"year",#"name"],
#[#"2015",#"A"],
#[#"2014",#"B"],....
];
//have 100 small array, year can be similar
So i wanna ask how can i loop through them, compare the year of the object with the next object, then if the year is same, put it into a NSDictionary similar to the data array but with the key = the year:
NSDictionary dict = #[#"year":#[#"year",#"name"],
#"2015":#[#[#"2015",#"A"],#[#"2015",#"C"]],
#"2014":#[#"2014",#"B"],
#"2013":#[#"2013",#"D"],...
];
What i tried using predicate give me what i want...but i not sure how to do it with like many different kind of year (i think this is wrong, not sure)
NSString *searchTerm = #"2015";
//predicate1 is compare all the value with 2015, same then product array of them, predicate2 is product array of all array that dont have 2015 inside
NSPredicate *predicate1 = [NSPredicate predicateWithFormat:#"ANY SELF == %#", searchTerm];
NSArray *filtered1 = [data filteredArrayUsingPredicate:predicate1];
NSLog(#"%#", filtered1);
NSPredicate *predicate2 = [NSPredicate predicateWithFormat:#"ANY SELF != %#", searchTerm];
NSArray *filtered2 = [data filteredArrayUsingPredicate:predicate2];
NSLog(#"%#", filtered2);
This give me 2 array that have same year in small array inside
//output of 2 array:
filtered1 = #[#[#"2015",#"diff name"],
#[#"2015",#"A"],..
];
filtered2 = #[#[#"other year",#"diff name"],
#[#"2014",#"B"],...
];
What i think is that get the year of first small array, then compare with the year of the rest 99 small array, product the array of that first year, get the other small array, then do it again...but not sure how to implements it
Use a for loop and check against the previous record, I didnt 100% understand what you want to do when you have a successful find but this should work
NSString *prevData;
NSMutableArray *newData=[NSMutableArray array];
//check the last one
for (int i=0;i< data.count;i++)
{
NSArray currentData= [data objectAtIndex:i];
if(i!=0 && ([currentData objectAtIndex:0] isEqualTo:[prevData objectAtIndex:0]))
{
//add the data to newData
[newData addObject:currentData];
}
prevData=currentData
}
I have a tableview that i want to search through with a searchable. It worked before but when i added sections i got into trouble because i had to change from arrays to dictionary.
So basically i have a NSDictionary that looks like this
{ #"districtA": array with point objects, #"districtB": array with point objects}
I need to filter them based on the point objects.name that is in the arrays. After that i want to create a new nsdictionary with the filtered objects in it.
I tried at least 10 different methods but i can't figure it out so i think this is the only way that i am most positive that should work.
This is the only way i can think of if there is an easier way or more logic way please tell me.
-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name BEGINSWITH[c] %#",searchText];
//create new array to fill
NSArray *arrayWithFilteredPoints = [[NSArray alloc] init];
//loop through the values and put into an rray based on the predicate
arrayWithFilteredPoints = [NSArray arrayWithObject:[[self.PointList allValues] filteredArrayUsingPredicate:predicate]];
NSMutableDictionary *dict = [#{} mutableCopy];
for (Point *point in arrayWithFilteredPoints) {
if (![dict objectForKey:Point.district])
dict[Point.district] = [#[] mutableCopy];
[dict[Point.district]addObject:Point];
}
self.filteredPointList = dict;
self.filteredDistrictSectionNames = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];}
This results in a crash, it happens of course where the predicate is used but i don't know how to debug what predicate i should use:
on 'NSInvalidArgumentException', reason: 'Can't do a substring operation with something that isn't a string (lhs = (
West ) rhs = w)'
I have read the comments and you are right. There was something wrong with my code.
I changed the logic, i added some more steps (like creating NSArray without needing it) to make the solution clear
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name BEGINSWITH[c] %#",searchText];
//1. create new array to fill only the Points from the dictionary
NSArray *allPoints = [self.PointList allValues];
NSMutableArray *allPointObjects = [[NSMutableArray alloc]init];
for (NSArray *array in allPoints) {
for (Point *point in array) {
[allPointObjects addObject:point];
}
}
//2. loop through allPointObjects and put into an mutablearray based on the predicate
NSArray *arrayWithFilteredPoints = [[NSArray alloc] init];
arrayWithFilteredPoints = [allPointObjects filteredArrayUsingPredicate:predicate];
NSMutableDictionary *dict = [#{} mutableCopy];
for (Point *point in arrayWithFilteredPoints) {
if (![dict objectForKey:point.district])
dict[point.district] = [#[] mutableCopy];
[dict[point.district]addObject:Point];
}
self.filteredPointList = dict;
self.filteredDistrictSectionNames = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I wanted a filtered nsdictionary at the end that i can pass back to my tableview that reads the dictionary objects based on the keys (districts)
It seems clear from your description that [self.PointList allValues] is not an array of Point objects but an array of arrays of Point objects. That is the source of your difficulty, including your original crash.
You need to decide what to do about that; for example, if you want just one big array of Point objects, then flatten the array of arrays before you filter. I can't advise you further because it is not obvious to me what ultimate outcome you desire.
EDIT You've now modified your code and I can see more clearly what you're trying to do. You have a dictionary whose values are arrays of points, and you are trying to filter some of the points out of each array. What I would have done is to do that - i.e., run thru the keys, extract each array, filter it, and put it back (or delete the key if the array is now empty). But I can see that what you are doing should work, because you have cleverly put the keys into the points to start with, so you can reconstruct the dictionary structure from that.
I have an array of NSDictionaries and i can access the values in them just fine but i am trying to filter these dictionaries down based on a user's search (user can only search by the dictionary key (#"uniqueSignName").
Once the user has searched through the names property i then need to display ALL dictionary associated data for that #"uniqueSignName" value.
I do the following code and always get the correct amount of NSLogs. For the life of me i cannot remember how to GET those dictionaries.
for (int i = 0; i < [filteredDictionaries count]; i++) {
if ([[[filteredDictionaries valueForKey:#"uniqueSignName"] objectAtIndex:i] isEqualToString:[self.filteredResults objectAtIndex:indexPath.row]]) {
NSLog(#"Power Rangers");
}
}
Eg: I search for "John"
NSLog: #"Power Rangers"
Correctly only appears once.
Now, how do i access another property of "John's" dictionary?
If you want to search the name then better way is that to use NPredicate without iterating the array.
Please see the below example..it may help you...
// Here array is your main array...
NSArray *filteredarray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(uniqueSignName == %#)", #"John"]];
So the problem is solved, I can now access all the filtered properties.
for (NSDictionary *dict in filteredDictionaries) {
if ([[self.filteredResults objectAtIndex:indexPath.row] isEqualToString: dict[#"uniqueSignName"]]) {
NSString *myString = [NSString stringWithFormat:#"%#", dict[#"pType"]];
NSLog(#"hugh: %#", myString);
myString = displayPtype;
}
}
This question already has answers here:
Removing duplicates from NSMutableArray
(9 answers)
Closed 10 years ago.
I have an NSMutableArray which has entity class object as its objects.
Now I want to remove the distinct objects from it. Consider following example
Entity *entity = [[Entity alloc] init];
entity.iUserId = 1;
entity.iUserName = #"Giri"
[arr addObject:entity];
Entity *entity = [[Entity alloc] init];
entity.iUserId = 2;
entity.iUserName = #"Sunil"
[arr addObject:entity];
Entity *entity = [[Entity alloc] init];
entity.iUserId = 3;
entity.iUserName = #"Giri"
[arr addObject:entity];
Now I want only two objects in the Array by removing the duplicate iUserName. I know the way by iteration but I want it without iterating it like predicate or some other way.
If anyone knows then please help me.
I had tried using [arr valueForKeyPath:#"distinctUnionOfObjects.iUsername"]; but it does not return me the entired object.
This question is totally different than the questions which are asked previously. Previously asked question is for getting the distinct objects is correct but they uses looping & I don't want this. I want it from NSPredicate or any other simple option which avoids looping.
EDIT: You can't do what you want to without looping over the array manually and building up a new array. The answer below won't work because it assumes that there are only at most two duplicates.
NSMutableArray *filteredArray = [NSMutableArray array];
for (Entity *entity in arr)
{
BOOL hasDuplicate = [[filteredArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"iUserName == %#", entity.iUserName]] count] > 0;
if (!hasDuplicate)
{
[filteredArray addObject:entity];
}
}
This will look for duplicates in the filtered array as it builds it.
Begin Original Answer
You can't use an NSSet because the Entity instances would have to return NSOrderedSame in compareTo:, which isn't a good idea since you shouldn't use names as unique identifiers.
You can use predicates, but they'll still loop over the array in an O(n^2) time without some optimization.
NSArray *filteredArray = [arr filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Entity *evaluatedObject, NSDictionary *bindings) {
return [[arr filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"iUserName == %#", evaluatedObject.iUserName]] count] > 1;
}]];
That'll work fine. You could make it even faster by sorting the array by the iUserName property first and doing a linear scan over the sorted array (stopping when you see the first duplicate). That's a lot of work if you're dealing with small sample sizes (say, under ten thousand or so). It's probably not worth your time, so just use the code above.
Well you have a few options (that I can think of).
Use a NSSet instead of a NSArray.
Use a for loop (but you don't want to iterate through the array)
Use a predicate search iUserName to see if the name exists before adding it to the array.
Something like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"iUserName == 'Giri'"];
NSArray *searchArray = [arr filterUsingPredicate:predicate];