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

121

У меня есть NSArrayи я хотел бы создать новый NSArrayс объектами из исходного массива, которые соответствуют определенным критериям. Критерии определяются функцией, которая возвращает BOOL.

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

Есть ли способ лучше?

Лайош
источник

Ответы:

140

NSArrayи NSMutableArrayпредоставить методы для фильтрации содержимого массива. NSArrayпредоставляет filterArrayUsingPredicate: который возвращает новый массив, содержащий объекты в получателе, соответствующие указанному предикату. 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 мертв, да здравствуют блоки! ср мой ответ ниже.
Clay Bridges
Что означает "[c]"? Я всегда вижу [c], но не понимаю, что он делает?
user1007522
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.
}]];

Я думаю, это настолько лаконично, насколько это возможно.


Swift:

Тем, кто работает с NSArrays в Swift, вы можете предпочесть еще более краткую версию:

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

filter- это просто метод Array( NSArrayнеявно связан с Swift Array). Он принимает один аргумент: замыкание, которое принимает один объект в массиве и возвращает Bool. В вашем закрытии просто вернитесь trueдля любых объектов, которые вы хотите в фильтрованном массиве.

Стюарт
источник
1
Какую роль здесь играют привязки NSDictionary *?
Kaitain
Привязки @Kaitain требуются NSPredicate predicateWithBlock:API.
ThomasW
@Kaitain bindingsСловарь может содержать привязки переменных для шаблонов.
Амин Негм-Авад
Намного более полезный, чем принятый ответ при работе со сложными объектами :)
Ben Leggiero 01
47

Основываясь на ответе Клея Бриджеса, вот пример фильтрации с использованием блоков (измените yourArrayимя переменной массива и testFuncимя вашей функции тестирования):

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];
PcKill
источник
Наконец, ответ не только упоминает фильтрацию с помощью блоков, но также дает хороший пример того, как это сделать. Спасибо.
Krystian
Мне нравится этот ответ; даже несмотря filteredArrayUsingPredicateна то, что он более компактный, тот факт, что вы не используете никаких предикатов, как бы скрывает цель.
Джеймс Пери,
Это самый современный способ сделать это. Лично я скучаю по старому сокращению objectsPassingTest, которое в определенный момент исчезло из API. Тем не менее, это работает быстро и хорошо. Мне действительно нравится NSPredicate - но для других вещей, где нужен более тяжелый молоток,
Мотти Шнеор
Теперь вы получите ошибку Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC... он ожидает NSIndexSets
anoop4real
@ anoop4real, я думаю, что причиной предупреждения, о котором вы упомянули, является то, что вы по ошибке использовали indexOfObjectPassingTestвместо indexesOfObjectsPassingTest. Легко пропустить, но большая разница :)
pckill
46

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

Хотите, чтобы кто-то еще написал эту категорию, протестировал ее и т. Д.? Проверьте BlocksKit ( массив документов ). И есть еще много примеров, которые можно найти, например, при поиске по запросу «nsarray block category select» .

Глиняные мосты
источник
5
Не могли бы вы дополнить свой ответ примером? Веб-сайты блогов имеют тенденцию умирать, когда они вам нужны больше всего.
Дэн Абрамов
8
Защита от гниения ссылок заключается в извлечении релевантного кода и прочего из статей, на которые есть ссылки, а не в добавлении дополнительных ссылок. Сохраните ссылки, но добавьте пример кода.
toolbear 07
3
@ClayBridges Я нашел этот вопрос в поисках способов фильтрации какао. Поскольку в вашем ответе нет примеров кода, мне потребовалось около 5 минут, чтобы перебрать ваши ссылки, чтобы понять, что блоки не позволяют достичь того, что мне нужно. Если бы код был в ответе, это заняло бы около 30 секунд. Этот ответ намного менее полезен без фактического кода.
N_A
1
@mydogisbox et alia: здесь всего 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 filterArrayUsingPredicate: [NSPredicate predicateWithFormat: @ "myMethod = TRUE"]]; хватит. Спасибо! (Мне тоже нравятся альтернативы, но этот хорош).
Мотти Шнеор
3

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

Например, рассмотрим два массива arrи filteredarr:

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

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

у filterarr обязательно будут элементы, содержащие только символ c.

чтобы было легко запомнить тех, у кого мало фона sql

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

1) выберите * из таблицы -> сборник

2) column1 как '% a%' ->NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF contains[c] %@",@"c"];

3) выберите * from tbl, где column1, например '% a%' ->

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

надеюсь, это поможет

Дурай Амутан.H
источник
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

Вы можете скачать эту категорию здесь

skywinder
источник
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;
}
jalmatari
источник