أفضل طريقة لإزالة من NSMutableArray حين بالتكرار?

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

  •  02-07-2019
  •  | 
  •  

سؤال

في الكاكاو ، إذا كنت ترغب في حلقة من خلال NSMutableArray وإزالة العديد من الأشياء التي تناسب معايير معينة ، ما هي أفضل طريقة للقيام بذلك دون إعادة تشغيل الحلقة في كل مرة يمكنني إزالة كائن ؟

شكرا

تحرير:فقط لتوضيح أنا كنت تبحث عن أفضل على سبيل المثال ، شيء أكثر أناقة من يدويا تحديث مؤشر أنا في.على سبيل المثال في C++ يمكنني القيام به ؛

iterator it = someList.begin();

while (it != someList.end())
{
    if (shouldRemove(it))   
        it = someList.erase(it);
}
هل كانت مفيدة؟

المحلول

لوضوح أود أن جعل أولي حلقة حيث جمع العناصر إلى حذف.ثم وحذفها.هنا عينة باستخدام الهدف-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, نحن لا تضيع أي وقت نسخ القيم من خلال تغيير الحجم.نحن لا تعديل مجموعة في مكان ، لذلك نحن أحرار في استخدام سريع التعداد.باستخدام setArray: استبدال محتويات array مع itemsToKeep سوف تكون فعالة.اعتمادا على التعليمات البرمجية الخاصة بك ، حتى يمكن استبدال السطر الأخير مع:

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

لذلك ليس هناك حاجة لنسخ القيم فقط مبادلة مؤشر.

يمكنك استخدام NSpredicate لإزالة العناصر من مجموعة قابلة للتغيير.هذا لا يتطلب أي عن الحلقات.

على سبيل المثال إذا كان لديك NSMutableArray من الأسماء ، يمكنك إنشاء المسند مثل هذا واحد:

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

السطر التالي سوف أترك لكم مع مجموعة تحتوي على أسماء تبدأ ب.

[namesArray filterUsingPredicate:caseInsensitiveBNames];

إذا كان لديك صعوبة في إيجاد المسندات كنت بحاجة إلى استخدام هذا المطور أبل الرابط.

أنا عملت اختبار الأداء باستخدام 4 طرق مختلفة.كل اختبار يتحرك من خلال جميع العناصر في 100 ، 000 عنصر الصفيف ، وإزالة كل 5 البند.فإن النتائج لا تختلف كثيرا مع/ بدون الأمثل.هذه تم القيام به على آي باد 4:

(1) removeObjectAtIndex: -- 271 ms

(2) removeObjectsAtIndexes: -- 1010 مللي (لأن بناء فهرس يأخذ ~700 مللي ثانية.وإلا فإن هذا هو أساسا نفس الدعوة removeObjectAtIndex:لكل بند)

(3) removeObjects: -- 326 ms

(4) جعل مجموعة جديدة مع الكائنات اجتياز الاختبار -- 17 ms

لذلك ، وخلق مجموعة جديدة حتى الآن أسرع.الطرق الأخرى كلها قابلة للمقارنة ، إلا أن استخدام removeObjectsAtIndexes:سوف يكون أسوأ مع المزيد من العناصر إزالتها بسبب الوقت اللازم لبناء مؤشر مجموعة.

إما استخدام حلقة العد التنازلي على المؤشرات:

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

أو إجراء نسخة مع الكائنات التي تريد الاحتفاظ بها.

على وجه الخصوص, لا تستخدم for (id object in array) حلقة أو NSEnumerator.

على دائرة الرقابة الداخلية 4+ أو OS X 10.6+, وأضاف أبل passingTest سلسلة من واجهات برمجة التطبيقات في 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]

@ناثان يجب أن تكون فعالة جدا

هنا هي سهلة ونظيفة الطريق.أود أن تكرار هذه المجموعة الحق في الصيام التعداد الاتصال:

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 يستحق العنصر ، 'ن-1 قيمته عنصر وهلم جرا ؟

عند الانتهاء من ذلك يمكنك تغيير حجم المصفوفة السابقة الحجم - عدد مقايضة'

إن جميع الكائنات في مجموعة الخاصة بك هي فريدة من نوعها أو كنت ترغب في إزالة كافة تواجدات كائن عندما وجدت ، يمكن أن تعداد سريع على مجموعة نسخ واستخدام [NSMutableArray removeObject:] إزالة الكائن من الأصل.

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

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

benzado هو الإجابة إنها تبدو أعلاه هو ما يجب عليك القيام به من أجل preformace.في واحدة من بلدي تطبيقات 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 طريقة هو الأقرب إلى ما أقوم به الآن ولكن أنا لم أدرك أبدا أن يكون هناك كومة تعديل وزاري بعد كل إزالة.

تحت كسكودي 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