【问题标题】:NSDictionary with ordered keys带有有序键的 NSDictionary
【发布时间】:2010-09-27 10:14:27
【问题描述】:

我有一个 NSDictionary(存储在 plist 中),基本上用作关联数组(字符串作为键和值)。我想将键数组用作我的应用程序的一部分,但我希望它们按特定顺序排列(不是我可以编写算法将它们排序的顺序)。我总是可以存储一个单独的键数组,但这似乎有点笨拙,因为我总是必须更新字典的键以及数组的值,并确保它们始终对应。目前我只使用 [myDictionary allKeys],但显然这会以任意、无保证的顺序返回它们。 Objective-C 中是否有我遗漏的数据结构?有人对如何更优雅地做到这一点有任何建议吗?

【问题讨论】:

    标签: ios objective-c cocoa nsdictionary


    【解决方案1】:

    拥有关联的 NSMutableArray 键的解决方案还不错。它避免了继承 NSDictionary,如果你在编写访问器时小心翼翼,保持同步应该不会太难。

    【讨论】:

    • 一个陷阱是 NS(Mutable)Dictionary 会复制键,因此键数组中的对象会有所不同。如果密钥发生突变,这可能会出现问题。
    • 这是一个很好的观点,但如果可变性仅限于从数组中添加和删除对象而不是更改其中的项目,则可以减轻这种影响。
    • 不确定如何限制某人修改他们首先为其提供指针的键。该数组仅存储指向该键的指针,并且不知道它是否更改。如果更改了键,您甚至可能根本无法从数组中删除添加的条目,如果您的数组保留不再在字典中的键,这可能是一个真正的同步问题,反之亦然......: -)
    • 顺便说一句,如果我听起来过于挑剔,我很抱歉。我自己也一直在处理同样的问题,但由于它是针对一个框架的,所以我试图将它设计为在每一种可能的情况下都是健壮和正确的。你是对的,一个单独的数组并不是 so 不好,但它不像为你跟踪它的类那么简单。另外,如果这样的类扩展了 NS(Mutable)Dictionary,还有很多好处,因为它可以在任何需要 Cocoa 字典的地方使用。
    • 我不介意讨论。我的建议只是一种解决方法。编写框架完全是一个不同的命题,需要仔细考虑。就我个人而言,正是因为这个原因,我发现可变性很麻烦,我尽量在我自己的程序中尽量减少这些类的使用。
    【解决方案2】:

    我迟到了,但您可能有兴趣调查CHOrderedDictionary。它是 NSMutableDictionary 的子类,它封装了另一种用于维护键顺序的结构。 (它是CHDataStructures.framework 的一部分。)我发现它比单独管理字典和数组更方便。

    披露:这是我编写的开源代码。只是希望它对面临这个问题的其他人有用。

    【讨论】:

    • +1 - 还没有尝试过,但是查看了文档,看来您在这里做了一些不错的工作。我认为 Apple 缺少这么多这些基础知识,这有点令人尴尬。
    • 我觉得并不尴尬,只是他们希望自己的框架如何发展的问题。具体来说,新课程必须增加很多价值才能考虑包含在 Foundation 中。将字典与排序的键数组配对并不复杂,Apple 试图通过不过度使用某些人可能使用的东西来包含框架的大小(和可维护性)。
    • 不只是缺少有序字典,还缺少很多有用的 CS101 集合。我不相信没有 Apple,Objective-C 不会成为一种广泛成功的语言,与 C、C++、Java、C# 不同,C、C++、Java、C# 已经在其创造者的范围之外多次证明是成功的。
    • 这个。是。惊人的。很遗憾我直到现在才发现这个框架。这将成为未来大多数项目的支柱。非常感谢!
    • 如果 key 是 NSString,那么做以下事情会不会有问题?创建一个字典和一个数组,并有一个通用的方法来添加和删除数据。数组包含 NSString 键列表(按所需顺序) 字典包含 NSString 键和值
    【解决方案3】:

    没有这样的内置方法可以从中获取。但是一个简单的逻辑对你有用。准备字典时,您可以简单地在每个键前添加一些数字文本。喜欢

    NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
                           @"01.Created",@"cre",
                           @"02.Being Assigned",@"bea",
                           @"03.Rejected",@"rej",
                           @"04.Assigned",@"ass",
                           @"05.Scheduled",@"sch",
                           @"06.En Route",@"inr",
                           @"07.On Job Site",@"ojs",
                           @"08.In Progress",@"inp",
                           @"09.On Hold",@"onh",
                           @"10.Completed",@"com",
                           @"11.Closed",@"clo",
                           @"12.Cancelled", @"can",
                           nil]; 
    

    现在,如果您可以使用sortingArrayUsingSelector,同时按照您放置的顺序获取所有键。

    NSArray *arr =  [[dict allKeys] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
    

    在UIView中要显示key的地方,把前面的3个字符剪掉即可。

    【讨论】:

    【解决方案4】:

    如果你要继承 NSDictionary,你至少需要实现这些方法:

    • NS 字典
      • -count
      • -objectForKey:
      • -keyEnumerator
    • NSMutable 字典
      • -removeObjectForKey:
      • -setObject:forKey:
    • NSCopying/NSMutableCopying
      • -copyWithZone:
      • -mutableCopyWithZone:
    • NS 编码
      • -encodeWithCoder:
      • -initWithCoder:
    • NSFastEnumeration (对于 Leopard)
      • -countByEnumeratingWithState:objects:count:

    做你想做的最简单的方法是创建一个 NSMutableDictionary 的子类,它包含它自己的 NSMutableDictionary 和一个 NSMutableArray 来存储一组有序的键。

    如果你永远不会对你的对象进行编码,你可以想象跳过实现 -encodeWithCoder:-initWithCoder:

    上述 10 种方法中的所有方法实现都将直接通过托管字典或有序键数组。

    【讨论】:

    • 还有一个不明显的东西让我很吃惊——如果你实现了 NSCoding,还要重写 -(Class)classForKeyedArchiver 以返回 [self class]。类集群将此设置为始终返回抽象父类,因此除非您更改它,否则实例将始终被解码为 NS(Mutable)Dictionary。
    【解决方案5】:

    我的小补充:按数字键排序(对较小的代码使用速记符号)

    // the resorted result array
    NSMutableArray *result = [NSMutableArray new];
    // the source dictionary - keys may be Ux timestamps (as integer, wrapped in NSNumber)
    NSDictionary *dict =
    @{
      @0: @"a",
      @3: @"d",
      @1: @"b",
      @2: @"c"
    };
    
    {// do the sorting to result
        NSArray *arr = [[dict allKeys] sortedArrayUsingSelector:@selector(compare:)];
    
        for (NSNumber *n in arr)
            [result addObject:dict[n]];
    }
    

    【讨论】:

      【解决方案6】:

      又快又脏:

      当您需要订购词典(此处称为“myDict”)时,请执行以下操作:

           NSArray *ordering = [NSArray arrayWithObjects: @"Thing",@"OtherThing",@"Last Thing",nil];
      

      然后,当您需要订购字典时,创建一个索引:

          NSEnumerator *sectEnum = [ordering objectEnumerator];
          NSMutableArray *index = [[NSMutableArray alloc] init];
              id sKey;
              while((sKey = [sectEnum nextObject])) {
                  if ([myDict objectForKey:sKey] != nil ) {
                      [index addObject:sKey];
                  }
              }
      

      现在,*index 对象将以正确的顺序包含适当的键。请注意,此解决方案并不要求所有密钥都必须存在,这是我们正在处理的常见情况...

      【讨论】:

      【解决方案7】:

      NSDictionary 的有序子类的最小实现(基于https://github.com/nicklockwood/OrderedDictionary)。随意扩展您的需求:

      斯威夫特 3 和 4

      class MutableOrderedDictionary: NSDictionary {
          let _values: NSMutableArray = []
          let _keys: NSMutableOrderedSet = []
      
          override var count: Int {
              return _keys.count
          }
          override func keyEnumerator() -> NSEnumerator {
              return _keys.objectEnumerator()
          }
          override func object(forKey aKey: Any) -> Any? {
              let index = _keys.index(of: aKey)
              if index != NSNotFound {
                  return _values[index]
              }
              return nil
          }
          func setObject(_ anObject: Any, forKey aKey: String) {
              let index = _keys.index(of: aKey)
              if index != NSNotFound {
                  _values[index] = anObject
              } else {
                  _keys.add(aKey)
                  _values.add(anObject)
              }
          }
      }
      

      用法

      let normalDic = ["hello": "world", "foo": "bar"]
      // initializing empty ordered dictionary
      let orderedDic = MutableOrderedDictionary()
      // copying normalDic in orderedDic after a sort
      normalDic.sorted { $0.0.compare($1.0) == .orderedAscending }
               .forEach { orderedDic.setObject($0.value, forKey: $0.key) }
      // from now, looping on orderedDic will be done in the alphabetical order of the keys
      orderedDic.forEach { print($0) }
      

      目标-C

      @interface MutableOrderedDictionary<__covariant KeyType, __covariant ObjectType> : NSDictionary<KeyType, ObjectType>
      @end
      @implementation MutableOrderedDictionary
      {
          @protected
          NSMutableArray *_values;
          NSMutableOrderedSet *_keys;
      }
      
      - (instancetype)init
      {
          if ((self = [super init]))
          {
              _values = NSMutableArray.new;
              _keys = NSMutableOrderedSet.new;
          }
          return self;
      }
      
      - (NSUInteger)count
      {
          return _keys.count;
      }
      
      - (NSEnumerator *)keyEnumerator
      {
          return _keys.objectEnumerator;
      }
      
      - (id)objectForKey:(id)key
      {
          NSUInteger index = [_keys indexOfObject:key];
          if (index != NSNotFound)
          {
              return _values[index];
          }
          return nil;
      }
      
      - (void)setObject:(id)object forKey:(id)key
      {
          NSUInteger index = [_keys indexOfObject:key];
          if (index != NSNotFound)
          {
              _values[index] = object;
          }
          else
          {
              [_keys addObject:key];
              [_values addObject:object];
          }
      }
      @end
      

      用法

      NSDictionary *normalDic = @{@"hello": @"world", @"foo": @"bar"};
      // initializing empty ordered dictionary
      MutableOrderedDictionary *orderedDic = MutableOrderedDictionary.new;
      // copying normalDic in orderedDic after a sort
      for (id key in [normalDic.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
          [orderedDic setObject:normalDic[key] forKey:key];
      }
      // from now, looping on orderedDic will be done in the alphabetical order of the keys
      for (id key in orderedDic) {
          NSLog(@"%@:%@", key, orderedDic[key]);
      }
      

      【讨论】:

        【解决方案8】:

        对于 Swift 3。 请尝试以下方法

                //Sample Dictionary
                let dict: [String: String] = ["01.One": "One",
                                              "02.Two": "Two",
                                              "03.Three": "Three",
                                              "04.Four": "Four",
                                              "05.Five": "Five",
                                              "06.Six": "Six",
                                              "07.Seven": "Seven",
                                              "08.Eight": "Eight",
                                              "09.Nine": "Nine",
                                              "10.Ten": "Ten"
                                             ]
        
                //Print the all keys of dictionary
                print(dict.keys)
        
                //Sort the dictionary keys array in ascending order
                let sortedKeys = dict.keys.sorted { $0.localizedCaseInsensitiveCompare($1) == ComparisonResult.orderedAscending }
        
                //Print the ordered dictionary keys
                print(sortedKeys)
        
                //Get the first ordered key
                var firstSortedKeyOfDictionary = sortedKeys[0]
        
                // Get range of all characters past the first 3.
                let c = firstSortedKeyOfDictionary.characters
                let range = c.index(c.startIndex, offsetBy: 3)..<c.endIndex
        
                // Get the dictionary key by removing first 3 chars
                let firstKey = firstSortedKeyOfDictionary[range]
        
                //Print the first key
                print(firstKey)
        

        【讨论】:

          【解决方案9】:

          我不太喜欢 C++,但我发现自己越来越多地使用的一种解决方案是使用标准模板库中的 Objective-C++ 和 std::map。它是一个字典,其键在插入时自动排序。无论是作为键还是作为值,标量类型或 Objective-C 对象都可以很好地工作。

          如果您需要包含一个数组作为值,只需使用std::vector 而不是NSArray

          需要注意的是,您可能需要提供自己的 insert_or_assign 函数,除非您可以使用 C++17(请参阅 this answer)。此外,您需要 typedef 您的类型以防止某些构建错误。一旦你弄清楚如何使用std::map、迭代器等,它就非常简单快速。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多