фільтрація NSArray в новий NSArray в Objective-C


121

У мене є NSArrayі я хотів би створити новеNSArray з об'єктами з початкового масиву, які відповідають певним критеріям. Критерій визначається функцією, яка повертає a BOOL.

Я можу створити NSMutableArray , повторити через вихідний масив і скопіювати об'єкти, які приймає функція фільтра, а потім створити незмінну його версію.

Чи є кращий спосіб?

Відповіді:


140

NSArrayта NSMutableArrayнадати методи фільтрації вмісту масиву. NSArrayнадає filteredArrayUsingPredicate: який повертає новий масив, що містить об'єкти в приймачі, що відповідають вказаному предикату. NSMutableArrayдодає filterUsingPredicate: який оцінює вміст приймача за вказаним предикатом і залишає лише ті об'єкти, які відповідають. Ці методи проілюстровані в наступному прикладі.

NSMutableArray *array =
    [NSMutableArray arrayWithObjects:@"Bill", @"Ben", @"Chris", @"Melissa", nil];

NSPredicate *bPredicate =
    [NSPredicate predicateWithFormat:@"SELF beginswith[c] 'b'"];
NSArray *beginWithB =
    [array filteredArrayUsingPredicate:bPredicate];
// beginWithB contains { @"Bill", @"Ben" }.

NSPredicate *sPredicate =
    [NSPredicate predicateWithFormat:@"SELF contains[c] 's'"];
[array filteredArrayUsingPredicate:sPredicate];
// array now contains { @"Chris", @"Melissa" }

84
Я слухав подкаст Папи Смурфа і Папа Смурф сказав, що відповіді повинні жити в StackOverflow, щоб громада могла оцінити їх та покращити.
willc2

10
@mmalc - Можливо, доречніше, але, звичайно, зручніше переглянути його тут.
Брайан

5
NSPredicate мертвий, живі блоки! пор. моя відповідь нижче.
Глиняні мости

Що означає [c]? Я завжди бачу [с], але не розумію, що це робить?
користувач1007522

3
@ user1007522, [c] робить матч нечутливим до випадку
jonbauer

104

Існує маса способів зробити це, але, безумовно, найсучасніший, безумовно, використовує [NSPredicate predicateWithBlock:]:

NSArray *filteredArray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
    return [object shouldIKeepYou];  // Return YES for each object you want in filteredArray.
}]];

Я думаю, що це настільки ж стисло, як це виходить.


Швидкий:

Для тих, хто працює з NSArrays у Swift, ви можете віддати перевагу цій ще стислішій версії:

nsArray = nsArray.filter { $0.shouldIKeepYou() }

filterце лише метод на Array( NSArrayявно пов'язаний з Swift Array). Він бере один аргумент: закриття, яке бере один об'єкт у масиві і повертає a Bool. На завершення просто поверніться trueдо об'єктів, які ви хочете, у відфільтрованому масиві.


1
Яку роль тут грають прив'язки NSDictionary *?
Кайтан

@Kaitain прив'язки потрібні NSPredicate predicateWithBlock:API.
ThomasW

@Kaitain bindingsСловник може містити змінні прив’язки для шаблонів.
Амін Негм-Авад

Набагато корисніша, ніж прийнята відповідь при роботі зі складними предметами :)
Бен Леджіеро

47

Виходячи з відповіді Clay Bridges, ось приклад фільтрації за допомогою блоків (змінити yourArrayім’я змінної масиву та testFuncназву функції тестування):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];

Нарешті відповідь не лише згадуючи фільтрування за допомогою блоків, але й даючи хороший приклад того, як це зробити. Дякую.
Крістіян

Мені подобається ця відповідь; хоч filteredArrayUsingPredicateвін і худіший, той факт, що ви не використовуєте жодних предикатів, затьмарює мету.
Джеймс Перих

Це найсучасніший спосіб зробити це. Особисто я прагну старого скороченого "objectPassingTest", який зник з API в певний момент. Все-таки це працює швидко і добре. Мені подобається NSPredicate - але для інших речей, де потрібен більш важкий молот
Motti Shneor

Ви отримаєте помилку зараз Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... він очікує NSIndexSets
anoop4real

@ anoop4real, я думаю, що причиною попередження, яке ви згадали, є те, що ви помилково використовували indexOfObjectPassingTestзамість цього indexesOfObjectsPassingTest. Легко пропустити, але велика різниця :)
pckill

46

Якщо ви ОС X 10.6 / iOS 4.0 або пізнішої версії, вам, мабуть, краще з блоками, ніж NSPredicate. Перегляньте -[NSArray indexesOfObjectsPassingTest:]або напишіть власну категорію, щоб додати зручний спосіб -select:або -filter:метод ( приклад ).

