NSArray еквівалент карти


75

Враховуючи an NSArrayіз NSDictionaryоб’єктів (що містять подібні об’єкти та ключі), чи можна записати виконувати карту в масив зазначеного ключа? Наприклад, у Ruby це можна зробити за допомогою:

array.map(&:name)

1
Яке повернене значення ви сподіваєтесь отримати від дзвінка?
jaminguy

Карта Рубі близька до типової ідеї функціонального програмування карти. Отримавши колекцію, перетворіть кожен об’єкт у колекції та поверніть отриману колекцію.
James Moore

До речі, я написав бібліотеку Objective-C, яка надає Ruby map/ collectметод для NSArray: github.com/mdippery/collections
mipadi

Відповіді:


19

Оновлення: якщо ви використовуєте Swift, дивіться карту .


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;

9
IMHO, використання крапкового синтаксису для методів Objective-C не є гарною ідеєю. Це не Рубі.
Рудольф Адамкович,

1
@RudolfAdamkovic Незважаючи на те, що я в принципі з вами погоджуюсь, робити це з позначеннями в дужках буде менш читабельним.
11684

8
Якщо ви не виконуєте функціональних операцій повсюдно, набагато кращою ідеєю є використання стандартних методів mapObjectsUsingBlock: or valueForKey: NSArray (запропонованих у відповідях нижче). Уникнення ще кількох символів "потворного" синтаксису Objective-C не виправдовує додавання залежності Cocoapods.
Gregarious

3
mapObjectsUsingBlock:це не стандартна функція, а розширення, запропоноване іншою відповіддю
mcfedr

1
Сторінка, на яку у відповіді посилається "Підкреслення", схоже, більше не пов’язана з Objective-C.
Панг,

130

Це економить лише пару рядків, але я використовую категорію на 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)

Не забудьте перевірити нуль на блоці, куди ви проходите mapObjectsUsingBlock. Наразі він вийде з ладу, якщо ви пройдете в нульовий блок.
DHamrick

7
mikeash.com/pyblog/friday-qa-2009-08-14-practical-blocks.html містить кращий (синтаксичний) варіант вищезазначеного, IMHO.
Manav

@Justin Anderson, чи можете ви, будь ласка, включити у свою відповідь зразок використання вищезазначеного?
настоятель

2
Ви повертаєте MutableArrayназад. Чи краще це робити return [result copy]?
Містер Роджерс

1
@abbood Якщо ви хочете зберегти 1-1 відображення індексів від вашого початкового масиву до вашого відображеного масиву, я пропоную повернутися NSNull. Це об’єкт Obj-C для представлення nil у класах колекції. NSNullце досить рідкісне явище та клопіт у використанні, хоча Obj-C не робить розпакування, тому NSNull != nil. Але якщо ви хочете просто відфільтрувати деякі елементи з масиву, ви можете mapObjectsUsingBlockвнести зміни, щоб перевірити відсутність відповідей блоку та пропустити їх.
Джастін Андерсон

78

Я поняття не маю , що робить це трохи Ruby , але я думаю , що ви шукаєте реалізації NSArray про -valueForKey: . Це надсилає -valueForKey:кожному елементу масиву і повертає масив результатів. Якщо елементами в приймальному масиві є NSDictionaries, -valueForKey:це майже те саме, що і -objectForKey:. Він буде працювати, доки клавіша не починається з символу@


Він робить саме це! Дуже дякую!
Стусса

Це не те, що робить карта. Карта Рубі приймає блок, а не лише один виклик методу. Цей конкретний випадок працює лише тому, що блок - це один виклик одного методу. Відповідь від @JustinAnderson набагато ближче до того, що ви можете робити в Ruby.
James Moore

2
Насправді, це рішення є набагато гнучкішим, ніж ви можете припустити, оскільки valueForKey:працює, викликаючи відповідний геттер. Таким чином, якщо ви заздалегідь знаєте різні речі, які, можливо, ви хочете зробити об’єктом, ви можете внести в нього потрібні геттери (наприклад, з категорією), а потім використовувати NSArray valueForKey:як спосіб передачі виклику конкретному геттеру через масив. до кожного об’єкта та отримання масиву результатів.
matt

3
+1; це найчистіший спосіб розглянути той конкретний (досить поширений) випадок використання, який описував запитувач, навіть якщо це не повністю гнучка функція картографії, як це вимагається в назві питання. Для людей, яким справді потрібен mapметод, використовуйте замість цього категорію з відповіді Джастіна Андерсона.
Mark Amery

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

38

Підсумовуючи всі інші відповіді:

Рубі (як у запитанні):

array.map{|o| o.name}

Obj-C (з valueForKey):

[array valueForKey:@"name"];

Obj-C (з valueForKeyPath, див. Оператори збору KVC ):

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

Obj-C (з enumerateObjectsUsingBlock):

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

Свіфт (з картою , див. Закриття )

array.map { $0.name }

Є ще кілька бібліотек, які дозволяють обробляти масиви більш функціонально. CocoaPods рекомендується встановлювати інші бібліотеки.


Відмінно! Щиро дякую
Мухаммед Аамір Алі

8

Я думаю, що 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

Я не є експертом 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
@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

Для Objective-C я б додав бібліотеку ObjectiveSugar до цього списку відповідей: https://github.com/supermarin/ObjectiveSugar

Плюс, його слоган - "Додатки ObjectiveC для людей. Стиль Ruby". що має добре пасувати OP ;-)

Моїм найпоширенішим випадком використання є зіставлення словника, що повертається викликом сервера, до масиву простіших об’єктів, наприклад отримання NSArray ідентифікаторів NSString із ваших публікацій NSDictionary:

NSArray *postIds = [results map:^NSString*(NSDictionary* post) {
                       return [post objectForKey:@"post_id"];
                   }];

0

Для 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

Існує спеціальний оператор копіювально-шлях для цього: @unionOfObjects. Можливо, це замінено на [collect]попередні версії.

Уявіть Transactionклас із payeeвластивістю:

NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];

Документи Apple щодо операторів масивів у кодуванні ключ-значення .


-1

Свіфт представляє нову функцію карти .

Ось приклад із документації :

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 приймає закриття, яке повертає значення будь-якого типу і відображає наявні значення в масиві в екземпляри цього нового типу.


5
Ви можете використати простіший приклад, наприклад [1,2,3].map {$0 * 2} //=> [2,4,6]. І, питання стосується Obj-C NSArray, а не Свіфта;)
tothemario
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.