NSDictionary,有序密钥


81

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

Answers:


23

具有关联的NSMutableArray键的解决方案还不错。它避免了对NSDictionary的子类化,并且如果您在编写访问器时很谨慎,那么保持同步就不难了。


2
一个陷阱是NS(Mutable)Dictionary复制密钥,因此密钥数组中的对象将不同。如果键被突变,这可能是有问题的。
奎因·泰勒,

1
这是一个好点,但是如果可变性仅限于在数组中添加和删除对象而不是更改其中的项,则可以缓解。
阿比森

1
不知道您将如何限制某人修改首先为其提供指针的键。该数组仅存储指向该键的指针,并且不知道它是否更改。如果更改了键,则您甚至可能根本无法从数组中删除添加的条目,如果您的数组保留了字典中不再存在的键,那么这可能是一个真正的同步问题,反之亦然...: -)
Quinn Taylor

2
顺便说一句,如果我听起来过于批评,我深表歉意。我本人一直在处理相同的问题,但是由于它是针对框架的,因此我试图将其设计为在每种可能的情况下均可靠且正确。你是正确的,一个单独的数组是不是那么糟糕,但它并不是这么简单,一类曲目给你。另外,如果此类扩展了NS(Mutable)Dictionary,则会有很多好处,因为它可以在需要Cocoa词典的任何地方使用。
奎因·泰勒

1
我不在乎讨论。我的建议只是一种解决方法。编写框架完全是另一回事,这是需要仔细考虑的事情。就我个人而言,出于这个原因,我发现可变性很麻烦,因此,我尝试在自己的程序中尽量减少使用这些类。
阿比森

19

我来不及回答实际问题,但是您可能有兴趣研究CHOrderedDictionary。它是NSMutableDictionary的子类,它封装了另一个用于维护键顺序的结构。(它是CHDataStructures.framework的一部分。)我发现它比分别管理字典和数组更方便。

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


1
+1-尚未尝试过,但仔细查看了文档,看来您在这里做了一些不错的工作。我认为苹果缺少这么多基础知识有点尴尬。
whitneyland 2011年

我认为这并不令人尴尬,这只是他们希望自己的框架如何发展的问题。具体来说,新类必须增加很多价值,才能考虑纳入Foundation。将字典与排序的键数组配对并不复杂,Apple尝试通过不过度使用某些人可能使用的东西来控制框架的大小(和可维护性)。
奎因·泰勒

3
不仅仅是缺少有序词典,还缺少许多有用的CS101集合。我不认为没有Apple而不是C,C ++,Java和C#,Objective-C不会是一种广泛成功的语言,而C,C ++,Java和C#在其创造者的权限范围之外已被证明多次成功。
whitneyland 2011年

1
这个。是的。太棒了 可惜我直到现在才发现这个框架。这将是未来大多数项目的支柱。非常感谢!
Dev Kanchen

如果密钥是NSString,则执行以下操作是否会成为问题?创建字典以及数组,并具有添加和删除数据的通用方法。数组包含NSString键的列表(按所需顺序)字典包含NSString键和值
user1046037,2012年

15

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

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中显示键的地方,只需将前3个字符切掉即可。


这说明如何删除前三个字母:stackoverflow.com/questions/1073872/...
莱昂

7

如果要对NSDictionary进行子类化,则需要至少实现以下方法:

  • NSDictionary
    • -count
    • -objectForKey:
    • -keyEnumerator
  • NSMutableDictionary
    • -removeObjectForKey:
    • -setObject:forKey:
  • NSCopying / NSMutableCopying
    • -copyWithZone:
    • -mutableCopyWithZone:
  • NS编码
    • -encodeWithCoder:
    • -initWithCoder:
  • NSFastEnumeration (适用于Leopard)
    • -countByEnumeratingWithState:objects:count:

做您想要的事的最简单方法是制作一个NSMutableDictionary的子类,该子类包含它自己的NSMutableDictionary(它可以操作)和一个NSMutableArray来存储一组有序的键。

如果您永远不会编码对象,则可以想象跳过实现-encodeWithCoder:-initWithCoder:

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


5
还有另外一个不明显的地方让我绊倒了—如果实现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]];
}

3

快速肮脏:

当您需要订购字典(此处称为“ 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对象将包含正确顺序的适当键。请注意,此解决方案不需要所有键都必须存在,这是我们正在处理的通常情况...



2

对于,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)

2

最小实现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]);
}

0

我不太喜欢C ++,但是我发现自己越来越多地使用的一种解决方案是使用Objective-C ++和std::mapStandard Template Library。这是一本字典,其键在插入时会自动排序。无论是作为键还是作为值的标量类型或Objective-C对象,它都能很好地工作。

如果您需要包含一个数组作为值,则使用std::vector代替NSArray

一个警告是您可能想要提供自己的insert_or_assign功能,除非可以使用C ++ 17(请参阅此答案)。另外,您需要对typedef类型进行预防,以防止某些构建错误。一旦弄清楚如何使用std::map,迭代器等,它就非常简单快捷。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.