NSArray-Äquivalent zur Karte

75

Ist es bei einem NSArrayder NSDictionaryObjekte (die ähnliche Objekte und Schlüssel enthalten) möglich, eine Zuordnung zu einem Array des angegebenen Schlüssels durchzuführen? In Ruby ist dies beispielsweise möglich mit:

array.map(&:name)
Stussa
quelle
1
Welchen Rückgabewert erhoffen Sie sich von dem Anruf?
Jaminguy
Rubys Karte kommt der typischen funktionalen Programmieridee der Karte nahe. Transformieren Sie bei einer bestimmten Sammlung jedes Objekt in der Sammlung und geben Sie die resultierende Sammlung zurück.
James Moore
Übrigens habe ich eine Objective-C-Bibliothek geschrieben, die Ruby's map/ collectmethod für NSArray
Folgendes bereitstellt

Antworten:

19

Update: Wenn Sie Swift verwenden, siehe Karte .


BlocksKit ist eine Option:

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

Unterstrich ist eine weitere Option. Es gibt eine mapFunktion, hier ein Beispiel von der Website:

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;
Sinnvoll
quelle
9
IMHO ist die Verwendung der Punktsyntax für Objective-C-Methoden keine gute Idee. Das ist nicht Ruby.
Rudolf Adamkovič
1
@RudolfAdamkovic Obwohl ich Ihnen im Prinzip zustimme, wäre es weniger lesbar, dies mit Klammernotation zu tun.
11684
8
Wenn Sie nicht überall funktionale Operationen ausführen, ist es eine viel bessere Idee, die Standardmethoden mapObjectsUsingBlock: oder valueForKey: des NSArray zu verwenden (in den folgenden Antworten vorgeschlagen). Das Vermeiden einiger weiterer Zeichen mit "hässlicher" Objective-C-Syntax rechtfertigt nicht das Hinzufügen einer Cocoapods-Abhängigkeit.
Gesellig
3
mapObjectsUsingBlock:ist keine Standardfunktion, sondern eine Erweiterung, die von einer anderen Antwort vorgeschlagen wird
mcfedr
1
Die von "Unterstrich" in der Antwort verlinkte Seite scheint nicht mehr mit Objective-C verbunden zu sein.
Pang
130

Es werden nur ein paar Zeilen gespeichert, aber ich verwende eine Kategorie in NSArray. Sie müssen sicherstellen, dass Ihr Block niemals Null zurückgibt. Ansonsten ist dies eine Zeitersparnis für Fälle, in denen dies -[NSArray valueForKey:]nicht funktioniert.

@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

Die Verwendung ist ähnlich wie -[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)
Justin Anderson
quelle
Vergessen Sie nicht, den Block, in den Sie gehen, auf Null zu prüfen mapObjectsUsingBlock. Es stürzt derzeit ab, wenn Sie einen Nullblock übergeben.
DHamrick
7
mikeash.com/pyblog/friday-qa-2009-08-14-practical-blocks.html enthält eine bessere (syntaktische) Variante der oben genannten, IMHO.
Manav
@ Justin Anderson, können Sie bitte ein Beispiel für die Verwendung der oben genannten Punkte in Ihre Antwort aufnehmen?
Abbood
2
Du gibst einen MutableArrayRücken zurück. Ist es besser zu tun return [result copy]?
Herr Rogers
1
@abbood Wenn Sie eine 1-1-Zuordnung der Indizes von Ihrem ursprünglichen Array zu Ihrem zugeordneten Array beibehalten möchten, würde ich die Rückkehr empfehlen NSNull. Dies ist das Obj-C-Objekt zur Darstellung von Null in Auflistungsklassen. NSNullist ziemlich ungewöhnlich und mühsam zu benutzen, da Obj-C kein Unboxing macht, also NSNull != nil. Wenn Sie jedoch nur einige Elemente aus dem Array herausfiltern möchten, können Sie Änderungen vornehmen, um mapObjectsUsingBlockzu prüfen, ob der Block keine Antworten enthält, und diese überspringen.
Justin Anderson
78

Ich habe keine Ahnung, was dieses bisschen Ruby macht, aber ich denke, Sie suchen nach NSArrays Implementierung von -valueForKey : . Dies sendet -valueForKey:an jedes Element des Arrays und gibt ein Array der Ergebnisse zurück. Wenn die Elemente im empfangenden Array NSDictionaries sind, -valueForKey:ist dies fast dasselbe wie -objectForKey:. Es funktioniert, solange der Schlüssel nicht mit einem beginnt@

