반복하는 동안 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];
필요한 술어를 작성하는 데 문제가있는 경우이 애플 개발자 링크를 사용 하십시오 .
I did a performance test using 4 different methods. Each test iterated through all elements in a 100,000 element array, and removed every 5th item. The results did not vary much with/ without optimization. These were done on an iPad 4:
(1) removeObjectAtIndex:
-- 271 ms
(2) removeObjectsAtIndexes:
-- 1010 ms (because building the index set takes ~700 ms; otherwise this is basically the same as calling removeObjectAtIndex: for each item)
(3) removeObjects:
-- 326 ms
(4) make a new array with objects passing the test -- 17 ms
So, creating a new array is by far the fastest. The other methods are all comparable, except that using removeObjectsAtIndexes: will be worse with more items to remove, because of the time needed to build the index set.
Either use loop counting down over indices:
for (NSInteger i = array.count - 1; i >= 0; --i) {
or make a copy with the objects you want to keep.
In particular, do not use a for (id object in array)
loop or NSEnumerator
.
For iOS 4+ or OS X 10.6+, Apple added passingTest
series of APIs in NSMutableArray
, like – indexesOfObjectsPassingTest:
. A solution with such API would be:
NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
Nowadays you can use reversed block-based enumeration. A simple example code:
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];
}];
Result:
(
{
name = b;
shouldDelete = 0;
},
{
name = d;
shouldDelete = 0;
}
)
another option with just one line of code:
[array filterUsingPredicate:[NSPredicate predicateWithFormat:@"shouldDelete == NO"]];
In a more declarative way, depending on the criteria matching the items to remove you could use:
[theArray filterUsingPredicate:aPredicate]
@Nathan should be very efficient
Here's the easy and clean way. I like to duplicate my array right in the fast enumeration call:
for (LineItem *item in [NSArray arrayWithArray:self.lineItems])
{
if ([item.toBeRemoved boolValue] == YES)
{
[self.lineItems removeObject:item];
}
}
This way you enumerate through a copy of the array being deleted from, both holding the same objects. An NSArray holds object pointers only so this is totally fine memory/performance wise.
Add the objects you want to remove to a second array and, after the loop, use -removeObjectsInArray:.
this should do it:
NSMutableArray* myArray = ....;
int i;
for(i=0; i<[myArray count]; i++) {
id element = [myArray objectAtIndex:i];
if(element == ...) {
[myArray removeObjectAtIndex:i];
i--;
}
}
hope this helps...
Why don't you add the objects to be removed to another NSMutableArray. When you are finished iterating, you can remove the objects that you have collected.
How about swapping the elements you want to delete with the 'n'th element, 'n-1'th element and so on?
When you're done you resize the array to 'previous size - number of swaps'
If all objects in your array are unique or you want to remove all occurrences of an object when found, you could fast enumerate on an array copy and use [NSMutableArray removeObject:] to remove the object from the original.
NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];
for (NSObject *anObject in myArrayCopy) {
if (shouldRemove(anObject)) {
[myArray removeObject:anObject];
}
}
benzado's anwser above is what you should do for preformace. In one of my applications removeObjectsInArray took a running time of 1 minute, just adding to a new array took .023 seconds.
I define a category that lets me filter using a block, like this:
@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
which can then be used like this:
[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
return [self doIWantToKeepThisObject:obj atIndex:idx];
}];
A nicer implementation could be to use the category method below on 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
The predicate block can be implemented to do processing on each object in the array. If the predicate returns true the object is removed.
An example for a date array to remove all dates that lie in the past:
NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
NSDate *date = (NSDate *)obj;
return [date timeIntervalSinceNow] < 0;
}];
Iterating backwards-ly was my favourite for years , but for a long time I never encountered the case where the 'deepest' ( highest count) object was removed first. Momentarily before the pointer moves on to the next index there ain't anything and it crashes.
Benzado's way is the closest to what i do now but I never realised there would be the stack reshuffle after every remove.
under Xcode 6 this works
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];
참고URL : https://stackoverflow.com/questions/111866/best-way-to-remove-from-nsmutablearray-while-iterating
'Programing' 카테고리의 다른 글
프로젝트 빌드 설정이 dSYM 파일을 생성하고 있는지 확인하십시오. (0) | 2020.05.11 |
---|---|
PHP에서 최대 실행 시간을 늘리는 방법 (0) | 2020.05.11 |
Xcode 8은 프로비저닝 프로파일에 서명 인증서가 포함되지 않았다는 오류를 표시합니다. (0) | 2020.05.11 |
Java에서 객체와 같은 구조 (0) | 2020.05.11 |
APK 설치 중 DELETE_FAILED_INTERNAL_ERROR 오류 (0) | 2020.05.11 |