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];
Related
I have an NSMutableArray of custom objects. Each contains an ID string that is unique, and each contains an downloadedDate property.
Objects could get added twice so I need to check the ID for duplicates, and if I find a duplicate I need to keep the one that has the newest date.
Currently I am doing the following to remove duplicates, but it doesn't take into account keeping the object with the newest date.
NSArray *originalArray = [NSArray arrayWithArray:mutableItems];
NSMutableArray *uniqueArray = [NSMutableArray array];
NSMutableSet *names = [NSMutableSet set];
for (ZSSObject *g in originalArray) {
NSString *destinationName = g.code;
if (![names containsObject:destinationName]) {
[uniqueArray addObject:g];
[names addObject:destinationName];
}
}
NSArray *uniqueObjects = uniqueArray;
Objects get created like this:
ZSSObject *obj = [ZSSObject alloc] init];
obj.code = #"12345";
obj.downloadedDate = [NSDate date];
Is there an easier way to do that I want than having a bunch of copies of my array and nested loops?
Using the suggestion to use an NSDictionary instead, I came up with this solution:
NSMutableDictionary *objDict = [[NSMutableDictionary alloc] init];
for (ZSSObject *g in mutableItems) {
ZSSObject *addedObj = objDict[g.code];
if (addedObj) {
// Compare dates
if ([addedObj respondsToSelector:#selector(dateDownloaded)]) {
if ([g.dateDownloaded compare:addedObj.dateDownloaded] == NSOrderedDescending) {
[objDict setObject:g forKey:g.code];
}
}
} else {
[objDict setObject:g forKey:g.code];
}
}
NSArray *uniqueObj = objDict.allValues;
I am trying to take an array and merge it into an array of dictionaries but unsure as to how to do it.
I have an array of dictionaries that looks like this:
(
{
caption = a;
urlRep = "12";
},
{
caption = b;
urlRep = "34";
},
{
caption = c;
urlRep = "56";
}
)
and given an array like this:
(12,34,56,78)
I want to merge it into my dictionaries to make it look like this:
(
{
caption = a;
urlRep = "12";
},
{
caption = b;
urlRep = "34";
},
{
caption = c;
urlRep = "56";
},
{
caption = "";
urlRep = "78";
}
)
edit:
I need to also consider removing from the array of dicts if the given array does not contain one of the urlReps.
Any help would be greatly appreciated as I've been stuck trying to figure this out for some time.
Here's a simple, efficient and elegant solution using NSSets to handle unique keys:
NSMutableArray *arrayOfDicts; // your input array of dictionaries
NSArray *urlRepArray; // the new array with string elements
// create a set of potentially new keys (urlReps)
NSMutableSet *urlReps = [NSMutableSet setWithArray:urlRepArray];
// remove existing keys from your original array
[urlReps minusSet:[NSSet setWithArray:[arrayOfDicts valueForKey:#"urlRep"]]];
// merge new dicts to the original array
for (id urlRep in urlReps)
[arrayOfDicts addObject:#{ #"urlRep" : urlRep, #"caption" : #"" }];
Easiest way AFAIK, Filter using valueForKeyPath
//Your array of dictionary I created here for debugging purpose.
NSArray *tmpArray = #[ #{#"caption":#"a",#"urlRep":#"12"},
#{#"caption":#"b",#"urlRep":#"34"},
#{#"caption":#"c",#"urlRep":#"56"}];
//This will give you 12,34,56 in your case
NSArray *existingURLRep = [tmpArray valueForKeyPath:#"urlRep"];
NSMutableArray *targetArray = [[NSMutableArray alloc] initWithObjects:#12, #34,#56, #78, nil]; //Assuming you have your array as you said
[targetArray removeObjectsInArray:existingURLRep];
//remove existing items you will have 78 here now loop through
//this targetArray and add it to your array of dictionary.
(void)filterArray{
NSLog(#"Array before filtering = %#",initialArray);
NSLog(#"given Array = %#",givenArray);
NSMutableSet *urlReps = [NSMutableSet setWithArray:givenArray];
// remove existing records
[urlReps minusSet:[NSSet setWithArray:[initialArray valueForKey:#"urlRep"]]];
// adding new objects
for (id obj in urlReps) {
[initialArray addObject:#{#"caption":#"", #"urlRep" : obj}];
}
// removing objects
NSMutableSet *set = [[NSMutableSet alloc] init];
for (id obj in initialArray) {
NSDictionary *dict = (NSDictionary *)obj;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self = %#", dict[#"urlRep"]];
NSArray *filteredArray = [givenArray filteredArrayUsingPredicate:predicate];
if(filteredArray.count == 0) {
[set addObject:dict];
}
}
[initialArray removeObjectsInArray:[set allObjects]];
NSLog(#"Array after filtering = %#",initialArray);
}
NSMutableArray *yourArray;//This will be your original array of dictionary.
NSArray *newArray;//This is your new array which you want to add.
for(id obj in newArray) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"urlRep = %#", id];
NSArray *filteredArray = [locationsArray filteredArrayUsingPredicate:predicate];
if(filteredArray.count == 0) {
[yourArray addObject:#{#"caption":#"", #"urlRep" : id}];
}
}
/*
NSArray *inputArray;//(12,34,56,78)- I assumes you are having array which contains strings. If you are having number then modify the code as you needed
NSMutableArray *colloectionArray;// your total collection
NSMutableArray *tobeMerged;
*/
// Extract the dictionary set only to be merged
for (NSString* aNumber in inputArray) {
for (NSDictionary *aItem in colloectionArray) {
NSString *urlRep= [aItem valueForKey:#"urlRep"];
if (![urlRep isEqualToString:aNumber]) {
[tobeMerged addObject:urlRep];
}
}
}
// Add missed items in collection
for (NSString *aNumber in tobeMerged) {
NSMutableDictionary *newset = [[NSMutableDictionary alloc]init];
[newset setObject:#"" forKey:#"caption"];
[newset setObject:aNumber forKey:#"urlRep"];
[colloectionArray addObject:newset];
}
I have a NSArray that looks like this:
{"result":
[
{
"epoch":"1371333600"
},
{
"epoch":"1371420000"
},
{
"epoch":"1371333600"
}
]
}
I want to sort the NSArray and make a new one so i can use it easier with the tableview methods to count the sections and rows.
All the dates that are the same need to be in one section.
The array should look like the example below but i don’t know how to get there. I have tried NSPredicate and used a loop but it won’t work.
What i want:
{"result":
[
{"data":
[
{
"epoch":"1371333600"
},
{
"epoch":"1371333600"
}
]
},
{"data":
[
{
"epoch":"1371420000"
}
]
}
]
}
My NSPredicate looks like this, but does not give me the result.
_finalArray = [[NSMutableArray alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"epoch IN %#", [_resultArray valueForKey:#"epoch"]];
_predicateDate = [NSMutableArray arrayWithArray:[dataSortArray filteredArrayUsingPredicate:predicate]];
if ([_predicateDate count] != 0)
{
NSDictionary *itemsArrayDict = [NSDictionary dictionaryWithObject:_predicateDate forKey:#"data"];
[_finalArray addObject:itemsArrayDict];
}
NSOrderedSet is awesome for this occasion as it allows you to get the unique strings.
NSDictionary *dict1 = [NSDictionary dictionaryWithObject:#"2222222" forKey:#"epoch"];
NSDictionary *dict2 = [NSDictionary dictionaryWithObject:#"2222222" forKey:#"epoch"];
NSDictionary *dict3 = [NSDictionary dictionaryWithObject:#"1111111" forKey:#"epoch"];
NSArray *dictArray = #[dict1, dict2, dict3];
NSMutableArray *finalArray = [[NSMutableArray alloc]init];
NSArray *epoches = [dictArray valueForKey:#"epoch"];
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:epoches];
for (NSString *string in orderedSet) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"epoch == %#", string];
NSArray *resultsArray = [dictArray filteredArrayUsingPredicate:predicate];
[finalArray addObject:resultsArray];
}
Hi you can use the NSPredicate to filter an array like:
//NSPredicate to filter an array
NSArray *data = [NSArray arrayWithObject:[NSMutableDictionary dictionaryWithObject:#"hello" forKey:#"Test"]];
NSArray *filtered = [data filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(Test == %#)", #"hello"]];
Thanks
It appears that you are using dictionaries along with arrays. Here's what I've achieved:
(result
(data
{
epoch = 1371333600;
},
{
epoch = 1371333600;
}
),
(data
{
epoch = 1371420000;
}
)
)
I'm not using predicates, but it looks as it's working :). Here is the code:
// Initial input
NSArray *result = #[#{#"epoch":#"1371333600"},#{#"epoch":#"1371420000"},#{#"epoch":#"1371333600"}];
// Get unique values for the input
NSSet *resultSet = [NSSet setWithArray:result];
// Here we are going to store the final result
NSMutableArray *newResult = [[NSMutableArray alloc] init];
// Loop over the unique items
for (NSDictionary *uniqueItem in resultSet) {
// Here we are going to store the grouped data
NSMutableArray *dataResult = [[NSMutableArray alloc] init];
// Loop over the initial input
for (NSDictionary *resultItem in result) {
// Search for all the items that are equal to the uniqe
// I would rather include a count instead of repeating values :)
if([uniqueItem isEqual:resultItem]) {
[dataResult addObject:resultItem];
}
}
[newResult addObject:dataResult];
}
NSLog(#"%#", newResult);
Cheers!
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
We have an app that calls a SOAP web service and retrieves a long list of XML, which the app then parses into an NSArray of NSDictionary objects. The NSArray contains a list of Rental Apartment information, each of which is stored into an NSDictionary.
The entire list may contain 10 different types of Apartments (i.e. 2-room, 3-room), and we need to split the NSArray into smaller NSArrays based on Room-Type, which has the key "roomType" in the NSDictionary objects.
Currently our algorithm is
Use [NSArray valueForKeyPath:#"#distinctUnionofObjects.room-type"]
to obtain a list of unique room-type values.
Loop through the list of unique room-type values
For each unique room-type value, use NSPredicate to retrieve matching items from the Original list
Our code is below (renamed for clarity):
NSArray *arrOriginal = ... ...; // Contains the Parsed XML list
NSMutableArray *marrApartmentsByRoomType = [NSMutableArray arrayWithCapacity:10];
NSMutableArray *arrRoomTypes = [arrOriginal valueForKeyPath:#"distinctUnionOfObjects.roomType"];
for(NSString *strRoomType in arrRoomTypes) {
NSPredicate *predicateRoomType = [NSPredicate predicateWithFormat:#"roomType=%#", strRoomType];
NSArray *arrApartmentsThatMatchRoomType = [arrOriginal filteredArrayUsingPredicate:predicateRoomType]; // TAKES A LONG TIME EACH LOOP-ROUND
[marrApartmentsByRoomType addObject:arrApartmentsThatMatchRoomType];
}
However, step 3 is taking a long time as the original list may contain large amount (>100,000) of items. It seems that NSPredicate goes through the entire list for each key value. Is there a more efficient way of splitting a large NSArray into smaller NSArrays, based on NSDictionary keys?
If the order of your splited Arrays is not important, i have a solution for you:
NSArray *arrOriginal;
NSMutableDictionary *grouped = [[NSMutableDictionary alloc] initWithCapacity:arrOriginal.count];
for (NSDictionary *dict in arrOriginal) {
id key = [dict valueForKey:#"roomType"];
NSMutableArray *tmp = [grouped objectForKey:key];
if (tmp == nil) {
tmp = [[NSMutableArray alloc] init];
[grouped setObject:tmp forKey:key];
}
[tmp addObject:dict];
}
NSMutableArray *marrApartmentsByRoomType = [grouped allValues];
This is quite performant
- (NSDictionary *)groupObjectsInArray:(NSArray *)array byKey:(id <NSCopying> (^)(id item))keyForItemBlock
{
NSMutableDictionary *groupedItems = [NSMutableDictionary new];
for (id item in array) {
id <NSCopying> key = keyForItemBlock(item);
NSParameterAssert(key);
NSMutableArray *arrayForKey = groupedItems[key];
if (arrayForKey == nil) {
arrayForKey = [NSMutableArray new];
groupedItems[key] = arrayForKey;
}
[arrayForKey addObject:item];
}
return groupedItems;
}
Improving #Jonathan answer
Converting array to dictionary
Maintaining the same order as it was in original array
//only to a take unique keys. (key order should be maintained)
NSMutableArray *aMutableArray = [[NSMutableArray alloc]init];
NSMutableDictionary *dictFromArray = [NSMutableDictionary dictionary];
for (NSDictionary *eachDict in arrOriginal) {
//Collecting all unique key in order of initial array
NSString *eachKey = [eachDict objectForKey:#"roomType"];
if (![aMutableArray containsObject:eachKey]) {
[aMutableArray addObject:eachKey];
}
NSMutableArray *tmp = [grouped objectForKey:key];
tmp = [dictFromArray objectForKey:eachKey];
if (!tmp) {
tmp = [NSMutableArray array];
[dictFromArray setObject:tmp forKey:eachKey];
}
[tmp addObject:eachDict];
}
//NSLog(#"dictFromArray %#",dictFromArray);
//NSLog(#"Unique Keys :: %#",aMutableArray);
//Converting from dictionary to array again...
self.finalArray = [[NSMutableArray alloc]init];
for (NSString *uniqueKey in aMutableArray) {
NSDictionary *aUniqueKeyDict = #{#"groupKey":uniqueKey,#"featureValues":[dictFromArray objectForKey:uniqueKey]};
[self.finalArray addObject:aUniqueKeyDict];
}
Hope, It will help when client wants final array in same order as input array.