반복하는 동안 NSMutableArray에서 제거하는 가장 좋은 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/111866

  •  02-07-2019
  •  | 
  •  

문제

Cocoa에서 NSMutableArray를 반복하고 특정 기준에 맞는 여러 개체를 제거하려는 경우 개체를 제거할 때마다 루프를 다시 시작하지 않고 이 작업을 수행하는 가장 좋은 방법은 무엇입니까?

감사해요,

편집하다:명확히하기 위해-나는 가장 좋은 방법을 찾고있었습니다.내가 있는 인덱스를 수동으로 업데이트하는 것보다 더 우아한 것입니다.예를 들어 C++에서는 할 수 있습니다.

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
도움이 되었습니까?

해결책

명확성을 위해 삭제할 항목을 수집하는 초기 루프를 만들고 싶습니다.그런 다음 삭제합니다.다음은 Objective-C 2.0 구문을 사용하는 샘플입니다.

NSMutableArray *discardedItems = [NSMutableArray array];

for (SomeObjectClass *item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addObject:item];
}

[originalArrayOfItems removeObjectsInArray:discardedItems];

그러면 지수가 올바르게 업데이트되고 있는지 또는 기타 작은 장부 세부 사항이 업데이트되는지 여부에 대해서는 의문의 여지가 없습니다.

다음을 추가하도록 편집되었습니다.

역공식이 더 빨라야한다는 것이 다른 답변에서 언급되었습니다.즉.배열을 반복하고 삭제할 개체 대신 유지할 새 개체 배열을 구성하는 경우.그것이 사실일 수도 있습니다(비록 새로운 배열을 할당하고 이전 배열을 폐기하는 데 드는 메모리 및 처리 비용은 어떻습니까?). 비록 더 빠르더라도 순진한 구현만큼 큰 문제는 아닐 수도 있습니다. 왜냐하면 NSArrays "일반" 배열처럼 동작하지 않습니다.그들은 말은 하지만 다른 길을 걷습니다. 여기에서 좋은 분석을 확인하세요.

역공식이 더 빠를 수도 있지만, 위의 공식이 항상 내 요구에 충분히 빠르기 때문에 그럴 필요가 없었습니다.

나에게 중요한 메시지는 당신에게 가장 명확한 공식을 사용하라는 것입니다.필요한 경우에만 최적화하십시오.나는 개인적으로 위의 공식이 가장 명확하다고 생각하므로 이 공식을 사용합니다.그러나 역공식이 더 명확하다면 그렇게 하십시오.

다른 팁

변형이 하나 더 있습니다.따라서 가독성과 좋은 성능을 얻을 수 있습니다.

NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;

for (item in originalArrayOfItems) {
    if ([item shouldBeDiscarded])
        [discardedItems addIndex:index];
    index++;
}

[originalArrayOfItems removeObjectsAtIndexes:discardedItems];

이것은 매우 간단한 문제입니다.거꾸로 반복하면 됩니다.

for (NSInteger i = array.count - 1; i >= 0; i--) {
   ElementType* element = array[i];
   if ([element shouldBeRemoved]) {
       [array removeObjectAtIndex:i];
   }
}

이것은 매우 일반적인 패턴입니다.

다른 답변 중 일부는 매우 큰 배열에서 성능이 저하될 수 있습니다. removeObject: 그리고 removeObjectsInArray: 수신기의 선형 검색을 수행하는 작업이 포함됩니다. 이는 개체가 어디에 있는지 이미 알고 있기 때문에 낭비입니다.또한 removeObjectAtIndex: 인덱스에서 배열 끝까지 한 번에 한 슬롯씩 값을 복사해야 합니다.

다음이 더 효율적입니다.

NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
    if (! shouldRemove(object)) {
        [itemsToKeep addObject:object];
    }
}
[array setArray:itemsToKeep];

