NSDictionary с упорядоченными ключами

Мне интересно, если это ситуация, в которой оказались другие люди. У меня есть NSDictionary (хранится в plist), который я использую в основном как ассоциативный массив (строки как ключи и значения). Я хочу использовать массив ключей как часть моего приложения, но я бы хотел, чтобы они были в определенном порядке (на самом деле не такой порядок, чтобы я мог написать алгоритм для их сортировки). Я всегда мог хранить отдельный массив ключей, но это похоже на kludgey, потому что мне всегда нужно обновлять ключи словаря, а также значения массива и следить за тем, чтобы они всегда соответствовали друг другу. В настоящее время я просто использую [myDictionary allKeys], но, очевидно, это возвращает их в произвольном, не гарантированном порядке. Есть ли структура данных в Objective-C, которую я пропускаю? У кого-нибудь есть предложения по тому, как более элегантно это сделать?

Ответ 1

Решение о наличии связанного ключа NSMutableArray не так уж плохо. Он избегает подклассификации NSDictionary, и если вы внимательно относитесь к написанию аксессуаров, не должно быть слишком сложно сохранить синхронизацию.

Ответ 2

Я опаздываю на игру с реальным ответом, но вам может быть интересно исследовать CHOrderedDictionary. Это подкласс NSMutableDictionary, который инкапсулирует другую структуру для поддержания порядка ключей. (Это часть CHDataStructures.framework.) Я считаю, что это более удобно, чем управление словарем и массивом по отдельности.

Раскрытие: Это открытый код, который я написал. Просто надеюсь, что это может быть полезно для других, сталкивающихся с этой проблемой.

Ответ 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, просто отрубите передний символ 3.

Ответ 4

Если вы собираетесь подклассифицировать NSDictionary, вам необходимо реализовать эти методы как минимум:

  • NSDictionary
    • -count
    • -objectForKey:
    • -keyEnumerator
  • NSMutableDictionary
    • -removeObjectForKey:
    • -setObject:forKey:
  • NSCopying/NSMutableCopying
    • -copyWithZone:
    • -mutableCopyWithZone:
  • NSCoding
    • -encodeWithCoder:
    • -initWithCoder:
  • NSFastEnumeration (для Leopard)
    • -countByEnumeratingWithState:objects:count:

Самый простой способ сделать то, что вы хотите, - сделать подкласс NSMutableDictionary, который содержит свой собственный NSMutableDictionary, который он управляет, и NSMutableArray для хранения упорядоченного набора ключей.

Если вы никогда не собираетесь кодировать свои объекты, вы могли бы пропустить реализацию -encodeWithCoder: и -initWithCoder:

Все реализации вашего метода в 10 описанных выше методах будут либо проходить непосредственно через ваш размещенный словарь, либо ваш упорядоченный массив ключей.

Ответ 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

Для, 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)

Ответ 8

Минимальная реализация упорядоченного подкласса NSDictionary (на основе https://github.com/nicklockwood/OrderedDictionary). Не стесняйтесь распространяться на ваши нужды:

Swift 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) }

Objective-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]);
}

Ответ 9

Я не очень люблю С++, но одно решение, которое я вижу, я использую все больше и больше, чтобы использовать Objective-C ++ и std::map в стандартной библиотеке шаблонов. Это словарь, ключи которого автоматически сортируются при вставке. Он работает на удивление хорошо с использованием либо скалярных типов, либо Objective-C объектов как ключей, так и значений.

Если вам нужно включить массив в качестве значения, просто используйте std::vector вместо NSArray.

Одно из предостережений состоит в том, что вы можете предоставить свою собственную функцию insert_or_assign, если вы не можете использовать С++ 17 (см. этот ответ). Кроме того, вам нужно typedef ваши типы, чтобы предотвратить определенные ошибки сборки. Когда вы выясните, как использовать std::map, итераторы и т.д., Это довольно просто и быстро.