Sort numbers inside an array of custom objects - ios

i tried this, this and this link
I have an array (Say personObjectArray) which contains objects of class Person.
Class Person having 2 variables say,
NSString *name, NSString *age.
Here age has type of nsstring.
Now when i sort like below,
personObjectArray = [[personObjectArray sortedArrayUsingComparator:^NSComparisonResult(Person *p1, Person *p2){
return [p1.age compare:p2.age];
}] mutableCopy];
It sorts like this,
1,
11,
123
2,
23,
3... It sorts like alphabetical order not considering it as an number.
So i change my code like this,
personObjectArray = [[personObjectArray sortedArrayUsingComparator:^NSComparisonResult(Person *p1, Person *p2){
return [[p1.age intValue] compare:[p2.age intValue] ];
}] mutableCopy];
And it says, Bad receiver type 'int'
How can i sort now ?
Please don't tell me to change the datatype of age in Person class. I can't change it. Any help appreciated (: , Thanks for the time.

You were using compare on a primitive (Not Object) type int , hence it won't work.
Try this
return [#([p1.age intValue]) compare:#([p2.age intValue])];
Here, we are using NSNumber (Which is an object type) to compare the int values
#() is a NSNumber's literal
Hope this will help you.. (:

Try this :
personObjectArray = [[personObjectArray sortedArrayUsingComparator:^NSComparisonResult(Person *p1, Person *p2){
return [p1.age compare:p2.age options:NSNumericSearch];
}] mutableCopy];

Related

Sorting an Array of objects by localized description of a property that contains a number

I have this object that has a property called nameEN that is the name the object has in english. When this object is shown on the screen it is like this:
NSLocalizedStringFromTable([myObject nameEN]);
In other words, the name is localized by using NSLocalizedStringFromTable.
Said that, I have a lot of these objects on an array and I want to sort that array by the localized name.
The problem is that the objects have names like House 1, House 2 ... House 10, etc.
When I sort that using this code:
NSArray *sorted = [unsortedArray sortedArrayUsingComparator:^NSComparisonResult(MyOBject *p1, MyOBject *p2){
NSString *name1 = NSLocalizedStringFromTable([p1 nameEN], #"MyTable", nil);
NSString *name2 = NSLocalizedStringFromTable([p2 nameEN], #"MyTable", nil);
return [name1 localizedCompare:name2];
}];
The order I get is House 1, House 10, House 2... 10 before 2, 20 before 3 and so one.
How do I sort this using NSComparisonResult?
Found the answer: just change localizedCompare: with localizedStandardCompare: and it works perfectly.
Try finding last whitespace with rangeOfCharacter method with NSBackwardsSearch option enabled. With this range you can divide your string and compare first part containing name separately from second part containing number.

-[Person componentsSeparatedByString:]: unrecognized selector sent to instance

I'm new to programming, and I feel a little intimidated posting, and I'm stuck. I don't want a quick fix! Please help me understand this.
I've created a custom method with a name, age, height and gender. It gets called when the NSMutableArray adds custom objects to the array. For some reason I cannot pull said items out of the NSMutableArray. Let's say the age needs to be printed out. I get a error saying...
-[Person componentsSeparatedByString:]: unrecognized selector sent to instance
Person.m
- (id)initWithName:(NSString *)n
age:(int)a
height:(float)h
gender:(char)g
{
self = [super init];
if (self) {
self.name = n;
self.age = a;
self.height = h;
self.gender = g;
}
return self;
}
- (NSString *)description
{
NSString *descriptionString = [[NSString alloc] initWithFormat:#"%#, %d, %.1f, %c",
self.name,
self.age,
self.height,
self.gender];
NSLog(#"Description String: %#", descriptionString);
return descriptionString;
}
When adding objects to the NSMutableArray they get converted to a NSString? How do I get the peoples age without the whole strings name and height in the NSLog?
ViewController.m
[self.people addObject:[[Person alloc] initWithName:#"Jake" age:29 height:73.5 gender:'f']];
[self.people addObject:[[Person alloc] initWithName:#"Jerry" age:24 height:82.3 gender:'m']];
[self.people addObject:[[Person alloc] initWithName:#"Jessica" age:29 height:67.2 gender:'f']];
NSString *mystring1 = [self.people objectAtIndex:0];
NSLog(#"%#", mystring1);
// Works
//NSString *list = #"Norm, 42, 73.2, m";
NSArray *listItems = [self.people[0] componentsSeparatedByString:#", "];
NSLog(#"List Items: %#", listItems[1]);// age
Output
Description String: Jake, 29, 73.5, f
Jake, 29, 73.5, f
-[Person componentsSeparatedByString:]: unrecognized selector sent to instance
Solved:
Notice the extra [ age];
int age = [[self.people objectAtIndex:0]age];
NSLog(#"%d", age);
I think the reason it seems like your object is getting converted to a string is this line,
NSString *mystring1 = [self.people objectAtIndex:0];
You need to specify the property if that is all you want to print
NSString *nameString = [[self.people objectAtIndex:0]name];
You can't perform componentsSeparatedByString on your Person object since it's not a string and doesn't have that method. (NSMutableArray objects are not automatically converted to NSStrings.) But to get the age of your Person, should be fairly simple anyway. Just access Person's age property:
int age = self.people[0].age;
NSLog(#"Age: %d", age);
Edit: You can technically use componentsSeparatedByString on your mystring1 NSString, like so:
NSArray *listItems = [mystring1 componentsSeparatedByString:#", "];
NSLog(#"List Items: %#", listItems[1]);// age
But again, I don't see the point of doing this when you can access the Person's age property directly.
The componentsSeparatedByString method is an instance method of NSString class. You can't call it on your Person class in this way.
I don't know why you need that code, if you are trying to access age, then:
NSLog(#"Age %d", self.people[0].age);
is enough. If you are trying to achieve any other thing, then you can get the components like:
NSArray *listItems = [self.people[0].description componentsSeparatedByString:#", "];
NSLog(#"List Items: %#", listItems[1]);// age
One would think this would work; however, I can not call .age on my array.
NSLog(#"Age %d", self.people[0].age);
Gives the following output...
Property 'age' not found on object of type 'id'
One would also think componentsSeparatedByString would work. It doesn't split my string by the comma and gives an error.
-[Person componentsSeparatedByString:]: unrecognized selector sent to instance
Solved READ UP
I think you are getting confused about why when you log the object you are getting a string, but you can't perform any methods on it.
And here is why you think you are getting a string, you've implemented the description property, which comes from the NSObject protocol. When you do a log of an object with a %# parameter you are getting the result of this property, which is a string.
So looking at your code:
NSString *mystring1 = [self.people objectAtIndex:0]; //1
NSLog(#"%#", mystring1); //2
The first thing is that you are getting the first object out of your array. objectAtIndex returns an id, which is a pointer to an NSObject. It is not a strongly typed return. You are allocating it to an NSString which is wrong, because it as actually a person object. But since you aren't calling any NSString methods on it, the compiler is not flagging this incorrect assignment.
The second thing is that you are just getting the result of the description property, as I've already mentioned.
Your edited solution:
int age = [[self.people objectAtIndex:0]age];
Works, but here is why: you are getting the objectAtIndex:0 which returns an id. Then you are sending it the message age. Objective-C is a dynamic language, which means you can send any message to any object (although you get a run-time crash if the object does not implement the method) In this case, your Person object does implement an age method (since it's a public property) so you get an age back.
A different way of doing it:
NSInteger age;
Person *person = self.people.firstObject;
if (person) {
age = person.age;
} else {
age = NSNotFound;
}
Why am I doing it this way?
Firstly, I am getting the firstObject out of the array and putting it into a typed variable. This is safer, as if there is no object at index 0 (i.e. the array is empty) then you won't crash your app. firstObject returns the first object if it exists, on a nil if it doesn't.
Secondly, Only if the person has been correctly extracted from the array, do I assign the age to an NSInteger variable. You should prefer the specific types of NSInteger over int (and CGFloat over float) because they will use the correctly sized variable when running on 32-bit or 64-bit` systems.
Thirdly, if person cannot be created I am assigned the value of NSNotFound to the age. This is a typedef for a very large number, one that you can compare against. Just returning 0 in age is not enough to tell you there was an error. The person could have an age of 0. with this code you can test the age with:
if (age != NSNotFound) {
// This is a valid age, do something with it
} else {
// A person object could not be extracted from the array, this is not a valid age
}
Is this overkill? Not really. When you start writing real, complex apps, this sort of defensive programming will come naturally to you. Arrays can be empty, your data could be incorrectly created, etc. Writing code that gracefully handles these eventualities is the real skill of programming. Unfortunately, It's a bit more long winded, and most tutorials that you find on the web show you the simple, happy path.
I hope this gives you a better idea of what your code is doing and what more you could be doing to write robust code.

How can I implement my logic properly to populate my UITableView

Sorry guys, this problem I am running into is pretty trivial. I just can't wrap my head around it so hope someone can help me. Your help is really appreciated. I am getting JSON data through NSURLConnectDelegate with a web API. I get something like this back:
(
{
id = 340
name = Vicent },
{
id = 339
name = Johny },
{
id = 338
name = Eric }
)
and I save it in a NSMutableArray as a global variable. Now, I have a NSSet of "ids". For example:
{
340, 339
}
In the numberOfRowsInSection, I return the set's count. I am trying to load only the ids in the NSSet from the array with the data saved from the webAPI, so I do something like this in cellForRowIndexPath:
for (NSNumber *num in [set allObjects]) {
NSString *newString = [[savedArray objectAtIndex:indexPath.row]
NSString *new = [num stringValue];
if ([new isEqual:newString]) {
}}
How can I just populate the ids I want?
The JSON makes it look like you have an array of dictionaries, which is a reasonable data structure to use as the data source for a table view.
It sounds like you're trying to filter your array to only include the items that are in your set. Is that right?
If so, you could write code that would create a new array containing the subset of your array elements who's ID is also in your set. There are at least a half-dozen ways to do that. One fairly simple approach would be to use the NSArray method indexesOfObjectsPassingTest. You'd pass that method a block of code that would check each array element to see if it's id object was in your set.
That would give you an NSIndexSet with the indexes of the items in your array who's ID are in your set. Then you could use the NSArray method objectsAtIndexes to get an array of only the objects that are also in the set. Something like this (Assuming that your array of dictionaries is called savedArray and your set is called allObjects:
//get the indexes of items in the array savedArray who's id appears in the set allObjects
NSIndexSet *indexes = [savedArray indexesOfObjectsPassingTest:
^(NSDictionary *obj,
NSUInteger idx,
BOOL *stop)
{
return [allObjects member: obj[#"id"]] != nil;
}
];
//Now build an (immutable) array of just the objects who's ID are in the set
NSArray *subArray = [savedArray objectsAtIndexes: indexes];
The array subArray created above is immutable. If you need a mutable array you would need to make a mutable copy, which is a one-line change.
Disclaimer: I still struggle a little with block syntax, so the above might not be exactly correct, but it gives you the general idea.

Find object by name in NSMutableArray

I have a generic person object with properties personName, lastName, and age. I am storing the user input into an NSMutableArray and I wanted to find a under by his/her name in the array. I have tried finding a bunch of different solutions but none that quite really work.
This is my main.m
#autoreleasepool {
char answer;
char locatePerson[40];
//Create mutable array to add users for retrieval later
NSMutableArray *people = [[NSMutableArray alloc] init];
do{
Person *newPerson = [[Person alloc]init];
[newPerson enterInfo];
[newPerson printInfo];
[people addObject:newPerson];
NSLog(#"Would you like to enter another name?");
scanf("\n%c", &answer);
}while (answer == 'y');
NSLog(#"Are you looking for a specific person?");
scanf("%c", locatePerson);
//This is where I need help
int idx = [people indexOfObject:]
}
This is very basic but I am new to objective-c and I wanted to try and find the user by name. The solutions I've seen have used the indexesOfObjectsPassingTest method. But I was wondering if I can't just use the indexOfObjectmethod the way I did there to locate a person by its name?
Any help is appreciated.
This is one of those hard problems you should avoid with some up-front design. If you know that you are putting things into a collection class and will need to get them out again based on some attribute (rather than by order of insertion) a dictionary is the most efficient collection class.
You can use a NSDictionary keyed with Person's name attribute. You can still iterate over all the objects but you will avoid having to search the whole collection. It can take a surprisingly long time to find a matching attribute in a NSArray! You wouldn't even have to change your Person object, just do
NSDictionary *peopleDictionary = #{ person1.name : person1, person2.name : person2 };
or add them one by one as they are created into a NSMutableArray.
You can try something like this assuming that 'name' is a property for your Person class.
NSUInteger i = 0;
for(Person *person in people) {
if([person.name isEqualToString:locatePerson]) {
break;
}
i++;
}

NSMutableArray or NSMutableDictionary : which is best for this scenario?

I need to scroll through several thousands of words to categorize them... to determine which words have the same pattern. (this part works)
For example, a four letter word that has two m's in 2nd & 4th position represent a pattern ("-m-m"). Once I have gone through all the words, I will know how many words there are for any given pattern. I am scrolling through now, but the problem I have is 'remembering' how many words I have in any given pattern.
I was thinking of using NSMutableDictionary and have the key be the pattern ('-m-m-') and the object represent the count of that pattern. This means every time I come across a pattern, I look up that pattern in the dictionary, get the key, increment the key, and put it back in the dictionary.
I need help with both the decision and syntax for performing this task.
Thank You
The answer to your question was this part of your (given) question "I will know how many words there are for any given pattern.". I would use an array of dictionary. You use the dictionary to store key value pair: a known pattern and the count. And you use the array to store those KVP records. So the next time you detect a pattern, search for the array for that record (dictionary), if found, increment the count. If not, create new record and set the count to 1.
Added sample code:
#define kPattern #"Pattern"
#define kPatternCount #"PatternCount"
-(NSMutableDictionary *)createANewDictionaryRecord:(NSString *) newPattern
{
int count = 1;
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
newPattern, kPattern,
[NSString stringWithFormat:#"%i",count], kPatternCount,
nil];
return myDictionary;
}
-(void)addANewPatternToArray:(NSMutableDictionary *)newDictionary
{
// NSMutableArray *myArrayOfDictionary = [[NSMutableArray alloc]init]; // you need to define it somewhere else and use property etc.
[self.myArrayOfDictionary addObject:newDictionary]; //or [self.myArrayOfDictionary addObject:newDictionary]; if you follow the recommendation above.
}
-(BOOL)existingPatternLookup:(NSString *)pattern
{
for (NSMutableDictionary *obj in self.myArrayOfDictionary)
{
if ([[obj objectForKey:kPattern] isEqual:pattern])
{
int count = [[obj objectForKey:kPatternCount] intValue] + 1;
[obj setValue:[NSString stringWithFormat:#"%i",count] forKey:kPatternCount];
return YES;
}
}
[self.myArrayOfDictionary addObject:[self createANewDictionaryRecord:pattern]];
return NO;
}
-(void)testData
{
NSMutableDictionary *newDict = [self createANewDictionaryRecord:#"mmm"];
[self addANewPatternToArray:newDict];
}
-(void) printArray
{
for (NSMutableDictionary * obj in self.myArrayOfDictionary)
{
NSLog(#"mydictionary: %#", obj);
}
}
- (IBAction)buttonPressed:(id)sender
{
if ([self existingPatternLookup:#"abc"])
{
[self printArray];
} else
{
[self printArray];
}
}
Not being an objective C expert but solving this problem in java before, I would say a dictionary(I used a map when doing it in java) is the best way. Check if the key(pattern) already exist if so increment that count else put a new one in the dictionary.
EDIT
If you want to not just get the count of a pattern, but in fact tell which words fall under that pattern, I would use a dictionary of strings to mutable arrays. In the arrays you store the words and the key to the array is the pattern(as a string), similar code as above but instead of just incrementing the count, you have to add the new word to the array.
The only difference in NSDictionary and NSMutableDictionary is that one can have objects added to it. I think your implementation is good, but English is a complex language. It would be more efficient to parse out the string with regex than to set a key for it.
Why don't you use NSCountedSet instead:
NSCountedSet Class Reference
..which is available in iOS 2.0 and later?
Each distinct object inserted into an NSCountedSet object has a counter associated with it. NSCountedSetkeeps track of the number of times objects are inserted [...] Thus, there is only one instance of an object in an NSSet object even if the object has been added to the set multiple times. The count method defined by the superclass NSSet has special significance; it returns the number of distinct objects, not the total number of times objects are represented in the set.
Then use:
- (NSUInteger)countForObject:(id)anObject
Use a dictionary of NSMutableArrays, and check for the existence of each search key as you recommended. If the key doesn't exist, add an NSMutableSet or NSMutableArray (depending on your needs) for the searched key type "-m-m" for example, and then add to the set or array for that key.

Resources