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

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


person Andy Bourassa    schedule 17.12.2008    source источник


Ответы (9)


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

person Abizern    schedule 17.12.2008
comment
Одна из ловушек заключается в том, что NS (Mutable) Dictionary делает копии ключей, поэтому объекты в массиве ключей будут другими. Это может быть проблематично, если ключ изменен. - person Quinn Taylor; 20.06.2009
comment
Это хороший момент, но смягчается, если изменчивость ограничивается добавлением и удалением объекта из массива, а не изменением элементов в них. - person Abizern; 20.06.2009
comment
Не уверен, как можно ограничить кого-либо изменять ключ, для которого они изначально предоставили указатель. Массив просто хранит указатель на этот ключ и не узнает, изменится он или нет. Если ключ изменен, возможно, вы вообще не сможете удалить добавленную запись из массива, что может быть реальной проблемой синхронизации, если в массиве хранятся ключи, которых больше нет в словаре, или наоборот ...: -) - person Quinn Taylor; 20.06.2009
comment
Кстати, приношу свои извинения, если я говорю слишком критично. Я сам имел дело с той же проблемой, но поскольку это для фреймворка, я пытаюсь спроектировать его так, чтобы он был надежным и правильным во всех мыслимых ситуациях. Вы правы, что отдельный массив - это не так плохо, но это не так просто, как класс, который отслеживает его за вас. Кроме того, есть множество преимуществ, если такой класс расширяет NS (Mutable) Dictionary, поскольку его можно использовать везде, где требуется словарь Какао. - person Quinn Taylor; 20.06.2009
comment
Я не против обсуждения. Мое предложение было обходным решением. Написание фреймворка - это совершенно другое предложение, и это требует тщательного обдумывания. Лично я считаю, что изменчивость проблематична именно по этой причине, и я стараюсь свести к минимуму использование этих классов в моих собственных программах. - person Abizern; 21.06.2009
comment
@Quinn Taylor: Поздно на вечеринке, но не могли бы вы избежать этой проблемы, если бы ваш изменяемый массив для ключей также скопировал ключевой объект перед его вставкой, как это делает словарь? - person Ben Zotto; 17.10.2010
comment
Не совсем, потому что тогда у вас будет две разные копии (разные указатели) в словаре и массиве. Вы хотите сохранить один и тот же указатель в обеих коллекциях, чтобы вы могли точно находить и / или удалять объекты, которые были добавлены и которые могли быть впоследствии изменены. В противном случае из словаря может быть удален объект, но не массив, или наоборот. - person Quinn Taylor; 18.10.2010
comment
@ Куинн Тейлор: Вы не можете изменить ключевой объект в словаре - поэтому он сначала копирует ключ; после установки вы больше не владеете им. Сравнение на предмет наличия в словаре не основано на сравнении указателя. Следовательно, вы должны иметь возможность скопировать ключ в свой словарь и построить на его основе правильную семантику. - person Ben Zotto; 02.11.2010
comment
скопируйте ключ в свой массив ... - person Ben Zotto; 02.11.2010

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

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

person Quinn Taylor    schedule 18.10.2010
comment
+1 - Еще не пробовал, но просмотрел документацию и, кажется, вы хорошо поработали. Я думаю, что это немного смущает, что Apple упускает из виду многие из этих основ. - person whitneyland; 24.10.2011
comment
Я не думаю, что это смущает, это просто вопрос того, как они хотят, чтобы их фреймворк развивался. В частности, новые классы должны добавлять большую ценность, чтобы их можно было включить в Foundation. Совместить словарь с отсортированным массивом ключей не так уж и сложно, и Apple пытается ограничить размер (и ремонтопригодность) фреймворка, не переусердствуя с вещами, которые могут использовать некоторые люди. - person Quinn Taylor; 25.10.2011
comment
Дело не только в том, что отсутствует упорядоченный словарь, но и в том, что отсутствует так много полезных коллекций CS101. Я не верю, что Objective-C был бы широко успешным языком без Apple, в отличие от C, C ++, Java, C #, которые много раз оказывались успешными за пределами компетенции их создателей. - person whitneyland; 25.10.2011
comment
Этот. Является. Потрясающий. Жаль, что я не обнаружил эту структуру до сих пор. Это будет опорой в большинстве будущих проектов. Большое спасибо! - person Dev Kanchen; 15.02.2012
comment
если ключом является NSString, будет ли проблема сделать следующее? создать словарь, а также массив и иметь общий метод добавления и удаления данных. Массив содержит список ключей NSString (в желаемом порядке) Словарь содержит ключи NSString и значения - person user1046037; 23.05.2012
comment
Спасибо за обновление ссылок в моем ответе, @Dolbz! Отлично сделано. - person Quinn Taylor; 24.09.2018
comment
И спасибо за библиотеку @QuinnTaylor. Она делает то, что я делал, намного чище ???? - person Dolbz; 26.09.2018

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

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 символа.

person Rajan Twanabashu    schedule 22.02.2013
comment
Здесь показано, как удалить первые три символа: stackoverflow.com/questions/1073872/ - person Leon; 06.06.2015

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

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

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

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

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

person Ashley Clark    schedule 18.12.2008
comment
Есть еще одно неочевидное и сбившее меня с толку - если вы реализуете NSCoding, также переопределите - (Class) classForKeyedArchiver для возврата [self class]. Кластеры классов устанавливают это так, чтобы всегда возвращать абстрактный родительский класс, поэтому, если вы не измените это, экземпляры всегда будут декодироваться как NS (Mutable) Dictionary. - person Quinn Taylor; 20.06.2009

Мое небольшое дополнение: сортировка по числовому ключу (с использованием сокращенных обозначений для меньшего кода)

// 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]];
}
person BananaAcid    schedule 20.03.2014

Быстро и грязно:

Если вам нужно заказать словарь (называемый здесь «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];
            }
        }

Теперь объект индекса * будет содержать соответствующие ключи в правильном порядке. Обратите внимание, что это решение не требует, чтобы все ключи обязательно существовали, что является обычной ситуацией, с которой мы имеем дело ...

person Adam Prall    schedule 13.08.2009
comment
А как насчет sortedArrayUsingSelector для порядка массива? developer.apple.com/library/ios/ документация / какао / концептуальный / - person Alex Zavatone; 26.03.2014

Минимальная реализация упорядоченного подкласса 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) }

Цель-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]);
}
person Cœur    schedule 06.09.2017

Для 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)
person Dinesh    schedule 19.10.2016

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

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

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

person Martin Winter    schedule 25.08.2017