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

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:(271) MS

(2) removeObjectsAtIndexes:1010 MS (인덱스 세트를 구축하는 것은 ~ 700 밀리 필요하기 때문에, 그렇지 않으면이 기본적으로 호출 removeObjectAtIndex과 동일 : 각 항목에 대한)

(3) removeObjects:(326) MS

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

따라서 새 어레이를 만드는 것이 훨씬 빠릅니다. removeObjectsAtIndexes :를 사용하면 인덱스 세트를 빌드하는 데 필요한 시간 때문에 제거 할 항목이 많을수록 더 나빠진다는 점을 제외하고 다른 메소드는 모두 비슷합니다.


답변

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

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

보관하려는 개체로 복사하십시오.

특히 for (id object in array)루프 또는을 사용하지 마십시오 NSEnumerator.