web-dev-qa-db-fra.com

NSArray Equivalent of Map

Étant donné que des objets NSArray of NSDictionary (contenant des objets et des clés similaires) sont possibles, est-il possible d'écrire une mappe sur un tableau de clés spécifiées? Par exemple, en Ruby, cela peut être fait avec:

array.map(&:name)
69
Stussa

Mise à jour: Si vous utilisez Swift, voir map .


BlocksKit est une option:

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

Souligner est une autre option. Il existe une fonction map, voici un exemple tiré du site Web:

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;
18
Senseful

Il n’enregistre que quelques lignes, mais j’utilise une catégorie sur NSArray. Vous devez vous assurer que votre bloc ne renvoie jamais la valeur nil, mais cela ne vous permet pas de gagner du temps dans les cas où -[NSArray valueForKey:] ne fonctionnera pas.

@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

L'utilisation ressemble beaucoup à -[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)
122
Justin Anderson

Je n'ai aucune idée de ce que fait ce morceau de Ruby mais je pense que vous recherchez l'implémentation de NSArray de - valueForKey: . Ceci envoie -valueForKey: à chaque élément du tableau et retourne un tableau des résultats. Si les éléments du tableau de réception sont NSDictionaries, -valueForKey: est presque identique à -objectForKey:. Cela fonctionnera tant que la clé ne commence pas par un @

74
JeremyP

Pour résumer toutes les autres réponses:

Ruby (comme dans la question):

array.map{|o| o.name}

Obj-C (avec valueForKey ):

[array valueForKey:@"name"];

Obj-C (avec valueForKeyPath, voir Opérateurs de collection KVC ):

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

Obj-C (avec enumerateObjectsUsingBlock ):

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

Swift (avec carte , voir fermetures )

array.map { $0.name }

De plus, il existe quelques bibliothèques qui vous permettent de gérer les tableaux de manière plus fonctionnelle. Cocoa Pods est recommandé d’installer d’autres bibliothèques.

31
tothemario

Je pense que valueForKeyPath est un bon choix.

Asseyez-vous ci-dessous a des exemples très cool. J'espère que c'est utile.

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

Quelques exemples:

NSArray *names = [allEmployees valueForKeyPath: @"[collect].{daysOff<10}.name"];
NSArray *albumCovers = [records valueForKeyPath:@"[collect].{artist like 'Bon Iver'}.<NSUnarchiveFromDataTransformerName>.albumCoverImageData"];
6
Steven Jiang

Je ne suis pas un expert en Ruby, je ne suis donc pas sûr à 100% que je réponds correctement, mais d'après l'interprétation que "map" fait quelque chose pour tout le tableau et génère un nouveau tableau avec les résultats, je pense que vous pensez probablement vouloir est quelque chose comme:

NSMutableArray *replacementArray = [NSMutableArray array];

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

Vous utilisez donc le nouveau support pour les «blocs» (qui sont des fermetures dans un langage plus général) dans OS X 10.6/iOS 4.0 pour effectuer les opérations du bloc dans tout le tableau. Vous choisissez d'effectuer certaines opérations puis d'ajouter le résultat à un tableau séparé.

Si vous envisagez de prendre en charge 10.5 ou iOS 3.x, vous souhaiterez probablement insérer le code approprié dans l'objet et utiliser makeObjectsPerformSelector: ou, au pire, effectuer une itération manuelle du tableau à l'aide de for(NSDictionary *dictionary in existingArray).

4
Tommy
@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


Une légère amélioration par rapport à quelques réponses publiées.

  1. Vérifications pour nil - vous pouvez utiliser nil pour supprimer des objets pendant le mappage
  2. Le nom de la méthode reflète mieux le fait que la méthode ne modifie pas le tableau sur lequel elle est appelée
  3. C’est plus un problème de style mais IMO a amélioré les noms des arguments du bloc
  4. Syntaxe Dot pour Count
2
james_womack

Pour Objective-C, j'ajouterais la bibliothèque ObjectiveSugar à cette liste de réponses: https://github.com/supermarin/ObjectiveSugar

De plus, son slogan est "Ajouts ObjectiveC pour les humains. Style Ruby". ce qui devrait bien convenir à l'OP ;-)

Mon cas d'utilisation le plus courant est de mapper un dictionnaire renvoyé par un appel de serveur vers un tableau d'objets plus simples, par exemple. Obtenir un NSArray d’ID NSString à partir de vos messages NSDictionary:

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

Pour Objective-C, j'ajouterais les fonctions d'ordre supérieur à cette liste de réponses: https://github.com/fanpyi/Higher-Order-Functions ;

Il y a un étudiant JSON array JSONList comme ceci:

[
    {"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
范陆离