NSArray Эквивалент карты - PullRequest
70 голосов
/ 25 мая 2011

Учитывая NSArray из NSDictionary объектов (содержащих похожие объекты и ключи), можно ли записать выполнение карты в массив указанного ключа?Например, в Ruby это можно сделать с помощью:

array.map(&:name)

Ответы [ 10 ]

123 голосов
/ 30 августа 2011

Сохраняет только пару строк, но я использую категорию на NSArray.Вы должны убедиться, что ваш блок никогда не вернет ноль, но кроме этого это экономит время для случаев, когда -[NSArray valueForKey:] не будет работать.

@interface NSArray (Map)

- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block;

@end

@implementation NSArray (Map)

- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [result addObject:block(obj, idx)];
    }];
    return result;
}

@end

Использование очень похоже на -[NSArray enumerateObjectsWithBlock:]:

NSArray *people = @[
                     @{ @"name": @"Bob", @"city": @"Boston" },
                     @{ @"name": @"Rob", @"city": @"Cambridge" },
                     @{ @"name": @"Robert", @"city": @"Somerville" }
                  ];
// per the original question
NSArray *names = [people mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
    return obj[@"name"];
}];
// (Bob, Rob, Robert)

// you can do just about anything in a block
NSArray *fancyNames = [people mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
    return [NSString stringWithFormat:@"%@ of %@", obj[@"name"], obj[@"city"]];
}];
// (Bob of Boston, Rob of Cambridge, Robert of Somerville)
74 голосов
/ 25 мая 2011

Я понятия не имею, что делает этот бит Ruby, но я думаю , что вам нужна реализация NSArray -valueForKey: .Это отправляет -valueForKey: каждому элементу массива и возвращает массив результатов.Если элементы в принимающем массиве являются NSDictionaries, -valueForKey: почти совпадает с -objectForKey:.Он будет работать, пока ключ не начинается с @

31 голосов
/ 31 марта 2015

Подведем итог всем остальным ответам:

Рубин (как в вопросе):

array.map{|o| o.name}

Obj-C (с valueForKey ):

[array valueForKey:@"name"];

Obj-C (со значением ForKeyPath, см. Операторы сбора KVC ):

[array valueForKeyPath:@"[collect].name"];

Obj-C (с перечисление объектов, использующих блок ):

NSMutableArray *newArray = [NSMutableArray array];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
     [newArray addObject:[obj name]];
}];

Swift (с картой , см. закрытия )

array.map { $0.name }

И есть несколько библиотек, которые позволяют вам обрабатывать массивы более функционально. Cocoa Pods рекомендуется для установки других библиотек.

18 голосов
/ 27 июня 2013

Обновление: Если вы используете Swift, см. map .


BlocksKit - опция:

NSArray *new = [stringArray bk_map:^id(NSString *obj) { 
    return [obj stringByAppendingString:@".png"]; 
}];

Подчеркивание - еще один вариант.Есть функция map, вот пример с сайта:

NSArray *tweets = Underscore.array(results)
    // Let's make sure that we only operate on NSDictionaries, you never
    // know with these APIs ;-)
    .filter(Underscore.isDictionary)
    // Remove all tweets that are in English
    .reject(^BOOL (NSDictionary *tweet) {
        return [tweet[@"iso_language_code"] isEqualToString:@"en"];
    })
    // Create a simple string representation for every tweet
    .map(^NSString *(NSDictionary *tweet) {
        NSString *name = tweet[@"from_user_name"];
        NSString *text = tweet[@"text"];

        return [NSString stringWithFormat:@"%@: %@", name, text];
    })
    .unwrap;
6 голосов
/ 08 августа 2014

Я думаю, что ValueForKeyPath - хороший выбор.

Сидите ниже, есть очень крутые примеры. Надеется, что это полезно.

http://kickingbear.com/blog/archives/9

Пример:

NSArray *names = [allEmployees valueForKeyPath: @"[collect].{daysOff<10}.name"];
NSArray *albumCovers = [records valueForKeyPath:@"[collect].{artist like 'Bon Iver'}.<NSUnarchiveFromDataTransformerName>.albumCoverImageData"];
4 голосов
/ 25 мая 2011

Я не эксперт по Ruby, поэтому я не уверен на 100%, что отвечаю правильно, но на основании интерпретации, что «map» что-то делает со всем в массиве и производит новый массив с результатами, я думаю то, что вы, вероятно, хотите, это что-то вроде:

NSMutableArray *replacementArray = [NSMutableArray array];

[existingArray enumerateObjectsUsingBlock:
    ^(NSDictionary *dictionary, NSUInteger idx, BOOL *stop)
    {
         NewObjectType *newObject = [something created from 'dictionary' somehow];
         [replacementArray addObject:newObject];
    }
];

Таким образом, вы используете новую поддержку «блоков» (которые являются замыканиями в более общем смысле) в OS X 10.6 / iOS 4.0 для выполнения блока в блоке для всего в массиве. Вы решили выполнить какую-либо операцию, а затем добавить результат в отдельный массив.

Если вы ищете поддержку 10.5 или iOS 3.x, вы, вероятно, захотите включить соответствующий код в объект и использовать makeObjectsPerformSelector: или, в худшем случае, выполнить итерацию массива вручную, используя for(NSDictionary *dictionary in existingArray) .

2 голосов
/ 26 января 2013
@implementation NSArray (BlockRockinBeats)

- (NSArray*)mappedWithBlock:(id (^)(id obj, NSUInteger idx))block {
    NSMutableArray* result = [NSMutableArray arrayWithCapacity:self.count];
    [self enumerateObjectsUsingBlock:^(id currentObject, NSUInteger index, BOOL *stop) {
        id mappedCurrentObject = block(currentObject, index);
        if (mappedCurrentObject)
        {
            [result addObject:mappedCurrentObject];
        }
    }];
    return result;
}

@end


Небольшое улучшение после нескольких ответов.

  1. Проверяет ноль - вы можете использовать ноль для удаления объектов при отображении
  2. Имя метода лучше отражает то, что метод не изменяет массив, к которому он вызван
  3. Это скорее стиль, но я IMO улучшил имена аргументов блока
  4. Синтаксис точки для количества
0 голосов
/ 22 июня 2017

Для Objective-C я бы добавил функции высшего порядка к этому списку ответов: https://github.com/fanpyi/Higher-Order-Functions;

Существует JSON-массив studentJSONList, подобный этому:

[
    {"number":"100366","name":"Alice","age":14,"score":80,"gender":"female"},
    {"number":"100368","name":"Scarlett","age":15,"score":90,"gender":"female"},
    {"number":"100370","name":"Morgan","age":16,"score":69.5,"gender":"male"},
    {"number":"100359","name":"Taylor","age":14,"score":86,"gender":"female"},
    {"number":"100381","name":"John","age":17,"score":72,"gender":"male"}
]
//studentJSONList map to NSArray<Student *>
NSArray *students = [studentJSONList map:^id(id obj) {
return [[Student alloc]initWithDictionary:obj];
}];

// use reduce to get average score
NSNumber *sum = [students reduce:@0 combine:^id(id accumulator, id item) {
Student *std = (Student *)item;
return @([accumulator floatValue] + std.score);
}];
float averageScore = sum.floatValue/students.count;

// use filter to find all student of score greater than 70
NSArray *greaterthan = [students filter:^BOOL(id obj) {
Student *std = (Student *)obj;
return std.score > 70;
}];

//use contains check students whether contain the student named 'Alice'
BOOL contains = [students contains:^BOOL(id obj) {
Student *std = (Student *)obj;
return [std.name isEqual:@"Alice"];
}];
0 голосов
/ 08 февраля 2017

Для Objective-C я бы добавил библиотеку ObjectiveSugar к этому списку ответов: https://github.com/supermarin/ObjectiveSugar

Плюс, его слоган "ObjectiveC дополнения для людей. Рубиновый стиль". который должен хорошо подходить к OP; -)

Мой наиболее распространенный вариант использования - сопоставление словаря, возвращаемого при обращении к серверу, с массивом более простых объектов, например получить NSArray из NSString ID из ваших сообщений NSDictionary:

NSArray *postIds = [results map:^NSString*(NSDictionary* post) {
                       return [post objectForKey:@"post_id"];
                   }];
0 голосов
/ 05 июня 2014

Swift представляет новую функцию map .

Вот пример из документации :

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

let strings = numbers.map {
    (var number) -> String in
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]! + output
        number /= 10
    }
    return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

Функция map принимает замыкание, которое возвращает значение любого типа и отображает существующие значения в массивэкземплярам этого нового типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...