용량을 정했기 때문에 itemsToKeep, 크기 조정 중에 값을 복사하는 데 시간을 낭비하지 않습니다.배열을 제자리에서 수정하지 않으므로 Fast Enumeration을 자유롭게 사용할 수 있습니다.사용 setArray: 내용을 대체하기 위해 array ~와 함께 itemsToKeep 효율적일 것입니다.코드에 따라 마지막 줄을 다음으로 바꿀 수도 있습니다.

[array release];
array = [itemsToKeep retain];

따라서 값을 복사할 필요도 없고 포인터만 교환하면 됩니다.

NSpredicate를 사용하여 가변 배열에서 항목을 제거할 수 있습니다.여기에는 for 루프가 필요하지 않습니다.

예를 들어 NSMutableArray 이름이 있는 경우 다음과 같은 조건자를 만들 수 있습니다.

NSPredicate *caseInsensitiveBNames = 
[NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];

다음 줄은 b로 시작하는 이름만 포함하는 배열을 남깁니다.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

필요한 술어를 생성하는 데 문제가 있는 경우 다음을 사용하세요. 애플 개발자 링크.

4가지 방법을 사용하여 성능 테스트를 수행했습니다.각 테스트는 100,000개 요소 배열의 모든 요소를 ​​반복하고 5번째 항목마다 제거했습니다.최적화 유무에 관계없이 결과는 크게 달라지지 않았습니다.이 작업은 iPad 4에서 수행되었습니다.

(1) removeObjectAtIndex: -- 271ms

(2) removeObjectsAtIndexes: -- 1010ms (인덱스 세트를 구축하는 데 ~700ms가 걸리기 때문입니다.그렇지 않은 경우 이는 기본적으로 RemoveObjectAtIndex를 호출하는 것과 동일합니다.각 항목마다)

(3) removeObjects: -- 326ms

(4) 테스트를 통과한 객체로 새 배열을 만듭니다. 17ms

따라서 새 배열을 만드는 것이 가장 빠릅니다.RemoveObjectsAtIndexes를 사용하는 것을 제외하고 다른 방법은 모두 비슷합니다.인덱스 세트를 구축하는 데 필요한 시간으로 인해 제거할 항목이 많아지면 상태가 더 나빠집니다.

인덱스에 대해 루프 카운트다운을 사용하십시오.

for (NSInteger i = array.count - 1; i >= 0; --i) {

또는 보관하려는 개체로 복사본을 만드세요.

특히, for (id object in array) 루프 또는 NSEnumerator.

iOS 4+ 또는 OS X 10.6+의 경우 Apple이 추가함 passingTest 일련의 API NSMutableArray, 좋다 – indexesOfObjectsPassingTest:.이러한 API를 사용하는 솔루션은 다음과 같습니다.

NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
    ^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];

요즘에는 역방향 블록 기반 열거를 사용할 수 있습니다.간단한 예제 코드:

NSMutableArray *array = [@[@{@"name": @"a", @"shouldDelete": @(YES)},
                           @{@"name": @"b", @"shouldDelete": @(NO)},
                           @{@"name": @"c", @"shouldDelete": @(YES)},
                           @{@"name": @"d", @"shouldDelete": @(NO)}] mutableCopy];

[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if([obj[@"shouldDelete"] boolValue])
        [array removeObjectAtIndex:idx];
}];

결과:

(
    {
        name = b;
        shouldDelete = 0;
    },
    {
        name = d;
        shouldDelete = 0;
    }
)

단 한 줄의 코드를 사용하는 또 다른 옵션:

[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];

보다 선언적인 방식으로 제거할 항목과 일치하는 기준에 따라 다음을 사용할 수 있습니다.

[theArray filterUsingPredicate:aPredicate]

@Nathan은 매우 효율적이어야 합니다.

쉽고 깨끗한 방법은 다음과 같습니다.나는 빠른 열거 호출에서 바로 배열을 복제하고 싶습니다.

for (LineItem *item in [NSArray arrayWithArray:self.lineItems]) 
{
    if ([item.toBeRemoved boolValue] == YES) 
    {
        [self.lineItems removeObject:item];
    }
}

