【问题标题】:NSArray Equivalent of MapNSArray 等价于 Map
【发布时间】:2011-09-01 22:16:02
【问题描述】:

给定 NSArrayNSDictionary 对象(包含相似的对象和键)是否可以将执行映射写入指定键的数组?例如,在 Ruby 中,可以这样做:

array.map(&:name)

【问题讨论】:

  • 你希望从调用中得到什么返回值?
  • Ruby 的 map 接近于 map 的典型函数式编程思想。给定一个集合,转换集合中的每个对象,并返回结果集合。
  • 顺便说一下,我写了一个Objective-C库,它为NSArray提供了Ruby的map/collect方法:github.com/mdippery/collections

标签: iphone objective-c cocoa-touch cocoa macos


【解决方案1】:

它只节省了几行,但我在 NSArray 上使用了一个类别。您需要确保您的块永远不会返回 nil,但除此之外,对于 -[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的块上的nil。如果你传入一个 nil 块,它当前会崩溃。
  • mikeash.com/pyblog/friday-qa-2009-08-14-practical-blocks.html 包含上述更好的(句法)变体,恕我直言。
  • @Justin Anderson 您能否在回答中包含上述示例用法?
  • 您返回的是MutableArrayreturn [result copy] 是更好的做法吗?
  • @abbood 如果您希望保持索引从原始数组到映射数组的 1-1 映射,我建议返回 NSNull。那就是在集合类中表​​示 nil 的 Obj-C 对象。 NSNull 很少见,但使用起来很麻烦,因为 Obj-C 不做拆箱,所以 NSNull != nil。但是,如果您只想从数组中过滤出一些项目,您可以修改 mapObjectsUsingBlock 以检查块中的 nil 响应并跳过它们。
【解决方案2】:

我不知道那一点 Ruby 做了什么,但我认为您正在寻找 NSArray 的 -valueForKey: 实现。这会将-valueForKey: 发送到数组的每个元素并返回结果数组。如果接收数组中的元素是 NSDictionaries,-valueForKey:-objectForKey: 几乎相同。只要密钥不是以@ 开头的,它就可以工作

【讨论】:

  • 就是这样!非常感谢!
  • 这不是地图的作用。 Ruby 的 map 需要一个块,而不仅仅是一个方法调用。这种特殊情况之所以有效,是因为该块是对单个方法的单个调用。 @JustinAnderson 的回答更接近于您在 Ruby 中可以做的事情。
  • 实际上,这个解决方案比你想象的要灵活得多,因为valueForKey: 通过调用相应的getter 来工作。因此,如果您事先知道您可能希望一个对象执行的各种操作,您可以将所需的 getter 注入其中(例如使用类别),然后使用 NSArray 的 valueForKey: 作为将调用传递给特定 getter 的一种方式通过数组到每个对象并获得结果数组。
  • +1;这是处理提问者所描述的特定(相当常见)用例的最简洁方法,即使它不是问题标题中要求的完全灵活的地图功能。对于真正需要 map 方法的人,请改用 Justin Anderson 的答案中的类别。
  • 我不断回到这个答案来记住这个名字。我已经习惯了在其他编程语言中“映射”,以至于我永远记不起“valueForKey”
【解决方案3】:

总结所有其他答案:

Ruby(如问题所示):

array.map{|o| o.name}

Obj-C(带有valueForKey):

[array valueForKey:@"name"];

Obj-C(与valueForKeyPath,见KVC Collection Operators):

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

Obj-C(带有enumerateObjectsUsingBlock):

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

Swift(与map,见closures

array.map { $0.name }

而且,有几个库可以让您以更实用的方式处理数组。推荐CocoaPods安装其他库。

【讨论】:

  • 太棒了!非常感谢
【解决方案4】:

更新:如果您使用的是 Swift,请参阅 map


BlocksKit 是一个选项:

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

Underscore 是另一种选择。有一个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;

【讨论】:

  • 恕我直言,对 Objective-C 方法使用点语法并不是一个好主意。这不是 Ruby。
  • @RudolfAdamkovic 虽然我原则上同意你的观点,但使用括号表示法会不太可读。
  • 除非您在各处进行功能操作,否则最好使用 NSArray 的标准 mapObjectsUsingBlock: 或 valueForKey: 方法(建议在下面的答案中)。避免更多“丑陋”的 Objective-C 语法字符并不能证明添加 Cocoapods 依赖项是合理的。
  • mapObjectsUsingBlock: 不是标准功能,而是另一个答案建议的扩展
  • 答案中从“下划线”链接的页面似乎不再与 Objective-C 相关。
【解决方案5】:

我认为 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"];

【讨论】:

    【解决方案6】:

    我不是 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) 对数组进行手动迭代.

    【讨论】:

      【解决方案7】:
      @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. 检查 nil - 您可以在映射时使用 nil 删除对象
      2. 方法名称更好地反映了该方法不会修改它所调用的数组
      3. 这更像是一种风格,但我已经改进了 IMO 块的参数名称
      4. 计数的点语法

      【讨论】:

        【解决方案8】:

        对于 Objective-C,我会将 ObjectiveSugar 库添加到这个答案列表中:https://github.com/supermarin/ObjectiveSugar

        另外,它的标语是“ObjectiveC 为人类添加。Ruby 风格”。应该很适合 OP ;-)

        我最常见的用例是将服务器调用返回的字典映射到一组更简单的对象,例如从您的 NSDictionary 帖子中获取 NSString ID 的 NSArray:

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

        【讨论】:

          【解决方案9】:

          对于 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"];
          }];
          

          【讨论】:

            【解决方案10】:

            为此有一个特殊的键路径运算符:@unionOfObjects。可能它取代了以前版本的[collect]

            想象一个带有payee 属性的Transaction 类:

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

            Array Operators in Key-Value coding 上的 Apple 文档。

            【讨论】:

              【解决方案11】:

              Swift 引入了一个新的 ma​​p 函数。

              这是example from the documentation

              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 函数采用一个闭包,该闭包返回任何类型的值,并将数组中的现有值映射到这种新类型的实例。

              【讨论】:

              • 您可以使用更简单的示例,例如[1,2,3].map {$0 * 2} //=&gt; [2,4,6]。而且,问题是关于 Obj-C NSArray,而不是 Swift ;)
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-01-02
              • 1970-01-01
              • 2013-03-02
              • 1970-01-01
              • 1970-01-01
              • 2018-01-25
              相关资源
              最近更新 更多