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

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

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

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


person lajos    schedule 21.09.2008    source источник


Ответы (9)


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" }
person lajos    schedule 21.09.2008
comment
Я слушал подкаст Папы Смурфа, и Папа Смурф сказал, что ответы должны находиться в StackOverflow, чтобы сообщество могло их оценивать и улучшать. - person willc2; 17.09.2009
comment
@mmalc - Может быть, более подходящим, но определенно более удобным для просмотра прямо здесь. - person Bryan; 04.08.2010
comment
NSPredicate мертв, да здравствуют блоки! ср. мой ответ ниже. - person Clay Bridges; 20.10.2011
comment
Что означает «содержит [c]»? Я всегда вижу [c], но не понимаю, что он делает? - person user1007522; 20.06.2014
comment
@ user1007522, [c] делает совпадение нечувствительным к регистру. - person jonbauer; 22.10.2014

Есть множество способов сделать это, но самый изящный, безусловно, использует [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, вы можете предпочесть эту еще более сжатую версию:

let filteredArray = array.filter { $0.shouldIKeepYou() }

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

person Stuart    schedule 22.10.2013
comment
Какую роль здесь играют привязки NSDictionary *? - person Kaitain; 15.08.2015
comment
Привязки @Kaitain требуются NSPredicate predicateWithBlock: API. - person ThomasW; 11.09.2015
comment
@Kaitain Словарь bindings может содержать привязки переменных для шаблонов. - person Amin Negm-Awad; 08.12.2015
comment
Намного полезнее, чем принятый ответ при работе со сложными объектами :) - person Ky Leggiero; 02.03.2016

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

yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
    return [self testFunc:obj];
}]];
person pckill    schedule 08.11.2012
comment
Наконец, ответ не только упоминает фильтрацию с помощью блоков, но также дает хороший пример того, как это сделать. Спасибо. - person Krystian; 30.01.2016
comment
Мне нравится этот ответ; хотя filteredArrayUsingPredicate более компактный, тот факт, что вы не используете никаких предикатов, как бы скрывает цель. - person James Perih; 14.04.2016
comment
Это самый современный способ сделать это. Лично мне очень нужны старые сокращенные объекты objectsPassingTest, которые в какой-то момент исчезли из API. Тем не менее, это работает быстро и хорошо. Мне нравится NSPredicate - но для других вещей, где нужен более тяжелый молоток - person Motti Shneor; 18.02.2019
comment
Теперь вы получите ошибку _1 _... он ожидает NSIndexSets - person anoop4real; 28.04.2020
comment
@ anoop4real, я думаю, что причиной предупреждения, которое вы упомянули, является то, что вы по ошибке использовали indexOfObjectPassingTest вместо indexesOfObjectsPassingTest. Легко пропустить, но большая разница :) - person pckill; 05.05.2020

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

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

