Removing item from array by property value

52,664

Solution 1

There are two general approaches. We can test each element and then immediately remove the element if it meets the test criteria, or we can test each element and store the indexes of elements meeting the test criteria, and then remove all such elements at once. With memory usage a real concern, the storage requirements for the latter approach may render it undesirable.

With the "store all indexes to remove, then remove them" approach off the table, we need to consider the details involved in the former approach, and how they will effect the correctness and speed of the approach. There are two fatal errors waiting in this approach. The first is to remove the evaluated object not based on its index in the array, but rather with the removeObject: method. removeObject: does a linear search of the array to find the object to remove. With a large, unsorted data set, this will destroy our performance as the time increases with the square of the input size. By the way, using indexOfObject: and then removeObjectAtIndex: is just as bad, so we should avoid it also. The second fatal error would be starting our iteration at index 0. NSMutableArray rearranges the indexes after adding or removing an object, so if we start with index 0, we'll be guaranteed an index out of bounds exception if even one object is removed during the iteration. So, we have to start from the back of the array, and only remove objects that have lower indexes than every index we've checked so far.

Having laid that out, there's really two obvious choices: a for loop that starts at the end rather than the beginning of the array, or the NSArray method enumerateObjectsWithOptions:usingBlock: method. Examples of each follow:

[persons enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(Person *p, NSUInteger index, BOOL *stop) {
    if ([p.hairColor isEqualToString:@"brown"]) {
        [persons removeObjectAtIndex:index];
    }
}];

NSInteger count = [persons count];
for (NSInteger index = (count - 1); index >= 0; index--) {
    Person *p = persons[index];
    if ([p.hairColor isEqualToString:@"brown"]) {
        [persons removeObjectAtIndex:index];
    }
}

My tests seem to show the for loop marginally faster - maybe about a quarter second faster for 500,000 elements, which is a difference between 8.5 and 8.25 seconds, basically. So I would suggest using the block approach, as it's safer and feels more idiomatic.

Solution 2

Assuming you're dealing with a mutable array and it isn't sorted/indexed (i.e. you have to scan through the array), you can iterate through the array in reverse order using enumerateObjectsWithOptions with the NSEnumerationReverse option:

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // now you can remove the object without affecting the enumeration
}];

By going in reverse order, you can remove an object from the array being enumerated.

Solution 3

NSMutableArray * tempArray = [self.peopleArray mutableCopy];

for (Person * person in peopleArray){

 if ([person.hair isEqualToString: @"Brown Hair"])
     [tempArray removeObject: person]

}

self.peopleArray = tempArray;

Or NSPredicate also works: http://nshipster.com/nspredicate/

Solution 4

The key is to use predicates for filtering array. See the code below;

- (NSArray*)filterArray:(NSArray*)list
{
    return  [list filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings){
        People *currentObj = (People*)evaluatedObject;
        return (![currentObj.hairColour isEqualToString:@"brown"]);
    }]];
}

Solution 5

try like this,

        NSIndexSet *indices = [personsArray indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
            return [[obj objectForKey:@"hair"] isEqual:@"Brown Hair"];
        }];
         NSArray *filtered = [personsArray objectsAtIndexes:indices];

OR

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.hair=%@ ",@"Brown Hair"];
        NSArray*   myArray = [personsArray filteredArrayUsingPredicate:predicate];
        NSLog(@"%@",myArray);
Share:
52,664
Choppin Broccoli
Author by

Choppin Broccoli

If you have to ask, you'll never know :)

Updated on July 24, 2022

Comments

  • Choppin Broccoli
    Choppin Broccoli almost 2 years

    I'm looking for the most efficient and memory friendly way.

    Let's say I have an array of Person objects. Each person has a hair color represented by an NSString. Let's then say I want to remove all Person objects from the array where their hair color is brown.

    How do I do this?

    Keep in mind that you cannot remove an object from an array that is being enumerated upon.