how to remove an NSMutableArray object by key? - objective-c

i have structured an NSMutableArray and here is an example
(
{
Account = A;
Type = Electricity;
},
{
Account = B;
Type = Water;
},
{
Account = C;
Type = Mobile;
} )
when i try to delete Account B using
[data removeObject:#"B"];
Nothing Happens
[[NSUserDefaults standardUserDefaults] synchronize];
NSArray *archivedArray = [NSKeyedUnarchiver unarchiveObjectWithFile:[self dataFilePath]];
if (archivedArray == nil) {
data = [[NSMutableArray alloc] init];
} else {
data = [[NSMutableArray alloc] initWithArray:archivedArray];
}

If you're actually using an array and not a dictionary, you need to search for the item before you can remove it:
NSUInteger index = [data indexOfObjectPassingTest:^BOOL (id obj, NSUInteger idx, BOOL *stop) {
return [[(NSDictionary *)obj objectForKey:#"Account"] isEqualToString:#"B"];
}];
if (index != NSNotFound) {
[data removeObjectAtIndex:index];
}

Alternative: try a NSMutableDictionary:
NSArray *accounts = [NSArray arrayWithObjects:#"A", #"B", #"C", nil];
NSArray *types = [NSArray arrayWithObjects:#"Electricity", #"Water", #"Mobile", nil];
NSMutableDictionary* data = [NSMutableDictionary dictionaryWithObjects:types forKeys:accounts];
[data removeObjectForKey:#"B"];

An NSArray is like a list of pointers, each pointer points to an object.
If you call:
[someArray removeObject:#"B"];
You create a new NSString object that contains the string "B". The address to this object is different from the NSString object in the array. Therefore NSArray cannot find it.
You will need to loop through the array and determine where the object is located, then you simply remove it by using removeObjectAtIndex:

Related

Reading from SQL database into NSArray

I have an iPad that reads data from an SQL database. The following code works fine and retrieves 2 fields from each record and reads them into an NSArray.
I now need to read 5 of the fields and I can't help but think that there is a better way of doing it rather than running 5 separate queries through php (the getinfo.php file with the choice parameter set to pick the different fields).
Any pointers to a better method for doing this?
NSString *strURLClass = [NSString stringWithFormat:#"%#%#", #"http://wwwaddress/getinfo.php?choice=1&schoolname=",obsSchoolName];
NSArray *observationsArrayClass = [[NSMutableArray alloc] initWithContentsOfURL:[NSURL URLWithString:strURLClass]];
observationListFromSQL = [[NSMutableArray alloc]init];
NSEnumerator *enumForObsClass = [observationsArrayClass objectEnumerator];
NSString *strURLDate = [NSString stringWithFormat:#"%#%#", #"http://wwwaddress/getinfo.php?choice=5&schoolname=",obsSchoolName];
NSArray *observationsArrayDate = [[NSMutableArray alloc] initWithContentsOfURL:[NSURL URLWithString:strURLDate]];
observationListFromSQL = [[NSMutableArray alloc]init];
NSEnumerator *enumForObsDate = [observationsArrayDate objectEnumerator];
id className, dateOfObs;
while (className = [enumForObsClass nextObject])
{
dateOfObs = [enumForObsDate nextObject];
[observationListFromSQL addObject:[NSDictionary dictionaryWithObjectsAndKeys:className, #"obsClass", dateOfObs, #"obsDate",nil]];
}
Yes, you can do that with less code by "folding" the statements into a loop, and using a mutable dictionary:
// Add other items that you wish to retrieve to the two arrays below:
NSArray *keys = #[#"obsClass", #"obsDate"]; // Key in the dictionary
NSArray *choices = #[#1, #5]; // Choice in the URL string
NSMutableArray *res = [NSMutableArray array];
NSMutableArray *observationListFromSQL = [NSMutableArray array];
for (int i = 0 ; i != keys.count ; i++) {
NSNumber *choice = choices[i];
NSString *strURLClass = [NSString stringWithFormat:#"http://wwwaddress/getinfo.php?choice=%#&schoolname=%#", choice, obsSchoolName];
NSArray *observationsArray = [[NSMutableArray alloc] initWithContentsOfURL:[NSURL URLWithString:strURLClass]];
NSEnumerator *objEnum = [observationsArrayClass objectEnumerator];
NSString *key = keys[i];
NSMutableDictionary *dict;
if (res.count < i) {
dict = res[i];
} else {
dict = [NSMutableDictionary dictionary];
[res addObject:dict];
}
id item;
while (item = [objEnum nextObject]) {
[res setObject:item forKey:key];
}
}

NSArray and NSDictionary

I have NSDictionaries in NSArray just like below.
array(dictionary("user":1, "p1":1), dictionary("user":2, "p1":3),
dictionary("user":1, "p1":5), dictionary("user":2, "p1":7))
And I want to turn this array into dictionary like below.
NSArray *u1 = [NSArray arrayWithObjects:#"1", #"5", nil];
NSArray *u2 = [NSArray arrayWithObjects:#"3", #"7", nil];
keys = [NSArray arrayWithObjects:#"u1", #"u2", nil];
points = [NSDictionary dictionaryWithObjectsAndKeys:u1, #"u1", u2, #"u2", nil];
How can I do that? I am lost, can you guys please help me?
Couldn't you just iterate over your original array, asking each dictionary if the object for key "user" is 1, and if so, copy the object into a new array at index 0? Or if your user numbers are in counting order, maybe even have the index number equal the user number. Then repeat for "user" = 2, etc. Then make a dictionary so that each key/object pair is created by keys from the keys array (keys[i]) and objects from your new array (objects[i]).
What have you tried?
Here is some code typed directly into the answer, so it has not be tested:
You haven't given a name for your original array, so let's assume it is:
NSArray *originalArray;
We need a mutable dictionary to store the result:
NSMutableDictionary *points = [NSMutableDictionary new];
Now we need to process every element in the original array and it is a dictionary:
for(NSDictionary *item in originalArray)
{
Get the current entry in points array that matches item. You don't give types for your entries, so we'll use id:
id currentUser = [item objectForKey:#"user"];
NSMutableArray *currentValues = [points objectForKey:currentUser];
If this is the first occurrence of currentUser then currentValues will be nil, and we need to create an array for the p1 value and add it to points:
if (currentValues == nil)
[points addObject:[NSMutableArray arrayWithObject:[item objectForKey:#"p1"]
forKey:currentUser
]
]
Otherwise we just add the p1 value to the array:
else
[currentValues setObject:[item objectForKey:#"p1"]];
close out the loop and get the keys:
}
NSArray *keys = [points allKeys];
Now if you're using Xcode 4.5 you can use modern syntax for some of that:
NSMutableDictionary *points = [NSMutableDictionary new];
for(NSDictionary *item in originalArray)
{
id currentUser = item[#"user"];
NSMutableArray *currentValues = points[currentUser];
if (currentValues == nil)
points[currentUser] = [NSMutableArray arrayWithObject:item[#"p1"];
else
[currentValues addObject:item[#"p1"]];
}
NSArray *keys = [points allKeys];
HTH
Another possible solution (works with an arbitrary number of users):
NSArray *orig = #[
#{#"user" : #"1", #"p1" : #"1"},
#{#"user" : #"2", #"p1" : #"3"},
#{#"user" : #"1", #"p1" : #"5"},
#{#"user" : #"2", #"p1" : #"7"},
];
// Create set of all users (without duplicates)
NSSet *users = [NSSet setWithArray:[orig valueForKey:#"user"]];
NSMutableDictionary *points = [NSMutableDictionary dictionary];
for (NSString *user in users) {
// newKey = "u" + username, e.g. "u1" or "u2":
NSString *newKey = [#"u" stringByAppendingString:user];
// newValue = array of "p1" values of the current user:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"user == %#", user];
NSArray *newValue = [[orig filteredArrayUsingPredicate:pred] valueForKey:#"p1"];
// Add to dictionary:
[points setObject:newValue forKey:newKey];
}
NSLog(#"%#", points);
Output:
{
u1 = (
1,
5
);
u2 = (
3,
7
);
}
And the keys can be obtained by
NSArray *keys = [points allKeys];
You can do, like this (code not tested)
NSMutableArray *keys=[NSMutableArray new];
NSMutableArray *u1=[NSMutableArray new];
NSMutableArray *u2=[NSMutableArray new];
NSMutableDictionary *points=[NSMutableDictionary new];
for (id dict in array){
NSString *user=[dict objectForKey:#"user"];
NSString *p1=[dict objectForKey:#"p1"];
[keys addObject:[NSString stringWithFormat:#"%#",user]];
if( [user isEqualToString:#"1"] ){
[u1 addObject:user];
}
else{
[u2 addObject:user];
}
}
points=[NSDictionary dictionaryWithObjectsAndKeys:u1,#"u1",u2, #"u2", nil];
Tons of approaches. Here's another:
NSArray *originalArray = #[
#{#"user":#"u1", #"p1":#"1"},
#{#"user":#"u2", #"p1":#"3"},
#{#"user":#"u1", #"p1":#"5"},
#{#"user":#"u2", #"p1":#"7"}
];
NSLog(#"originalArray = %#", originalArray);
NSMutableDictionary *results = [NSMutableDictionary dictionary];
for (NSDictionary *dictionary in originalArray) {
NSString *user = dictionary[#"user"];
NSString *p1 = dictionary[#"p1"];
if (!results[user])
results[user] = [NSMutableArray array];
[results[user] addObject:p1];
}
NSLog(#"results = %#", results);
That takes:
originalArray = (
{
p1 = 1;
user = u1;
},
{
p1 = 3;
user = u2;
},
{
p1 = 5;
user = u1;
},
{
p1 = 7;
user = u2;
}
)
And gives
results = {
u1 = (
1,
5
);
u2 = (
3,
7
);
}

Sort NSArray of custom objects based on sorting of another NSArray of strings

I have two NSArray objects that I would like to be sorted the same. One contains NSString objects, the other custom Attribute objects. Here is what my "key" NSArray looks like:
// The master order
NSArray *stringOrder = [NSArray arrayWithObjects:#"12", #"10", #"2", nil];
The NSArray with custom objects:
// The array of custom Attribute objects that I want sorted by the stringOrder array
NSMutableArray *items = [[NSMutableArray alloc] init];
Attribute *attribute = nil;
attribute = [[Attribute alloc] init];
attribute.assetID = #"10";
[items addObject:attribute];
attribute = [[Attribute alloc] init];
attribute.assetID = #"12";
[items addObject:attribute];
attribute = [[Attribute alloc] init];
attribute.assetID = #"2";
[items addObject:attribute];
So, what I would like to do is use the stringOrder array to determine the sorting of the items array of custom objects.
How can I do this?
Hereby, I compare directly the index of obj1.assetID in stringOrder with the index of obj2.assetID in stringOrder (using Objective-C literals for #() to transform NSString => NSNumber)
[items sortUsingComparator:^NSComparisonResult(Attribute *obj1, Attribute *obj2) {
return [#([stringOrder indexOfObject:obj1.assetID]) compare:#([stringOrder indexOfObject:obj2.assetID])]
}];
Or without ObjC literals :
[items sortUsingComparator:^NSComparisonResult(Attribute *obj1, Attribute *obj2) {
return [[NSNumber numberWithInt:[stringOrder indexOfObject:obj1.assetID]] compare:[NSNumber numberWithInt:[stringOrder indexOfObject:obj2.assetID]]]
}];
While cwehrungs answer will get the job done, the performance is not great on relatively small arrays.
Here is another method for performing the same kind of sort that is a bit quicker (though still far from perfect):
NSMutableArray *sorted = [NSMutableArray array];
// pre-populate with objects
for (int i = 0; i < stringOrder.count; i++)
{
[sorted addObject:[NSNull null]];
}
// place the items at the correct position
for (Attribute *a in items)
{
NSUInteger idx = [stringOrder indexOfObject:a.assetID];
if (idx != NSNotFound)
{
[sorted setObject:a atIndexedSubscript:idx];
}
}
// finally remove all the unecesarry placeholders if one array was smaller
[sorted removeObject:[NSNull null]];
Comparison
Here are the results form running the two methods on an iPhone 5:
sortUsingComparator:
100 - 0.012 s
1000 - 1.116 s
2000 - 4.405 s
3000 - 9.028 s
prepopulated array
100 - 0.003 s
1000 - 0.236 s
2000 - 0.917 s
3000 - 2.063 s
There are a couple approaches you could take.
You could store your Attribute objects in an NSDictionary, with the keys being the strings in your stringOrder array. Then, you could get a sorted array of the keys and use that to populate whatever view you're using to display them:
NSArray* sortedKeys = [dict keysSortedByValueUsingComparator:^(id obj1, id obj2) {
return [obj1 compareTo:obj2];
}
The other is that you make the sort order an intrinsic property of your Attribute object, so an array of Attributes can be sorted directly. I would only recommend taking this approach if the sort order is actually an intrinsic property of your Attributes object. If it isn't and you do this, you'll wind up storing presentation information where it doesn't belong.
Here's an example:
NSArray* sortedAttrs = [attributes sortedArrayUsingComparator:^(id obj1, id obj2) {
// Perform comparison of Attribute's, ahem, attributes
}
Here is the solution that I came up with that works extremely well. Anyone see performance issues with this?
for (Attribute *a in items) {
int index = [stringOrder indexOfObject:a.assetID];
a.sortOrder = index;
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray = [items sortedArrayUsingDescriptors:sortDescriptors];
Parallel Processing:
Results (quad core):
1. sortme:95 sortby:852345 sorted:95 time:0.052576
2. sortme:54248 sortby:852345 sorted:54243 time:0.264660
-(NSArray *)sortArray:(NSArray *)sortme sortBy:(NSArray *)sortBy{
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
NSSet *sortmeSet = [NSSet setWithArray:sortme];
NSMutableDictionary *sortDictionary = [NSMutableDictionary dictionary];
dispatch_queue_t sortDictionaryThread = dispatch_queue_create("my.sortDictionaryThread", DISPATCH_QUEUE_CONCURRENT);
[sortBy enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([sortmeSet containsObject:obj]){
dispatch_barrier_async(sortDictionaryThread, ^{
sortDictionary[obj] = #(idx);
});
}
}];
__block NSArray *sortedArray = nil;
dispatch_barrier_sync(sortDictionaryThread, ^{
sortedArray = [sortDictionary keysSortedByValueUsingSelector:#selector(compare:)];
});
NSLog(#"sortme:%li sortby:%li sorted:%li time:%f",sortme.count,sortBy.count,sortedArray.count, CFAbsoluteTimeGetCurrent() - time);
return sortedArray;
}

Creating NSDictionary with keys that have multiple values

I have a mutable array that contains NSDictionary dic1 objects,
each dictionary has a key called contactId, more than one dictionary can have the same value for contactId.
What I want to do is to create an NSDictionary with unique contactIds as the keys and an array value that contains a list of all NSDictionary dic1 objects that have the value contactId equal to the key.
How can I do this?
My data looks like this:
**myArray**:[ **dic1** {contactId = x1 , name = name1 }, **dic2**{contactId = x2, name =
name2 }, **dic3**{contactId = x1, name = name3} ]
I want it to become like this:
**NSDictionary**: { **x1**:[dic1, dic3], **x2**:[dic2] }
Use fast enumeration:
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (id obj in myArray)
{
NSString *contactId = [obj objectForKey:#"contactId"];
NSMutableSet *contacts = [result objectForKey:contactId];
if (!contacts)
{
contacts = [NSMutableSet set]
[result setObject:contacts forKey:contactId];
}
[contacts addObject:obj];
}
You could use blocks for no real added benefit:
__block NSMutableDictionary *result = [NSMutableDictionary dictionary];
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
NSString *contactId = [obj objectForKey:#"contactId"];
NSMutableSet *contacts = [result objectForKey:contactId];
if (!contacts)
{
contacts = [NSMutableSet set]
[result setObject:contacts forKey:contactId];
}
[contacts addObject:obj];
}];
How about the classic way?
NSMutableDictionary* Result;
NSEnumerator* Enumerator;
NSDictionary* Dict;
Result=[[NSMutableDictionary alloc] init];
Enumerator=[YourArray objectEnumerator];
while ((Dict=[Enumerator nextObject])!=nil)
{
NSString* ContactID;
NSMutableSet* Contacts;
ContactID=[Dict objectForKey:#"contactID"];
Contacts=[Result objectForKey:ContactID];
if (Contacts==nil)
{
Contacts=[[NSMutableSet alloc] init];
[Result setObject:Contacts forKey:ContactID];
[Contacts release];
}
[Contacts addObject:Dict];
}
This should create a Result dictionary. I haven't tested (or even compiled) this, though.

How to extract unique objects from multiple arrays

EDIT:
I have two different arrays with some repeated strings and i want to create a new array with the only the unique strings.
For instance, take these two arrays:
NSArray *array1 = [[NSArray alloc] initWithObjects:#"a",#"b",#"c",nil];
NSArray *array2 = [[NSArray alloc] initWithObjects:#"a",#"d",#"c",nil];
// Result should be an array with objects "b", and "d"
// since they are the only two that are not repeated in the other array.
EDIT:
// Your starting arrays
NSArray *array1 = [[NSArray alloc] initWithObjects:#"a",#"b",#"c",nil];
NSArray *array2 = [[NSArray alloc] initWithObjects:#"a",#"d",#"c",nil];
// Create two new arrays that only contain the objects
// which are not in the other array:
NSMutableArray *uniqueElementsInArray1 = [array1 mutableCopy];
[uniqueElementsInArray1 removeObjectsInArray:array2];
NSMutableArray *uniqueElementsInArray2 = [array2 mutableCopy];
[uniqueElementsInArray2 removeObjectsInArray:array1];
// Combine the two arrays.
// Result contains objects #"b" and #"d":
NSArray *result = [uniqueElementsInArray1 arrayByAddingObjectsFromArray:uniqueElementsInArray2];
For this you just declare one another temp NSMutableArray . Retrieve whatever data u have from your original array say objectArray. Check whether the temp array have that or not and put it into the temp array. Just refer following code:
for(NSString *str in objectArray)
{
if(![tempArray containsObject:str])
{
[tempArray addObject:str];
}
}
After this u can continue to use tempArray or put tempArray into objectArray if you want to use objectArray further.I think this should work for you.
You can use NSSet as a filter (think of Venn Diagrams in your head):
NSArray *array1 = #[#1,#2,#3,#4,#2,#3];
NSArray *array2 = #[#3,#4,#5,#6,#4,#6];
NSSet *set1 = [NSSet setWithArray:array1]; // [1,2,3,4]
NSSet *set2 = [NSSet setWithArray:array2]; // [3,4,5,6]
METHOD 1 (my favorite):
NSMutableSet *mSet1 = [set1 mutableCopy];
NSMutableSet *mSet2 = [set2 mutableCopy];
[mSet1 minusSet:set2]; // mSet1 = [1,2]
[mSet2 minusSet:set1]; // mSet2 = [5,6]
[mSet1 unionSet:mSet2]; // mSet1 = [1,2,5,6], only the unique elements.
// Now just put it in an immutable collections with a self-docu name...
NSArray *arrayOfUniqueness = [setOfUniqueElementsOnly allObjects];
METHOD 2 (more explicit test, no need for Venn Diagrams):
NSSet *setOfObjsUniqueTo1 = [set1 objectsPassingTest:^BOOL(id _Nonnull obj, BOOL * _Nonnull stop) {
return ![set2 containsObject:obj];
}]; // [1,2]
NSSet *setOfObjsUniqueTo2 = [set2 objectsPassingTest:^BOOL(id _Nonnull obj, BOOL * _Nonnull stop) {
return ![set1 containsObject:obj];
}]; // [5,6]
NSMutableSet *oneSetToRuleThemAll = [NSMutableSet setWithSet:setOfObjsUniqueTo1];
// [1,2]
[oneSetToRuleThemAll unionSet:setOfObjsUniqueTo2]; // [1,2,5,6]
// Or as an array:
NSArray *anotherArrayOfUniqueness = [oneSetToRuleThemAll allObjects];
METHOD 3 (eschews NSSet, but I would not seat this code opposite the Queen of England at a formal dinner -- it is inelegant):
NSMutableArray *mArray1 = [NSMutableArray new];
NSMutableArray *mArray2 = [NSMutableArray new];
NSIndexSet *uniqueIndexes1 = [array1 indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return ![array2 containsObject:obj];
}]; // [0,1,4] (b/c #1 and #2 are unique to array1)
[uniqueIndexes1 enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
[mArray1 addObject:array1[idx]];
}]; // #[#1,#2,#2]
NSIndexSet *uniqueIndexes2 = [array2 indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return ![array1 containsObject:obj];
}]; // [2,3,5] (b/c #5 and #6 are unique to array2)
[uniqueIndexes2 enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
[mArray2 addObject:array2[idx]];
}]; // #[#5,#6,#6]
NSArray *unionArray = [array1 arrayByAddingObjectsFromArray:array2];
// #[#1,#2,#2,#5,#6,#6]
NSArray *yetAnotherArrayOfUniqueness = [[NSSet setWithArray:unionArray] allObjects];
// #[#1,#2,#5,#6]
Not the questioner's question, but to get an array with duplicates removed (i.e., where each element is unique), similar magic can be done:
//given...
NSArray *arr1 = #[#"a", #"b", #"c"];
NSArray *arr2 = #[#"b", #"c", #"d"];
//...make a single array to rule them all:
NSArray *temp = [arr1 arrayByAddingObjectsFromArray:arr2];
//[a,b,c,b,c,d]
//Make an NSSet from the two:
NSSet *filterSet = [NSSet setWithArray:temp]; // Set has: a,b,c,d
//Finally, transmogrify that NSSet into an NSArray:
NSArray *arrayOfUniqueness = [filterSet allObjects]; // [a,b,c,d]
As per the Apple Docs (emphasis added):
+setWithArray:
Creates and returns a set containing a uniqued collection of the objects contained in a given array.
UPDATE: And see here for a similar question: Remove all strings with duplicates in an NSArray
use Set as a filter, example:
String[] arr = {"a","a","b"};
Object[] uniqueArr = (Object[])new HashSet<String>(Arrays.asList(arr)).toArray();