Objective-C: Relating NSStrings to each other? - ios

I am very new to code in general and I'm in the process of learning Objective-C, so I apologize in advance if I phrased this question incorrectly.
I have created several arrays with this goal in mind:
Show how many people there are and tell their names
Show how many cities there are and tell their names
Assign people to cities and calculate the populations
I have completed that task, but I have assigned each person individually to the array for each city. Is there a way that I can simply relate the NSStrings? For example "blockCity = bill, bob, jim" instead of creating a new array for populations?
// My people
NSString *bill = (#"Bill");
NSString *bob = (#"Bob");
NSString *jim = (#"Jim");
NSString *kevin = (#"kevin");
NSString *stacy = (#"stacy");
NSString *cooper = (#"Cooper");
NSMutableArray *people = [NSMutableArray arrayWithObjects: bill, bob, jim, kevin, stacy, cooper, nil];
NSLog(#"Here are my people: %#", people);
NSLog(#"I have %lu people", [people count]);
// My places
NSString *blockCity = (#"BlockCity");
NSString *hyperCity = (#"HyperCity");
NSString *pixelTown = (#"PixelTown");
NSString *nowhere = (#"Nowhere");
NSMutableArray *cities = [NSMutableArray arrayWithObjects: blockCity, hyperCity, pixelTown, nowhere, nil];
NSLog(#"Cities: %#", cities);
NSLog(#"There are %lu cities", [cities count]);
//populations
// BlockCity population
NSMutableArray *bcpop = [NSMutableArray arrayWithObjects: bill, bob, jim, nil];
if ([bcpop count] == 0) {
NSLog(#"%# is abandoned.", blockCity);
} else {
NSLog(#"%# has a population of %lu", blockCity, [bcpop count]);
}
//HyperCity population
NSMutableArray *hcpop = [NSMutableArray arrayWithObjects: nil];
if ([hcpop count] == 0) {
NSLog(#"%# is abandoned.", hyperCity);
} else {
NSLog(#"%# has a population of %lu", hyperCity, [hcpop count]);
}
//PixelTown population
NSMutableArray *ptpop = [NSMutableArray arrayWithObjects: kevin, stacy, cooper, nil];
if ([ptpop count] == 0) {
NSLog(#"%# is abandoned.", pixelTown);
} else {
NSLog(#"%# has a population of %lu", pixelTown, [ptpop count]);
}
//Nowhere population
NSMutableArray *npop = [NSMutableArray arrayWithObjects: nil];
if ([npop count] == 0) {
NSLog(#"%# is abandoned.", nowhere);
} else {
NSLog(#"%# has a population of %lu", nowhere, [npop count]);
}

No, there is no built-in way you can just “relate the NSStrings”.1
The appropriate thing to do here is to start thinking about your design in an object-oriented way.
What are the different sorts of objects in your program? There are people, so a Person class is appropriate. Give the Person class a name property (an NSString).
There are cities, so a City class is appropriate. Give the City class a name property (an NSString), an inhabitants property (an NSArray or NSMutableArray to be filled with references to Person objects), and a population method (returning unsigned long) that returns the number of inhabitants.
Once you have all that, you can even give City a populationDescription method that returns the string description of its population, like this:
- (NSString *)populationDescription {
if (self.population == 0) {
return [NSString stringWithFormat:#"%# is abandoned.", self.name];
} else {
return [NSString stringWithFormat:#"%# has a population of %lu", self.name, self.population);
}
}
Footnote 1. For the pedants: associated objects don't count. They are not appropriate for this and they are too advanced for a learner at this level.

I will recommend to use NSMutableDictionary in this case to relate people with their city. Something like:
NSMutableArray *people = [NSMutableArray arrayWithObjects: bill, bob, jim, kevin, stacy, cooper, nil];
NSMutableDictionary *cityWithPeople = [[NSMutableDictionary alloc] init];
[cityWithPeople setObject:people forKey:#"CityName"]; // to add people related to that city
NSArray peopleInCity=[cityWithPeople objectForKey:key]; // return array of people in city
Also read here to know how to use NSDictionary

Related

Filter NSMutableArray on the base of filtering another NSMutableArray

I have 3 NSMutableArrays of identical size. They are "linked" that means that for the corresponding index they have something related to each other.
tableData = [NSMutableArray arrayWithObjects:#"Egg Benedict", #"Mushroom Risotto", #"Full Breakfast", nil]
thumbnails = [NSMutableArray arrayWithObjects:#"egg_benedict.jpg", #"mushroom_risotto.jpg", #"full_breakfast.jpg",nil]
prepTime = [NSMutableArray arrayWithObjects:#"10min", #"15min", #"8min",nil]
This comes from a tutorial I'm playing on.
I'm filtering the tableData array like this:
NSPredicate *resultPredicate = [NSPredicate
predicateWithFormat:#"SELF contains[cd] %#",
searchText];
searchResultsData = [[tableData filteredArrayUsingPredicate:resultPredicate] mutableCopy];
where searchText is the string containing the filter (for example "egg").
This works great, I mean I have the correct filtering. (searchResultsData is another NSMutableArray)
What I need to do is filter the other two NSMutableArrays on the basis of the result got from the NSPredicate above.
So I created other two NSMutableArrays called "searchResultThumbnails" and "searchResultPrepTime".
I'm expecting this: if I filter using the word "egg" I want the first element containing "egg" from the "tableData" array (in this case only one element) and the correspondent element at index in the thumbnails and preptime arrays.
So after filtering with "Egg" the result should be:
searchResultData = "Egg"
searchResultThumbnails = "egg_benedict.jpg"
searchResultPrepTime = "10min"
Thank you for your help.
Believing "They are "linked" that means that for the corresponding index they have something related to each other." as your situation
NSPredicate *resultPredicate = [NSPredicate
predicateWithFormat:#"SELF contains[cd] %#",
searchText];
searchResultsData = [[tableData filteredArrayUsingPredicate:resultPredicate] mutableCopy];
NSString *searchedText = [searchResultsData objectAtIndex:0];
NSInteger index = [tableData indexOfObject:searchedText]; //if searchedText = "Egg"
NSString *thumb = [thumbnails objectAtIndex:index];
NSString *prep= [prepTime objectAtIndex:index];
But this is not a better way to do this.
You got couple of options like
You can use a custom Class which might have properties item, thumbnail, prepTime.
You can also use a Array of dictionaries similar to the following format,
array = (
{
searchResultData = "Egg"
searchResultThumbnails = "egg_benedict.jpg"
searchResultPrepTime = "10min"
}
{
searchResultData = "someItem"
searchResultThumbnails = "some.jpg"
searchResultPrepTime = "10min"
}
)
Try this:
NSArray* tableData = [NSMutableArray arrayWithObjects:#"Egg Benedict", #"Mushroom Risotto", #"Full Breakfast", nil];
NSArray* thumbnails = [NSMutableArray arrayWithObjects:#"egg_benedict.jpg", #"mushroom_risotto.jpg", #"full_breakfast.jpg",nil];
NSArray* prepTime = [NSMutableArray arrayWithObjects:#"10min", #"15min", #"8min",nil];
NSMutableArray *storedIndex = [NSMutableArray arrayWithCapacity:tableData.count];
for (NSUInteger i = 0 ; i != tableData.count ; i++) {
[storedIndex addObject:[NSNumber numberWithInteger:i]];
}
//Now you are going to sort tabledata.. with it we will sort storedIndexs
//suppose we will compare the strings for this time
[storedIndex sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
NSString *lhs = [[tableData objectAtIndex:[obj1 intValue]] lowercaseString];
NSString *rhs = [[tableData objectAtIndex:[obj2 intValue]] lowercaseString];
return [lhs compare:rhs];
}]; //now storedIndex are sorted according to sorted tableData array
NSMutableArray *sortedTableData = [NSMutableArray arrayWithCapacity:tableData.count];
NSMutableArray *sortedThumbnail = [NSMutableArray arrayWithCapacity:tableData.count];
NSMutableArray *sortedPrepTime = [NSMutableArray arrayWithCapacity:tableData.count];
[p enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSUInteger pos = [obj intValue];
[sortedTableData addObject:[tableData objectAtIndex:pos]];
[sortedThumbnail addObject:[thumbnails objectAtIndex:pos]];
[sortedPrepTime addObject:[prepTime objectAtIndex:pos]];
}];
//Now all will be correct index relation to each other as previous
It will work perfectly.
Happy coding. :)

How to remove all dictionaries from NSMutableArray based on the value in the dictionary

I am developing one iPad application.I have one NSMutableArray and NSMutableDictionary .These both are changeable based on the data from the web service.I need to remove some dictionary from my NSMutableArray based on the NSMutableDictionary values. Here I explain the situation through one example:
testArray =[{ language :"ESP"},{language :"ENG"},{language :"ENG"},{language :"FRH"}];
From the test array i need to remove the all Dictionaries which have key value language :"ENG".
I've written code like this:
for(int i =0;i<testArray.count;i++){
NSString *lang = [NSString stringWithFormat:#"%#", [testArray[i] objectForKey:#"language"]];
if([lang isEqualToString:#"ENG"]){
[testArray removeObjectAtIndex:i];
}
}
But it is not working. I think the problem is when I remove the dictionary from at index the array count is also reducing so the loop is executing based on new array count. Some help me to rewrite the code for get exact answer?
This is my favorite way, it's fast, clear and correct.
NSMutableArray *itemsToRemove = [NSMutableArray array];
for (id item in theArray) {
if ([item shouldBeRemoved])// Condition to check the key pair Value
[itemsToRemove addObject:item];
}
[theArray removeObjectsInArray:itemsToRemove];
Try this.
NSMutableArray *arrTemp = [[NSMutableArray alloc]initwithArray:testArray];
for(int i =0;i<testArray.count;i++){
NSString *lang = [NSString stringWithFormat:#"%#", [testArray[i] objectForKey:#"language"]];
if([lang isEqualToString:#"ENG"]){
[arrTemp removeObjectAtIndex:i];
}
}
[testArray removeAllObjects];
testArray = arrTemp;
for(int i =0;i<testArray.count;i++){
NSString *lang = [NSString stringWithFormat:#"%#", [testArray[i] objectForKey:#"language"]];
if([lang isEqualToString:#"ENG"]){
[testArray removeObjectAtIndex:i];
i--;
}
}
Replace your code with below code.
NSMutableArray *arrTemp = [NSMutableArray new];
for(int i =0;i<testArray.count;i++){
NSString *lang = [NSString stringWithFormat:#"%#", [testArray[i] objectForKey:#"language"]];
if([lang isEqualToString:#"ENG"]){
[arrTemp addObject:[NSNumber numberWithInt:i]];
}
}
for(int k=0;k<[arrTemp count];k++)
{
int ii = [[arrTemp objectAtIndex:k]intValue];
[testArray removeObjectAtIndex:ii];
}
let me know it is working or not!!!
Happy Coding!!!
I would implement that using NSPredicate:
NSMutableArray *testArray = [#[#{ #"language" :#"ESP"}, #{#"language" :#"ENG"},
#{#"language" :#"ENG"}, #{#"language" :#"FRH"}] mutableCopy];
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"language != %#", #"ENG" ];
testArray = [[testArray filteredArrayUsingPredicate:predicate] mutableCopy];
I have just tested it, it works (it is short and nice to read, but NSPredicate can be really slow).
Another way to do it is using enumerateObjectsWithOptions:usingBlock:
[testArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSDictionary *dict, NSUInteger index, BOOL *stop) {
if ([dict[#"language"] isEqualToString:#"ENG"]) {
[testArray removeObjectAtIndex:index];
}
}];
Pleace notice that i use NSEnumerationReverse as NSEnumerationOptions because according to docs of removeObjectAtIndex:- Method :
To fill the gap, all elements beyond index are moved by subtracting 1
from their index.

using nested fast enumeration takes too long, how to optimize?

I am doing this is soon as the app starts, luckily I have to do it only once in a singleton class called CMIDataManager, my app is taking too long to launch.
The plist contains:
Commanders.plist:
German - Array
Soviet - Array
each commander array has 19 commander and each commander has 5 abilities (mapping through a unique ability uid).
Abilities.plist:
GermanAbilities - Array
SovietAbilities - Array
Each array contains 40 abilities with a uid (used for mapping commanders to abilities)
At the start, I need to make a model class, so I iterate commander's abilities uid against each Ability hid, once a match is found I add the ability model object to Commaders model object.
How can I do it faster? Would using block based enumeration speed it up? How can I use it?
-(void)loadCommandersAndAbilities{
#pragma German Abilities iteration
NSString* abilitiesPlistPath = [[NSBundle mainBundle] pathForResource:#"Abilities" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:abilitiesPlistPath];
NSArray *tempArray = [dictionary objectForKey:#"GermanAbilities"];
NSArray *tempArray2 = [dictionary objectForKey:#"SovietAbilities"];
NSMutableArray *tempAbilitiesArray = [[NSMutableArray alloc] initWithCapacity:tempArray.count];
for (NSDictionary *dict in tempArray) {
Ability *ability = [[Ability alloc] init];
[ability populateWithDictionary:dict];
[tempAbilitiesArray addObject:ability];
NSLog(#"Adding object %# to temp abilities",ability.name);
}
self.germanAbilitiesArray = [NSArray arrayWithArray:tempAbilitiesArray];
[tempAbilitiesArray removeAllObjects];
#pragma Soviet abilities iteration
for (NSDictionary *dict in tempArray2) {
Ability *ability = [[Ability alloc] init];
[ability populateWithDictionary:dict];
[tempAbilitiesArray addObject:ability];
}
self.sovietAbilitiesArray = [NSArray arrayWithArray:tempAbilitiesArray];
#pragma German commander itertation
NSString* commandersPlistPath = [[NSBundle mainBundle] pathForResource:#"Commanders" ofType:#"plist"];
dictionary = [[NSDictionary alloc] initWithContentsOfFile:commandersPlistPath];
tempArray = [dictionary objectForKey:#"German"];
tempArray2 = [dictionary objectForKey:#"Soviet"];
NSLog(#"Temp German commadner array is %#", tempArray);
NSLog(#"Temp Soviet commadner array is %#", tempArray2);
NSMutableArray *tempCommandersArray = [[NSMutableArray alloc] initWithCapacity:tempArray.count];
NSMutableArray *tempCommandersArray2 = [[NSMutableArray alloc] initWithCapacity:tempArray2.count];
for (NSDictionary *dict in tempArray) {
Commander *commander = [[Commander alloc] init];
[commander populateWithDictionary:dict];
for (NSNumber *uid in commander.abilitiesUIDArray) {
NSLog(#"uid %#", uid);
for (Ability *ability in self.germanAbilitiesArray) {
NSLog(#"ability uid is : %#, target uid %# ",ability.uid, uid);
if ([ability.uid isEqualToNumber: uid]) {
NSLog(#"Adding abilty %# to commander %#: ",ability.name, commander.name);
[commander.abilitiesArray addObject:ability];
NSLog(#"Current commander abilty array is %#: ",commander.abilitiesArray);
}
}
}
[tempCommandersArray addObject:commander];
}
self.germanCommandersArray = [NSArray arrayWithArray:tempCommandersArray];
NSLog(#"Final german Commaders %#",self.germanCommandersArray);
#pragma Soviet commander itertation
for (NSDictionary *dict in tempArray2) {
Commander *commander = [[Commander alloc] init];
[commander populateWithDictionary:dict];
for (NSNumber *uid in commander.abilitiesUIDArray) {
NSLog(#"uid %#", uid);
for (Ability *ability in self.sovietAbilitiesArray) {
NSLog(#"ability uid is : %#, target uid %# ",ability.uid, uid);
if ([ability.uid isEqualToNumber: uid]) {
NSLog(#"Adding abilty %# to commander %#: ",ability.name, commander.name);
[commander.abilitiesArray addObject:ability];
NSLog(#"Current commander abilty array is %#: ",commander.abilitiesArray);
}
}
}
[tempCommandersArray2 addObject:commander];
}
self.sovietCommandersArray = [NSArray arrayWithArray:tempCommandersArray2];
NSLog(#"Final Soviet Commaders %#",self.germanCommandersArray);
}
Adding images:
The obvious thing is that your abilities array shouldn't be an array but a dictionary. That way you don't compare the uid with the uid of every ability, but look it up in a single operation.
seems like the issues was in this loop:
if ([ability.uid isEqualToNumber: uid]) {
[commander.abilitiesArray addObject:ability];
}
once i have find a match for commander's ability in the list of all abilities, I don't need to check for that ability to match with rest of the abilities, so I added a break statement.
if ([ability.uid isEqualToNumber: uid]) {
//NSLog(#"Adding abilty %# to commander %#: ",ability.name, commander.name);
[commander.abilitiesArray addObject:ability];
//NSLog(#"Current commander abilty array is %#: ",commander.abilitiesArray);
break;
}
I also added this to the code to make it run on background thread, bringing the launch time down from 6 s to .5 second.
-(instancetype)init {
self = [super init];
if(self) {
[self performSelectorInBackground:#selector(loadCommandersAndAbilities) withObject:nil];
//[self loadCommandersAndAbilities];
// NSOperationQueue
}
return self;
}
loadCommandersAndAbilities: is the method listed in the original question, I also added notifications to let my view controller know when the method has finished.
//end of loadCommandersAndAbilities
[[NSNotificationCenter defaultCenter] postNotificationName:#"TableViewDataDownloaded" object:nil];

Sorting one array in the same order as another array

I have two arrays. Let's say:
array = "Dave", "Mike", "Joe", "Jason", "Kevin"
and
IQ = 110, 145, 75, 122, 130
I want to sort them by IQ. Say Highest to lowest. I can sort one array... and then I go back and check to see what position it was in and then rearrange the other array. It seems like there must be a better way to do this. Especially if the array gets larger.
Here is how I'm doing it right now.
d1, d2, d3, d4, d5 are my IQ variable. I use the sortBack array to rearrange another array in the same order.
NSMutableArray *myArray = [NSMutableArray arrayWithObjects:[NSString stringWithFormat:#"%d",d1], [NSString stringWithFormat:#"%d",d2],[NSString stringWithFormat:#"%d",d3], [NSString stringWithFormat:#"%d",d4],[NSString stringWithFormat:#"%d",d5], nil];
//sorting
[myArray sortUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2) {
return [str1 compare:str2 options:(NSNumericSearch)];
}];
for(int i=0;i<5;i++)
{
if([[myArray objectAtIndex:i] integerValue]==d1)
{
sortBackArray[i]=1;
}
else if([[myArray objectAtIndex:i] integerValue]==d2)
{
sortBackArray[i]=2;
}
else if([[myArray objectAtIndex:i] integerValue]==d3)
{
sortBackArray[i]=3;
}
else if([[myArray objectAtIndex:i] integerValue]==d4)
{
sortBackArray[i]=4;
}
else if([[myArray objectAtIndex:i] integerValue]==d5)
{
sortBackArray[i]=5;
}
}
This would be a better way to make a dictionary for users. And then sorting based on their specific values like IQ, Name etc.
NSArray *users = #[#"Dave",#"Mike",#"Joe",#"Jason",#"Kevin"];
NSArray *iqs = #[#110,#145,#75,#122,#130];
NSMutableArray *array = [NSMutableArray array];
for (int idx = 0;idx<[users count];idx++) {
NSDictionary *dict = #{#"Name": users[idx],#"IQ":iqs[idx]};
[array addObject:dict];
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"IQ" ascending:NO];
[array sortUsingDescriptors:#[descriptor]];
Construct a 2d array with first row as names and second row as IQ
Sort this array according to the IQ in O(nlogn)

Core data filtering

I have a core data structure as follows:
Business <-------->> Employee <-------->> Address
Each business has multiple employees and each employee can have multiple addresses.
From the Business object, I want to be able to get an NSArray or NSSet of all the Address objects that specify a certain condition. E.g. All the street names have to be unique.
I know that I could override the isEqual: but I'm guessing this is going to have unintended results. Otherwise, I have been looking into using valueForKeyPath:#"#distinctUnionOfObjects", but I don't think I can pass a condition.
Here is some code that I have so far:
NSMutableArray *addressArray = [NSMutableArray array];
NSArray *employees = [Employee sortedArray];
//loop through employees
for (Employee *employee in employees) {
for (Address *address in employee.addresses) {
[addressArray addObject:address];
}
}
//filter out duplicates
addressArray = [addressArray valueForKeyPath:#"#distinctUnionOfObjects.city"];
This code gives me a list of unique cities, however, I want a collection containing Address objects that have unique city values (or some other condition).
I found a way to do this with the LinqToObjectiveC library:
NSArray* addressesWithUniqueCities = [input distinct:^id(id address) {
return [address city];
}];
Looking at the source, the underlying implementation is as follows:
typedef id (^Selector)(id item);
-(NSArray *)distinct:(Selector)keySelector
{
NSMutableSet* keyValues = [[NSMutableSet alloc] init];
NSMutableArray* distinctSet = [[NSMutableArray alloc] init];
for (id item in self) {
id keyForItem = keySelector(item);
if (![keyValues containsObject:keyForItem]) {
[distinctSet addObject:item];
[keyValues addObject:keyForItem];
}
}
return distinctSet;
}
My final code ended up being:
NSMutableArray *addressArray = [NSMutableArray array];
NSArray *employees = [Employee sortedEmployees];
//loop through employees
for (Employee *employee in employees) {
for (Address *address in employee.addresses) {
[addressArray addObject:address];
}
}
//filter out duplicates
NSArray *distinctAddressArray = [addressArray distinct:^id(id address) {
return [address addressLine];
}];
return distinctAddressArray;
I'm not sure this is what you meant but you could try something like: (untested)
+ (NSMutableArray*) addressesForBusiness:(Business*)business
sectionProperty:(NSString*)sectionProperty
{
NSFetchRequest* request = [[NSFetchRequest alloc] initWithEntityName:#"Address"];
request.predicate = [NSPredicate predicateWithFormat:#"employee.business == %#",business.objectID];
request.sortDescriptors = #[[NSSortDescriptor sortDescriptorWithKey:sectionProperty ascending:YES]];
NSArray* addresses = [business.managedObjectContext executeFetchRequest:request error:NULL];
NSMutableArray* sections = [NSMutableArray new];
NSMutableArray* currentSection = [NSMutableArray new];
NSManagedObject* prevAddress = nil;
for (NSManagedObject* address in addresses) {
if (prevAddress && ([[address valueForKey:sectionProperty] isEqual:[prevAddress valueForKey:sectionProperty]])) {
currentSection = [NSMutableArray new];
[sections addObject:currentSection];
}
prevAddress = address;
[currentSection addObject:address];
}
return sections;
}
This will return an array of arrays where each internal array hold objects with the same property value. In your example you could call:
[[self class] addressesForBusiness:someBusiness sectionProperty:addressLine];

Resources