JeremyP
quelle
Genau das macht es! Vielen Dank!
Stussa
Dies ist nicht das, was die Karte tut. Rubys Karte benötigt einen Block, nicht nur einen einzelnen Methodenaufruf. Dieser spezielle Fall funktioniert nur, weil der Block ein einzelner Aufruf einer einzelnen Methode ist. Die Antwort von @JustinAnderson ist viel näher an dem, was Sie in Ruby tun können.
James Moore
2
Tatsächlich ist diese Lösung weitaus flexibler als Sie vielleicht annehmen, da valueForKey:der entsprechende Getter aufgerufen wird. Wenn Sie also im Voraus wissen, welche verschiedenen Aufgaben ein Objekt ausführen soll, können Sie die erforderlichen Getter in das Objekt einfügen (z. B. mit einer Kategorie) und dann NSArrays verwenden valueForKey:, um einen Aufruf an einen bestimmten Getter über das Array weiterzuleiten zu jedem Objekt und Abrufen eines Arrays der Ergebnisse.
Matt
3
+1; Dies ist die sauberste Methode, um den speziellen (ziemlich häufigen) Anwendungsfall zu behandeln, den der Fragesteller beschrieben hat, auch wenn es sich nicht um eine vollständig flexible Kartenfunktion handelt, wie im Fragentitel gefordert. mapVerwenden Sie stattdessen die Kategorie aus Justin Andersons Antwort , wenn Sie wirklich eine Methode benötigen .
Mark Amery
1
Ich komme immer wieder auf diese Antwort zurück, um mich an diesen Namen zu erinnern. Ich bin es so gewohnt, in anderen Programmiersprachen "abzubilden", dass ich mich nie an "valueForKey" erinnern kann
bis zum
38

Um alle anderen Antworten zusammenzufassen:

Ruby (wie in der Frage):

array.map{|o| o.name}

Obj-C (mit valueForKey):

[array valueForKey:@"name"];

Obj-C (mit valueForKeyPath, siehe KVC Collection Operators ):

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

Obj-C (mit enumerateObjectsUsingBlock):

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

Swift (mit Karte , siehe Schließungen )

array.map { $0.name }

Außerdem gibt es einige Bibliotheken, mit denen Sie Arrays funktionaler handhaben können. CocoaPods wird empfohlen, um andere Bibliotheken zu installieren.

tothemario
quelle
Ausgezeichnet! Vielen Dank
Muhammad Aamir Ali
8

Ich denke, valueForKeyPath ist eine gute Wahl.

Sitzen Sie unten hat sehr coole Beispiele. Hoffe es ist hilfreich.

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

Ein Beispiel:

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

Ich bin kein Ruby-Experte, daher bin ich nicht zu 100% sicher, dass ich richtig antworte, aber basierend auf der Interpretation, dass 'map' etwas mit allem im Array macht und ein neues Array mit den Ergebnissen erzeugt, denke ich, was Sie wahrscheinlich tun wollen ist so etwas wie:

NSMutableArray *replacementArray = [NSMutableArray array];

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

Sie verwenden also die neue Unterstützung für 'Blöcke' (die im Allgemeinen Sprachabschlüsse sind) in OS X 10.6 / iOS 4.0, um die Dinge im Block für alles im Array auszuführen. Sie möchten eine Operation ausführen und das Ergebnis dann einem separaten Array hinzufügen.

Wenn Sie 10.5 oder iOS 3.x unterstützen möchten, möchten Sie wahrscheinlich den relevanten Code in das Objekt einfügen und makeObjectsPerformSelector verwenden: oder im schlimmsten Fall eine manuelle Iteration des Arrays mit for(NSDictionary *dictionary in existingArray).

Tommy
quelle
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


Eine leichte Verbesserung gegenüber einigen der veröffentlichten Antworten.

  1. Überprüft auf Null - Sie können Null verwenden, um Objekte während der Zuordnung zu entfernen
  2. Der Methodenname spiegelt besser wider, dass die Methode das aufgerufene Array nicht ändert
  3. Dies ist eher eine Stilsache, aber ich habe IMO die Argumentnamen des Blocks verbessert
  4. Punktsyntax für die Zählung
james_womack
quelle
0

Für Objective-C würde ich die ObjectiveSugar-Bibliothek zu dieser Liste von Antworten hinzufügen: https://github.com/supermarin/ObjectiveSugar

Außerdem lautet der Slogan "ObjectiveC-Ergänzungen für Menschen. Ruby-Stil". was gut zu OP passen sollte ;-)

Mein häufigster Anwendungsfall ist das Zuordnen eines Wörterbuchs, das von einem Serveraufruf zurückgegeben wird, zu einem Array einfacherer Objekte, z. B. zum Abrufen eines NSArray von NSString-IDs aus Ihren NSDictionary-Posts:

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

Für Objective-C würde ich die Funktionen höherer Ordnung zu dieser Liste von Antworten hinzufügen: https://github.com/fanpyi/Higher-Order-Functions ;

Es gibt ein JSON-Array studentJSONList wie folgt:

[
    {"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"];
}];
范 陆 离
quelle
0

Hierfür gibt es einen speziellen Schlüsselpfadoperator : @unionOfObjects. Wahrscheinlich wurde es [collect]von früheren Versionen ersetzt.

Stellen Sie sich eine TransactionKlasse mit payeeEigentum vor:

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

Apple-Dokumente zu Array-Operatoren in der Schlüsselwertcodierung .

kelin
quelle
-1

Swift führt eine neue Kartenfunktion ein .

Hier ist ein Beispiel aus der Dokumentation :

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"]

Die Zuordnungsfunktion schließt, dass ein Wert eines beliebigen Typs zurückgegeben wird, und ordnet die vorhandenen Werte im Array Instanzen dieses neuen Typs zu.

Sinnvoll
quelle
5
Sie könnten ein einfacheres Beispiel verwenden, wie z [1,2,3].map {$0 * 2} //=> [2,4,6]. UND, die Frage ist über Obj-C NSArray, nicht Swift;)
tothemario