hi i have an array which holds 200 objects or so. Each of these objects is another array with 6 fields of mixed types ( ints, strings and bools).
2 questions...
can i search the array to find the objects that have a certain element i.e say all objects that have element "A" = TRUE.
How do i update a single element from one of the objects? DO i have to find that object (from the parent array hence why i asked the first question) , remove it then add a new object with the updated field? seems a bit overkill but is this what i need to do? is there anyway just to update that single element ?
Yes you can search for that, and yes you must if you're going to change a value. You can use indexOfObjectPassingTest to find the object. in your posted example, you would use it like this (assuming that your objects are each dictionaries with one of the fields being "A"):
NSUInteger indx =[myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj valueForKey:#"A"] isEqualToNumber:[NSNumber numberWithBool:TRUE]];
}];
indx will be the index of the object that passes that test in your array.
Related
I have an array which is a parsed xml feed which i want to add to another array using the code....
int insertIdx = [blogEntries count];
for (RSSItem *nextItem in feedItems) {
[blogEntries insertObject:nextItem atIndex:insertIdx];
//[blogEntries addObject:nextItem];
insertIdx += 1;
}
For some reason all of the objects in the blogEntries array all have an index of 0, when i slog them all out using...
for (RSSItem *nextItem in blogEntries)
NSLog(#"title - %#, pos - %i", nextItem.title, [blogEntries IndexOfObject:nextItem]);
do you know why the index might not be updating?
Any help would be appreciated
Did you try to use addObject: instead of insertObject:atIndex?
Are you sure that objects are different as:
indexOfObject:
Returns the lowest index whose corresponding array value is equal to a given object.
I was not able to refrain myself On the Topic of adding many item at once into a NSMutableArray, I do prefer to use this method :
- (void)addObjectsFromArray:(NSArray *)otherArray
it remove that useless for loop. (unless you need to do some other work in that loop)
And as of your problem, viperking have a big hint about a possible problem you may be facing. (if that is the case you may need to validate your isEqual: method.
I am filtering a NSMutableArray to fetch an object(NSDictionary), Then I want to get this object's index in the original NSMutableArray. But somehow the index given is 2147483647 when there are only 5 items in that array.
Could anyone point how could i get the index of the fetched object in its original array?
predicate = [NSPredicate predicateWithFormat:#"data.resourceID == %#",addEvents[#"resourceID"]];
result = [appDataMutable filteredArrayUsingPredicate:predicate];
NSLog(#"%i",[appDataMutable indexOfObject:result]);
//...
[appDataMutable replaceObjectAtIndex:[appDataMutable indexOfObject:result] withObject:resultDictionary]'
When an object is not found in an array, indexOfObject: returns NSNotFound, defined as
enum {NSNotFound = NSIntegerMax};
NSIntegerMax is 0x7fffffff, i.e. 2147483647.
result = [appDataMutable filteredArrayUsingPredicate:predicate];
NSLog(#"%i",[appDataMutable indexOfObject:result]);
filteredArrayUsingPredicate: does not return some single object from the starting array; it returns another array, containing zero or more of the objects in the original array (namely, the ones that match the predicate).
That array contains some (or none) of the objects in the original array; it does not get added to the original array, nor was it ever there before, so it is not one of the objects in the original array.
As such, asking the original array for its indexOfObject: the filtered-down array will get you a result of NSNotFound, which is what you saw.
The solution, as Kevin mentioned in a comment on his answer, is to check whether the result array is empty or not (check its count) and then use the object(s) in that array.
A postscript
If you expect there to only be one object in the result array, you should probably assert that condition, which will raise an exception if that isn't the case. (Your choice whether you want to assert that the array's count is exactly one or less than or equal to one.)
If you want to handle the case of more than one match, you should probably use enumerateObjectsUsingBlock: instead, evaluating the predicate yourself for each match.
I have an array of objects which have an enum as one of their properties, I would like to obtain a filtered array based upon the value of the enum i.e. the returned array contains only objects which have a specified enum value.
I was wondering if KVO could be used as a tidy way of doing this, but haven't found anything suggesting it is?
You can do this by filtering the array using a predicate:
NSArray * filteredArray = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"nameOfProperty == %d", theEnumValue]];
The string for the predicate names the property you're interested in, the value to which it should be compared, and the relationship the two must have for the predicate to evaluate as true.
I have two arrays, an NSMutableArray and an NSArray. The NSMutableArray is the "store", it stores results from a source of NSArrays. Every 5 minute, a new NSArray comes in and the data needs to be filtered and sorted.
Sorting by date is pretty easy, so I managed to get the NSArray sorted by NSDate. Sorting the other array is not necessary, as it would only cause confusion with the user.
What I want to do: the NSArray has a lot of different objects that all respond to -[object name], returning an NSString. The NSArray needs to be merged into the NSMutableArray, only adding new objects.
The merging itself is no problem, but performance is. The NSMutableArray can contain up to 3000 items, and the NSArray can contain up to 250 items, although usually only 5 or 6 of these have to be merged into the NSMutableArray.
So, my question is: how do you merge two arrays in Objective-C, filtering the duplicates, without iterating (250*3000) times?
Tom
Edited to clarify something
The "duplicate" objects are objects that are duplicate to the user but not to the code. They have the same name, but not the same address.
More clarification: #"value" != #"value" // true
Is name a property of the objects being stored in the arrays? If so, you could use a fairly simple NSPredicate to filter the immutable array before adding the results to the mutable one. Here's an example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"NONE name == %#.name", mutableArray];
resultsArray = [immutableArray filteredArrayUsingPredicate:predicate];
[mutableArray addObjectsFromArray:immutableArray];
How about this:
[mutable removeObjectsInArray:newArray];
[mutable addObjectsFromArray:newArray];
It isn't the fattest, but is easy to implement :)
Edited to remove some stupidity (left plenty, though)
A couple of options:
Remove all matching objects from the NSMutableArray using removeObjectIdenticalTo. This requires iterating through the smaller array, but as you note they're commonly small. Then,
Add all of the items from the new array using addObjectsFromArray
Or... well, it actually might be faster to instead:
Iterate through the new array looking for matches with indexOfObjectIdenticalTo, using addObject to add in non-matching objects.
Costly either way, but doable.
I would probably start by creating a new mutable array which contains the contents of your NSMutableArray and NSArray. Then, sort the new array based on the name property and then run through the array once, only pulling out the unique items.
Can you use NSSet and NSMutableSet instead? That could help deal with the duplicates issue.
Edit:
Based on your comments, you could use an NSSet to check for object membership quickly, in addition to your array. It'd require a bit more memory, but if you don't mind that, it could allow you to check really fast. You'd have your NSMutableArray backing store, and then an NSSet to keep track of object membership. You'd maintain the invariant that the NSMutableArray does not contain duplicates. You could use code like this:
// Assume that arrayStore is an NSMutableArray * instance variable
// Also, storeSet is an NSMutableSet * ivar
- (void)addObjectsFromArray:(NSArray *)data
{
for (id item in data) {
if (![storeSet member:item]) {
// Will have to keep arrayStore sorted somehow
[arrayStore addObject:item];
[storeSet addObject:item];
}
}
}
You only have to iterate through the NSArray. I'm not sure how NSSet is implemented off the top of my head, but checking for membership won't be an O(n) operation like it is for an unsorted array.
It's not the most efficient method, but it works well with what you already have in place, with minor modifications.
There are likely many ways to dramatically improve performance, but to be able to suggest any, we really need to know more about what the objects in the arrays "are": what do they represent? How are they being used? (For example, are the items in the store array being displayed in a table view?)
NSMutableDictionary, NSMutableSet, etc. could be combined with NSMutableArray to organize and implement the model in an efficient manner.
For example, let's say we know the object represents a person: MDPerson. A person has a gender, a date of birth, a name, a unique id, and a set of attributes that can change. Given this higher level understanding of what the object represents, we know that 2 people are equal only if their unique ids are the same (in other words, 2 different people can have the same name, gender, and date of birth). Let's say that your main NSMutableArray is made up of a list of 3000 people. The incoming array is made up of 500 people which are already in the main NSMutableArray. A few of these 500 people instances might have "updated" attributes, which means that their instance in the main array needs to be updated with that info.
Given that understanding, it's clear that the main list should be implemented as an NSMutableDictionary rather than an NSMutableArray. In the dictionary, the person's unique id would be the key, and their person instance would be the value for the key. You could then loop through the incoming array of 500 persons only once:
// main dictionary is called personIDsAndPersons
for (MDPerson *person in incomingPersons) {
MDPerson *existingPerson = [personIDsAndPersons objectForKey:[person uniqueID]];
// if nil, the person doesn't exist
if (existingPerson) {
// update the existing person's attributes
[existingPerson setUniqueAttributes:[person uniqueAttributes]];
}
}
Again, without knowing more of the details or having a higher level understanding of what the objects are, we're really just shooting in the dark.
You mention that 2 items are only the same if they have the same name. So, does that mean that each item in the main array of 3000 objects each have a unique name? If so, you could use an NSMutableDictionary to allow access to the objects in an efficient manner by having the keys in the dictionary be the name and the values be the object instance. You could then use a separate NSMutableArray that's used merely for display purposes: it allows an ordered, sorted organization of the same objects that are stored in the NSMutableDictionary. Remember that when you add an object to an array or a dictionary, normally you're not creating a new copy, you're just retaining the existing object.
I'm writing a game using cocos2d-iphone and our stages are defined in .plist files. However, the files are growing large - so I've developed an editor that adds some structure to the process and breaks most of the plist down into fixed fields. However, some elements still require plist editor type functionality, so I have implemented an NSOutlineView on the panels that show 'other parameters'. I am attempting to duplicate the functionality from XCode's 'Property List Editor'.
I've implemented the following system; http://www.stupendous.net/archives/2009/01/11/nsoutlineview-example/
This is very close to what I need, but there is a problem that I've spent most of today attempting to solve. Values for the key column are calculated 'backwards' from the selected item by finding the parent dictionary and using;
return [[parentObject allKeysForObject:item] objectAtIndex:0];
However, when there are multiple items with the same value within a given dictionary in the tree, this statement always returns the first item that has that value (it appears to compare the strings using isEqualToString: or hash values). This leads to the key column showing 'item1, item1, item1' instead of item1, item2, item3 (where items 1-3 all have value ''). I next tried;
-(NSString*)keyFromDictionary:(NSDictionary*)dict forItem:(id)item
{
for( uint i = 0; i < [[dict allKeys] count]; i++ ) {
id object = [dict objectForKey:[[dict allKeys] objectAtIndex:i]];
if ( &object == &item ) {
return [[dict allKeys] objectAtIndex:i];
}
}
return nil;
}
But this always returns nil. I was hoping that somebody with a bit more experience with NSOutlineView might be able to provide a better solution. While this problem only appears once in the linked example, I've had to use this a number of times when deleting items from dictionaries for instance. Any help would be greatly appreciated.
return [[parentObject allKeysForObject:item] objectAtIndex:0];
However, when there are multiple items with the same value within a given dictionary in the tree, this statement always returns the first item that has that value …
Well, yeah. That's what you told it to do: “Get me all the keys for this value; get me the first item in the array; return that”.
… this statement always returns the first item that has that value (it appears to compare the strings using isEqualToString: or hash values).
It's not that statement that's doing it; it's how dictionaries work: Each key can only be in the dictionary once and can only have exactly one object as its value, and this is enforced using the hash of the key and by sending the keys isEqual: messages (not the NSString-specific isEqualToString:—keys are not required to be strings*).
The values, on the other hand, are not uniquified. Any number of keys can have the same value. That's why going from values to keys—and especially to a key—is so problematic.
*Not in NSDictionary, anyway. When you attempt to generate the plist output, it will barf if the dictionary contains any non-string keys.
I next tried;
-(NSString*)keyFromDictionary:(NSDictionary*)dict forItem:(id)item
{
for( uint i = 0; i < [[dict allKeys] count]; i++ ) {
id object = [dict objectForKey:[[dict allKeys] objectAtIndex:i]];
if ( &object == &item ) {
return [[dict allKeys] objectAtIndex:i];
}
}
return nil;
}
But this always returns nil.
That's the least of that code's problems.
First, when iterating on an NSArray, you generally should not use indexes unless you absolutely need to. It's much cleaner to use fast enumeration.
Second, when you do need indexes into an NSArray, the correct type is NSUInteger. Don't mix and match types when you can help it.
Third, I don't know what you meant to do with the address-of operator there, but what you actually did was take the address of those two variables. Thus, you compared whether the local variable object is the same variable as the argument variable item. Since they're not the same variable, that test always returns false, which is why you never return an object—the only other exit point returns nil, so that's what always happens.
The problem with this code and the earlier one-liner is that you're attempting to go from a value to a single key, which is contrary to how dictionaries work: Only the keys are unique; any number of keys can have the same value.
You need to use something else as the items. Using the keys as the items would be one way; making a model object to represent each row would be another.
If you go the model-object route, don't forget to prevent multiple rows in the same virtual dictionary from having the same key. An NSMutableSet plus implementing hash and isEqual: would help with that.
You probably should also make the same change to your handling of arrays.
To clarify, I eventually resolved this problem by creating proxy objects for each of the collections in the plist file (so, for every NSMutableArray or NSMutableDictionary). This meant that I essentially mirrored the Plist structure and included references back to the original objects at each level. This allowed me to store the array index for each object or the dictionary key, so when saving items back from the outline view to the Plist structures, I used the 'key' or 'index' properties on the proxy object.