person Clay Bridges    schedule 04.10.2010
comment
Не могли бы вы дополнить свой ответ примером? Веб-сайты блогов имеют тенденцию умирать, когда они вам нужны больше всего. - person Dan Abramov; 21.03.2012
comment
Добавлено больше предложений в случае линкрота. - person Clay Bridges; 21.03.2012
comment
Защита от гниения ссылок заключается в извлечении соответствующего кода и еще много чего из статей, на которые есть ссылки, а не в добавлении дополнительных ссылок. Сохраните ссылки, но добавьте пример кода. - person toolbear; 07.06.2012
comment
@mydogisbox: все на демонстрации. - person Clay Bridges; 19.06.2012
comment
@ClayBridges Я нашел этот вопрос в поисках способов фильтрации какао. Поскольку в вашем ответе нет примеров кода, мне потребовалось около 5 минут, чтобы просмотреть ваши ссылки, чтобы выяснить, что блоки не позволяют достичь того, что мне нужно. Если бы код был в ответе, это заняло бы, может быть, 30 секунд. Этот ответ НАМНОГО менее полезен без фактического кода. - person N_A; 19.06.2012
comment
@mydogisbox et alia: здесь всего 4 ответа, а значит, достаточно места для блестящего и превосходного собственного ответа на основе кода. Я доволен своим, поэтому, пожалуйста, оставьте его в покое. - person Clay Bridges; 19.06.2012
comment
@ClayBridges SO - это пространство для совместной работы, где приветствуется редактирование ответов. Согласно часто задаваемым вопросам, ваша публикация находится под лицензией creativecommons.org/licenses/by-sa/3.0 < / а>. stackoverflow.com/faq#editing - person N_A; 20.06.2012
comment
@mydogisbox: Извините, но я склонен согласиться с Клэем в этом вопросе. Его ответ получил 12 голосов, так что сообществу он уже нравится, а ваше правка слишком сильно меняется. Опубликуйте свой собственный ответ, если вы считаете, что существующие неадекватны. - person Robert Harvey; 20.06.2012
comment
В защиту mydogisbox ваш ответ станет бесполезным, если какая-либо из ссылок сломается, и предложение людям искать что-то на самом деле не отвечает на вопрос. - person Robert Harvey; 20.06.2012
comment
@RobertHarvey Я не понимаю, как я слишком сильно изменился. Согласно часто задаваемым вопросам: этот сайт редактируется совместно, как и Википедия. Если вы видите что-то, что требует улучшения, нажмите «Изменить» и помогите нам сделать это! Добавление связанного кода в ответ является стандартной процедурой для борьбы с гниением ссылок и сокращает время, необходимое для понимания ответа. Единственная часть, которую я удалил или изменил, кроме добавления связанного кода, была плохой ссылкой. - person N_A; 20.06.2012
comment
mydogisbox прав в этом вопросе; если все, что вы делаете, - это ссылка, на самом деле это не ответ, даже если вы добавляете больше ссылок. См. meta.stackexchange.com/questions/8231. Лакмусовая бумажка: Может ли ваш ответ стоять сам по себе или для этого нужно щелкать ссылки, чтобы иметь какую-либо ценность? В частности, вы утверждаете, что вам, вероятно, лучше использовать блоки, чем NSPredicate, но вы на самом деле не объясняю почему. - person Robert Harvey; 20.06.2012
comment
@mydogisbox: Уже есть принятый ответ, который, вероятно, удовлетворительный. - person Robert Harvey; 20.06.2012
comment
@RobertHarvey Хммм, ладно. Я думал, что вторичные ответы также нуждаются в улучшении, поскольку они также имеют ценность (как в этом случае). Я исправился. - person N_A; 20.06.2012
comment
@RobertHarvey Поскольку вы обычно меня защищаете: спасибо, правда, большое. Я действительно думаю, что ответ стоит сам по себе без ссылок (как если бы вместо этого использовались блоки), поэтому я думаю, что станет бесполезным - это преувеличение. Что касается того, почему и когда блоки лучше, чем NSPredicate, все сводится к сути, ремеслу и мнению, и эти вещи трудно объяснить в то время, когда я планировал бюджет. Таким образом, вам, вероятно, лучше. - person Clay Bridges; 20.06.2012
comment
Заинтересованным читателям (всем троим) я настоятельно рекомендую нажать @ ссылку Роберта Харви. Здесь разгораются споры, и этот вопрос вряд ли решится. - person Clay Bridges; 20.06.2012
comment
Принятый ответ по этой ссылке является общепринятым в настоящее время консенсусом. Ваш ответ прошел успешно, потому что за него так много голосов, но мы регулярно удаляем ответы, содержащие всего лишь одну ссылку. - person Robert Harvey; 20.06.2012
comment
В чем разница в производительности между подходами на основе предикатов и блоков? - person AlexR; 19.10.2012
comment
@AlexR Нет, не знаю. Думаю, лучше задать отдельный вопрос. - person Clay Bridges; 03.04.2013

Предполагая, что все ваши объекты относятся к одному и тому же типу, вы можете добавить метод в качестве категории их базового класса, который вызывает функцию, которую вы используете для своих критериев. Затем создайте объект NSPredicate, который ссылается на этот метод.

В какой-то категории определите свой метод, который использует вашу функцию

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

Тогда везде, где вы будете фильтровать:

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

Конечно, если ваша функция сравнивается только со свойствами, доступными из вашего класса, может быть проще преобразовать условия функции в строку предиката.

person Ashley Clark    schedule 21.11.2008
comment
Мне понравился этот ответ, потому что он изящный и короткий, и сокращает необходимость снова изучить, как формализовать NSPredicate для самого простого - доступа к свойствам и логическому методу. Думаю даже обертка была не нужна. Простой [myArray filterArrayUsingPredicate: [NSPredicate predicateWithFormat: @myMethod = TRUE]]; хватит. Спасибо! (Мне тоже нравятся альтернативы, но этот хорош). - person Motti Shneor; 18.02.2019

NSPredicate - это способ создания условия для фильтрации коллекции (NSArray, NSSet, NSDictionary) nextstep.

Например, рассмотрим два массива 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) выберите * из таблицы, где столбец 1, например "% a%" ->

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

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

person Durai Amuthan.H    schedule 04.04.2013

Оформить заказ в этой библиотеке

https://github.com/BadChoice/Collection

Он поставляется с множеством простых функций массива, чтобы никогда больше не писать цикл

Итак, вы можете просто сделать:

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

or

NSArray* oldHeroes = [self.heroes reject:^BOOL(Hero *object) {
    return object.age.intValue < 20;
}];
person Jordi Puigdellívol    schedule 29.08.2016

Лучший и простой способ - создать этот метод и передать массив и значение:

- (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;
}
person jalmatari    schedule 20.11.2018

Другой метод категорий, который вы могли бы использовать:

- (NSArray *) filteredArrayUsingBlock:(BOOL (^)(id obj))block {
    NSIndexSet *const filteredIndexes = [self indexesOfObjectsPassingTest:^BOOL (id _Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
                                       return block(obj);
                                   }];

    return [self objectsAtIndexes:filteredIndexes];
}
person Dan Rosenstark    schedule 18.01.2021