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





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" }
@mmalc - Может быть, более подходящим, но определенно более удобным для просмотра прямо здесь.
NSPredicate мертв, да здравствуют блоки! ср. мой ответ ниже.
Что означает «содержит [c]»? Я всегда вижу [c], но не понимаю, что он делает?
@ user1007522, [c] делает совпадение нечувствительным к регистру.
Предполагая, что все ваши объекты относятся к одному и тому же типу, вы можете добавить метод в качестве категории их базового класса, который вызывает функцию, которую вы используете для своих критериев. Затем создайте объект 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"]]; хватит. Спасибо! (Мне тоже нравятся альтернативы, но этот хорош).
Если у вас OS X 10.6 / iOS 4.0 или новее, вам, вероятно, лучше использовать блоки, чем NSPredicate. См. -[NSArray indexesOfObjectsPassingTest:] или напишите свою собственную категорию, чтобы добавить удобный метод -select: или -filter: (пример).
Хотите, чтобы кто-то еще написал эту категорию, протестировал ее и т. д.? Проверьте BlocksKit (массив документов). И есть еще примеры много, которые можно найти, скажем, путем поиска, например, "Выбор категории блока nsarray".
Не могли бы вы дополнить свой ответ примером? Веб-сайты блогов имеют тенденцию умирать, когда они вам нужны больше всего.
Добавлено больше предложений в случае линкрота.
Защита от гниения ссылок заключается в извлечении релевантного кода и прочего из статей, на которые есть ссылки, а не в добавлении дополнительных ссылок. Сохраните ссылки, но добавьте пример кода.
@mydogisbox: это демонстрация.
@ClayBridges Я нашел этот вопрос в поисках способов фильтрации какао. Поскольку в вашем ответе нет примеров кода, мне потребовалось около 5 минут, чтобы просмотреть ваши ссылки, чтобы понять, что блоки не достигнут того, что мне нужно. Если бы код был в ответе, это заняло бы, может быть, 30 секунд. Этот ответ НАМНОГО менее полезен без фактического кода.
@mydogisbox et alia: здесь всего 4 ответа, и, таким образом, достаточно места для блестящего и превосходного собственного ответа на основе кода. Я доволен своим, поэтому, пожалуйста, оставьте его в покое.
@ClayBridges SO - это пространство для совместной работы, где приветствуется редактирование ответов. Согласно часто задаваемым вопросам, ваше сообщение имеет лицензию creativecommons.org/licenses/by-sa/3.0. stackoverflow.com/faq#editing
@mydogisbox: Извините, но я склонен согласиться с Клэем в этом вопросе. Его ответ получил 12 голосов, так что сообществу он уже нравится, а ваши правки слишком сильно меняются. Опубликуйте свой собственный ответ, если вы считаете, что существующие неадекватны.
В защиту mydogisbox ваш ответ станет бесполезным, если какая-либо из ссылок сломается, и призыв людей искать что-то на самом деле не отвечает на вопрос.
@RobertHarvey Я не понимаю, как я слишком сильно изменился. Согласно часто задаваемым вопросам: «этот сайт редактируется совместно, как и Википедия. Если вы видите что-то, что требует улучшения, нажмите« Изменить »и помогите нам сделать это!» Включение связанного кода в ответ является стандартной процедурой для борьбы с гниением ссылок и сокращает время, необходимое для понимания ответа. Единственная часть, которую я удалил или изменил, кроме добавления связанного кода, была плохой ссылкой.
mydogisbox прав в этом вопросе; если все, что вы делаете, - это ссылка, на самом деле это не ответ, даже если вы добавляете больше ссылок. См. meta.stackexchange.com/questions/8231. Лакмусовая бумажка: Может ли ваш ответ стоять сам по себе или для этого нужно щелкать ссылки, чтобы иметь какую-либо ценность? В частности, вы утверждаете, что «вам, вероятно, лучше с блоками, чем с NSPredicate», но вы не объясняете, почему.
@mydogisbox: Уже есть принятый ответ, который, вероятно, удовлетворительный.
@RobertHarvey Хммм, ладно. Я думал, что вторичные ответы также нуждаются в улучшении, поскольку они также имеют ценность (как в этом случае). Я исправился.
@RobertHarvey Поскольку вы обычно меня защищаете: спасибо, правда, большое. Я действительно думаю, что ответ сам по себе без ссылок (как и «Используйте вместо этого блоки»), поэтому я думаю, что «станет бесполезным» - это преувеличение. Что касается того, почему и когда блоки лучше, чем NSPredicate, все сводится к сути, ремеслу и мнению, и эти вещи трудно объяснить в то время, когда я планировал бюджет. Таким образом, «вам, вероятно, лучше».
Для заинтересованных читателей (всех троих) я настоятельно рекомендую нажать Ссылка @ RobertHarvey. Там разгораются споры, и этот вопрос вряд ли решится.
Принятый ответ по этой ссылке является общепринятым в настоящее время консенсусом. Ваш ответ прошел успешно, потому что за него так много голосов, но мы регулярно удаляем ответы, содержащие всего лишь одну ссылку.
В чем разница в производительности между подходами на основе предикатов и блоков?
@AlexR Нет, не знаю. Думаю, лучше задать отдельный вопрос.
Основываясь на ответе Клея Бриджеса, вот пример фильтрации с использованием блоков (замените yourArray на имя переменной вашего массива, а testFunc на имя вашей функции тестирования):
yourArray = [yourArray objectsAtIndexes:[yourArray indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self testFunc:obj];
}]];
Наконец, ответ не только упоминает фильтрацию с помощью блоков, но также дает хороший пример того, как это сделать. Спасибо.
Мне нравится этот ответ; хотя filteredArrayUsingPredicate более компактный, тот факт, что вы не используете никаких предикатов, как бы скрывает цель.
Это самый современный способ сделать это. Лично я скучаю по старому сокращению «objectsPassingTest», которое в какой-то момент исчезло из API. Тем не менее, это работает быстро и хорошо. Мне нравится NSPredicate - но для других вещей, где нужен более тяжелый молоток
Теперь вы получите ошибку Implicit conversion of 'NSUInteger' (aka 'unsigned long') to 'NSIndexSet * _Nonnull' is disallowed with ARC ... он ожидает NSIndexSets
@ anoop4real, я думаю, что причиной предупреждения, о котором вы упомянули, является то, что вы по ошибке использовали indexOfObjectPassingTest вместо indexesOfObjectsPassingTest. Легко пропустить, но большая разница :)
NSPredicate - это новый способ создания условия для фильтрации коллекции (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) выберите * из таблицы, где столбец1, например '% a%' ->
[NSMutableArray arrayWithArray:[arr filteredArrayUsingPredicate:predicate]];
надеюсь, это поможет
Есть множество способов сделать это, но, безусловно, самый изящный - использовать [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.
}]];
Я думаю, что это настолько лаконично, насколько это возможно.
Для тех, кто работает с NSArray в Swift, вы можете предпочесть эту краткую версию даже больше:
let filteredArray = array.filter { $0.shouldIKeepYou() }
filter - это просто метод на Array (NSArray неявно связан с Array Swift). Он принимает один аргумент: замыкание, которое принимает один объект в массиве и возвращает Bool. В вашем закрытии просто верните true для любых объектов, которые вы хотите в отфильтрованном массиве.
Какую роль здесь играют привязки NSDictionary *?
Привязки @Kaitain требуются API NSPredicate predicateWithBlock:.
@Kaitain Словарь bindings может содержать привязки переменных для шаблонов.
Намного полезнее, чем принятый ответ при работе со сложными объектами :)
Оформить заказ в этой библиотеке
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;
}];
Лучший и простой способ - создать этот метод и передать массив и значение:
- (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;
}
Другой метод категорий, который вы могли бы использовать:
- (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];
}
Я слушал подкаст Папы Смурфа, и Папа Смурф сказал, что ответы должны находиться в StackOverflow, чтобы сообщество могло их оценивать и улучшать.