NSD Dictionary з упорядкованими ключами


81

У мене є NSD Dictionary (зберігається в plist), який я в основному використовую як асоціативний масив (рядки як ключі та значення). Я хочу використовувати масив ключів як частину мого додатка, але я хотів би, щоб вони були в певному порядку (насправді це не порядок, за яким я можу написати алгоритм для їх сортування). Я завжди міг зберігати окремий масив ключів, але це здається якось неприємним, тому що мені завжди доводиться оновлювати ключі словника, а також значення масиву, і переконуватися, що вони завжди відповідають. В даний час я просто використовую [myDictionary allKeys], але, очевидно, це повертає їх у довільному, негарантованому порядку. Чи є в Objective-C структура даних, якої я відсутня? Хтось має якісь пропозиції щодо того, як зробити це більш елегантно?

Відповіді:


23

Рішення наявності асоційованого ключа NSMutableArray не так вже й погане. Це дозволяє уникнути підкласифікації NSD Dictionary, і якщо ви будете обережні з написанням доступу, не повинно бути надто складно тримати синхронізацію.


2
Одна з підводних каменів полягає в тому, що словник NS (Mutable) робить копії ключів, тому об’єкти в масиві ключів будуть різними. Це може бути проблематично, якщо ключ мутується.
Квін Тейлор

1
Це хороший момент, але пом’якшений, якщо змінність обмежена додаванням та видаленням об’єкта з масиву, а не зміною елементів у них.
Abizern

1
Не впевнений, як би ви обмежили когось модифікуючи ключ, для якого вони спочатку надавали вказівник. Масив просто зберігає вказівник на цей ключ і не буде знати, змінюється він чи ні. Якщо ключ змінено, можливо, ви навіть не зможете видалити доданий запис із масиву, що може бути справжньою проблемою синхронізації, якщо масив зберігає ключі, яких більше немає у словнику, або навпаки ...: -)
Квін Тейлор

2
До речі, мої вибачення, якщо я звучу надмірно критично. Я сам мав справу з цією ж проблемою, але оскільки це стосується фреймворку, я намагаюся спроектувати його таким, щоб він був надійним і правильним у будь-якій мислимій ситуації. Ви праві, що окремий масив не такий вже й поганий, але це не так просто, як клас, який відстежує його для вас. Плюс, є численні переваги, якщо такий клас розширює словник NS (Mutable), оскільки його можна використовувати в будь-якому місці, де потрібен словник какао.
Квін Тейлор

1
Я не проти дискусії. Моя пропозиція була лише обхідним шляхом. Написання фреймворку - це зовсім інша пропозиція, і це те, що потребує ретельної продуманості. Особисто я вважаю, що змінність незручна саме з цієї причини, і я намагаюся мінімізувати використання цих класів у власних програмах.
Abizern

19

Я запізнююсь із грою з фактичною відповіддю, але вам може бути цікаво дослідити CHOrderedDictionary . Це підклас NSMutableDictionary, який інкапсулює іншу структуру для підтримки впорядкування ключів. (Це частина CHDataStructures.framework .) Я вважаю, що це зручніше, ніж управління словником та масивом окремо.

Розкриття інформації: Це відкритий код, який я написав. Просто сподіваючись, що це може бути корисним для інших, хто стикається з цією проблемою.


1
+1 - ще не пробував, але переглянув документи, і здається, ви зробили тут гарну роботу. Я думаю, що трохи соромно, що Apple відсутня так багато з цих основ.
Whitneyland

Я не думаю, що це бентежить, це лише питання про те, як вони хочуть, щоб їх структура розвивалася. Зокрема, нові класи повинні додати багато цінності, щоб врахувати їх для включення до Фонду. Не так складно поєднати словник із відсортованим масивом ключів, і Apple намагається утримати розмір (і ремонтопридатність) фреймворку, не переборщуючи з речами, якими можуть користуватися деякі люди.
Quinn Taylor

3
Справа не тільки в тому, що бракує впорядкованого словника, в тому, що бракує стільки корисних колекцій CS101. Я не вірю, що Objective-C був би широко успішною мовою без Apple, на відміну від C, C ++, Java, C #, які довели свою успішність багато разів поза сферою діяльності їхніх творців.
Whitneyland

1
Це. Є. Приголомшливо Ганьба, я дотепер не знайшов цього фреймворку. Це буде опорою в більшості майбутніх проектів. Дуже дякую!
Dev Kanchen

якщо ключем є NSString, чи буде проблемою зробити наступне? створити словник, а також масив і мати загальний метод додавання та видалення даних. Масив містить список ключів NSString (у бажаному порядку) Словник містить ключі NSString та значення
user1046037

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

Якщо ви збираєтеся підклас NSD Dictionary, вам потрібно реалізувати ці методи як мінімум:

  • NSD Dictionary
    • -count
    • -objectForKey:
    • -keyEnumerator
  • NSMutableDictionary
    • -removeObjectForKey:
    • -setObject:forKey:
  • NSCopying / NSMutableCopying
    • -copyWithZone:
    • -mutableCopyWithZone:
  • Кодування NSC
    • -encodeWithCoder:
    • -initWithCoder:
  • NSFastEnumeration (для Leopard)
    • -countByEnumeratingWithState:objects:count:

Найпростіший спосіб зробити те, що ви хочете, - це створити підклас NSMutableDictionary, який містить власний NSMutableDictionary, яким він маніпулює, та NSMutableArray для зберігання упорядкованого набору ключів.

Якщо ви ніколи не збираєтесь кодувати свої об'єкти, ви можете пропустити впровадження -encodeWithCoder:та-initWithCoder:

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


5
Є ще один, який є неочевидним і спонукав мене - якщо ви реалізуєте NSCoding, також перевизначте - (Class) classForKeyedArchiver для повернення [self class]. Кластери класів встановлюють це так, щоб завжди повертати абстрактний батьківський клас, тому, якщо ви цього не зміните, екземпляри завжди будуть декодуватися як словник NS (Mutable).
Quinn Taylor

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 міститиме відповідні ключі у правильному порядку. Зверніть увагу, що це рішення не вимагає обов'язкового існування всіх ключів, що є звичайною ситуацією, з якою ми маємо справу ...


А як щодо sortedArrayUsingSelector для порядку масиву? developer.apple.com/library/ios/documentation/cocoa/Conceptual/…
Алекс Заватоне

2

Для, Свіфт 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

Я не дуже люблю С ++, але одне із рішень, яке я бачу все більше і більше, це використання Objective-C ++ та std::mapзі стандартної бібліотеки шаблонів. Це словник, ключі якого автоматично сортуються при вставці. Це напрочуд добре працює як із скалярними типами, так і з об’єктами Objective-C як як ключі, так і як значення.

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

Одне застереження полягає в тому, що ви можете надати власну insert_or_assignфункцію, якщо тільки ви не можете використовувати C ++ 17 (див. Цю відповідь ). Крім того, вам потрібні typedefваші типи, щоб запобігти певним помилкам збірки. Як тільки ви зрозумієте, як використовувати std::map, ітератори тощо, це досить просто і швидко.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.