이 방법으로 삭제되는 배열의 복사본을 열거하며 둘 다 동일한 개체를 보유합니다.NSArray는 객체 포인터만 보유하므로 이는 메모리/성능 측면에서 완전히 좋습니다.

제거하려는 개체를 두 번째 배열에 추가하고 루프 후에 -removeObjectsInArray:를 사용합니다.

이렇게 해야 합니다:

    NSMutableArray* myArray = ....;

    int i;
    for(i=0; i<[myArray count]; i++) {
        id element = [myArray objectAtIndex:i];
        if(element == ...) {
            [myArray removeObjectAtIndex:i];
            i--;
        }
    }

도움이 되었기를 바랍니다...

제거할 객체를 다른 NSMutableArray에 추가하는 것이 어떻습니까?반복이 끝나면 수집한 객체를 제거할 수 있습니다.

삭제하려는 요소를 'n'번째 요소, 'n-1'번째 요소 등으로 바꾸는 것은 어떻습니까?

완료되면 배열의 크기를 '이전 크기 - 스왑 수'로 조정합니다.

배열의 모든 개체가 고유하거나 발견된 개체의 모든 항목을 제거하려는 경우 배열 복사본을 빠르게 열거하고 [NSMutableArray RemoveObject:]를 사용하여 원본에서 개체를 제거할 수 있습니다.

NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];

for (NSObject *anObject in myArrayCopy) {
    if (shouldRemove(anObject)) {
        [myArray removeObject:anObject];
    }
}

위의 benzado의 답변은 사전 형성을 위해 수행해야 할 작업입니다.내 응용 프로그램 중 하나에서 RemoveObjectsInArray의 실행 시간은 1분이었고, 새 배열을 추가하는 데만 .023초가 걸렸습니다.

다음과 같이 블록을 사용하여 필터링할 수 있는 카테고리를 정의합니다.

@implementation NSMutableArray (Filtering)

- (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate {
    NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init];

    NSUInteger index = 0;
    for (id object in self) {
        if (!predicate(object, index)) {
            [indexesFailingTest addIndex:index];
        }
        ++index;
    }
    [self removeObjectsAtIndexes:indexesFailingTest];

    [indexesFailingTest release];
}

@end

그런 다음 다음과 같이 사용할 수 있습니다.

[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
    return [self doIWantToKeepThisObject:obj atIndex:idx];
}];

더 나은 구현은 NSMutableArray에서 아래 카테고리 메소드를 사용하는 것입니다.

@implementation NSMutableArray(BMCommons)

- (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate {
    if (predicate != nil) {
        NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count];
        for (id obj in self) {
            BOOL shouldRemove = predicate(obj);
            if (!shouldRemove) {
                [newArray addObject:obj];
            }
        }
        [self setArray:newArray];
    }
}

@end

배열의 각 개체에 대한 처리를 수행하기 위해 조건자 블록을 구현할 수 있습니다.조건자가 true를 반환하면 개체가 제거됩니다.

과거의 모든 날짜를 제거하는 날짜 배열의 예:

NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
    NSDate *date = (NSDate *)obj;
    return [date timeIntervalSinceNow] < 0;
}];

거꾸로 반복하는 것은 제가 수년 동안 가장 좋아했지만 오랫동안 '가장 깊은'(가장 높은 개수) 객체가 먼저 제거되는 경우를 본 적이 없습니다.포인터가 다음 인덱스로 이동하기 직전에 아무 것도 없고 충돌이 발생합니다.

Benzado의 방식은 내가 지금 하는 일과 가장 가깝지만 제거할 때마다 스택이 다시 섞이게 될 것이라는 것을 결코 깨닫지 못했습니다.

Xcode 6에서는 작동합니다.

NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];

    for (id object in array)
    {
        if ( [object isNotEqualTo:@"whatever"]) {
           [itemsToKeep addObject:object ];
        }
    }
    array = nil;
    array = [[NSMutableArray alloc]initWithArray:itemsToKeep];
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top