Хочете, щоб хтось ще написав цю категорію, перевірив її тощо? Ознайомтеся з BlocksKit ( масив документів ). І є ще багато прикладів, які можна знайти, скажімо, шукаючи, наприклад, "Вибір категорії блоку nsarray" .


5
Чи не проти розширити свою відповідь прикладом? Веб-сайти блогу мають тенденцію вмирати, коли вам це найбільше потрібно.
Дан Абрамов

8
Захист від гниття посилань полягає у витягуванні відповідного коду і того, що з пов'язаних статей, а не додавання більше посилань. Зберігайте посилання, але додайте приклад коду.
інструментарій

3
@ClayBridges Я знайшов це питання, шукаючи способи фільтрації в какао. Оскільки у вашій відповіді немає зразків коду, мені знадобилося близько 5 хвилин, щоб переглянути ваші посилання, щоб з'ясувати, що блоки не досягнуть того, що мені потрібно. Якби код був у відповіді, це зайняло б, можливо, 30 секунд. Ця відповідь FAR менш корисна без фактичного коду.
N_A

1
@mydogisbox та інше: тут є лише 4 відповіді, а отже, достатньо місця для блискучої та чудової вашої власної відповіді. Я задоволений своїм, тому, будь ласка, залиште його в спокої.
Clay Bridges

2
mydogisbox правильний з цього приводу; якщо все, що ви робите, - це надання посилання, це насправді не відповідь, навіть якщо ви додаєте більше посилань. Див. Meta.stackexchange.com/questions/8231 . Тест лакмусу: чи може відповідь відповідати самостійно, чи вимагає натискання посилань будь-яка цінність? Зокрема, ви заявляєте, що "вам, мабуть, краще з блоками, ніж NSPredicate", але ви не пояснюєте чому.
Роберт Харві

16

Якщо припустити, що ваші об’єкти подібного типу, ви можете додати метод як категорію їх базового класу, який викликає функцію, яку ви використовуєте за вашими критеріями. Потім створіть об'єкт NSPredicate, який посилається на цей метод.

У якійсь категорії визначте свій метод, який використовує вашу функцію

@implementation BaseClass (SomeCategory)
- (BOOL)myMethod {
    return someComparisonFunction(self, whatever);
}
@end

Тоді, куди ви фільтруєте:

- (NSArray *)myFilteredObjects {
    NSPredicate *pred = [NSPredicate predicateWithFormat:@"myMethod = TRUE"];
    return [myArray filteredArrayUsingPredicate:pred];
}

Звичайно, якщо ваша функція порівнюється лише з властивостями, доступними в межах вашого класу, можливо, просто простіше перетворити умови функції в рядок предикатів.


Мені сподобалась ця відповідь, тому що це витончено і коротко, а у скороченнях потрібно знову вивчити, як формалізувати NSPredicate для самого елементарного - доступу до властивостей та булевого методу. Я навіть думаю, що обгортка не потрібна. Простий [myArray filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; вистачило б Дякую! (Я теж люблю альтернативи, але ця приємна).
Motti Shneor

3

NSPredicateє способом завдяки NeXTstep про побудову умови для фільтрації колекції ( NSArray, NSSet,NSDictionary ).

Наприклад, розгляньте два масиви arrта filteredarr:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

filteredarr = [NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

filteredarr, безумовно, матиме елементи, які містять символ c поодинці.

щоб легко запам'ятати тих, у кого це мало sql

*--select * from tbl where column1 like '%a%'--*

1) виберіть * з колекції tbl ->

2) стовпчик1, як '% a%' ->NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3) виберіть * з tbl, де стовбур1, як '% a%' ->

[NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];

Я сподіваюся, що це допомагає


2

NSArray + Xh

@interface NSArray (X)
/**
 *  @return new NSArray with objects, that passing test block
 */
- (NSArray *)filteredArrayPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate;
@end

NSArray + Xm

@implementation NSArray (X)

- (NSArray *)filteredArrayPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
{
    return [self objectsAtIndexes:[self indexesOfObjectsPassingTest:predicate]];
}

@end

Ви можете завантажити цю категорію тут


0

Оформити цю бібліотеку

https://github.com/BadChoice/Collection

У комплекті є багато простих функцій масиву, щоб більше ніколи не писати цикл

Тож ви можете просто зробити:

NSArray* youngHeroes = [self.heroes filter:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

або

NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];

0

Найкращий і простий спосіб - це створити цей метод і передати масив і значення:

- (NSArray *) filter:(NSArray *)array where:(NSString *)key is:(id)value{
    NSMutableArray *temArr=[[NSMutableArray alloc] init];
    for(NSDictionary *dic in self)
        if([dic[key] isEqual:value])
            [temArr addObject:dic];
    return temArr;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.