NSDictionary z uporządkowanymi kluczami

81

Mam NSDictionary (przechowywany w plistie), którego zasadniczo używam jako tablicy asocjacyjnej (ciągi znaków jako klucze i wartości). Chcę użyć tablicy kluczy jako części mojej aplikacji, ale chciałbym, aby były w określonej kolejności (nie w takiej kolejności, w jakiej mogę napisać algorytm, aby je posortować). Zawsze mógłbym przechowywać oddzielną tablicę kluczy, ale wydaje się to trochę niezdarne, ponieważ zawsze musiałem aktualizować klucze słownika, a także wartości tablicy i upewnić się, że zawsze odpowiadają. Obecnie po prostu używam [myDictionary allKeys], ale oczywiście zwraca to je w dowolnej, niegwarantowanej kolejności. Czy brakuje mi struktury danych w Objective-C? Czy ktoś ma jakieś sugestie, jak to zrobić bardziej elegancko?

Andy Bourassa
źródło

Odpowiedzi:

23

Rozwiązanie polegające na powiązaniu NSMutableArray kluczy nie jest takie złe. Unika tworzenia podklas NSDictionary, a jeśli zachowujesz ostrożność podczas pisania akcesorów, nie powinno być zbyt trudno utrzymać synchronizację.

Abizern
źródło
2
Jedną z pułapek jest to, że słownik NS (Mutable) tworzy kopie kluczy, więc obiekty w tablicy kluczy będą różne. Może to być problematyczne, jeśli klucz jest zmutowany.
Quinn Taylor
1
To dobra uwaga, ale złagodzona, jeśli zmienność jest ograniczona do dodawania i usuwania obiektu z tablicy zamiast zmiany elementów w nich.
Abizern
1
Nie jestem pewien, jak ograniczyć komuś modyfikację klucza, dla którego dostarczył wskaźnik w pierwszej kolejności. Tablica po prostu przechowuje wskaźnik do tego klucza i nie będzie wiedzieć, czy się zmieni, czy nie. Jeśli klucz zostanie zmieniony, możesz w ogóle nie być w stanie usunąć dodanego wpisu z tablicy, co może być prawdziwym problemem z synchronizacją, jeśli macierz przechowuje klucze, których już nie ma w słowniku lub odwrotnie ...: -)
Quinn Taylor
2
Przy okazji, przepraszam, jeśli brzmię zbyt krytycznie. Sam miałem do czynienia z tym samym problemem, ale ponieważ dotyczy on frameworka, staram się zaprojektować go tak, aby był solidny i poprawny w każdej możliwej sytuacji. Masz rację, że oddzielna tablica nie jest taka zła, ale nie jest tak prosta, jak klasa, która ją śledzi. Dodatkowo istnieje wiele korzyści, jeśli taka klasa rozszerza słownik NS (Mutable), ponieważ może być używana wszędzie tam, gdzie wymagany jest słownik Cocoa.
Quinn Taylor
1
Nie mam nic przeciwko dyskusji. Moja sugestia była tylko obejściem. Pisanie frameworka to zupełnie inna propozycja i jest to coś, co wymaga dokładnego przemyślenia. Osobiście uważam, że zmienność jest kłopotliwa właśnie z tego powodu i staram się zminimalizować użycie tych klas w moich własnych programach.
Abizern
19

Spóźniłem się na grę z rzeczywistą odpowiedzią, ale możesz być zainteresowany zbadaniem CHOrderedDictionary . Jest to podklasa NSMutableDictionary, która zawiera inną strukturę do utrzymywania kolejności kluczy. (Jest częścią CHDataStructures.framework .) Uważam, że jest to wygodniejsze niż osobne zarządzanie słownikiem i tablicą.

Ujawnienie: to jest kod open source, który napisałem. Mam tylko nadzieję, że może to być przydatne dla innych osób borykających się z tym problemem.

Quinn Taylor
źródło
1
+1 - Jeszcze tego nie próbowałem, ale przejrzałem dokumenty i wygląda na to, że wykonałeś tutaj niezłą robotę. Myślę, że to trochę żenujące, że Apple brakuje tak wielu z tych podstaw.
whitneyland
Nie wydaje mi się, żeby to było żenujące, to tylko kwestia tego, jak chcą, aby rozwijały się ich ramy. W szczególności nowe klasy muszą wnosić wiele wartości, aby można je było uwzględnić w programie Foundation. Sparowanie słownika z posortowaną tablicą kluczy nie jest skomplikowane, a Apple stara się ograniczyć rozmiar (i łatwość utrzymania) struktury, nie przesadzając z rzeczami, z których niektórzy mogą korzystać.
Quinn Taylor
3
Nie chodzi tylko o to, że brakuje uporządkowanego słownika, ale o to, że brakuje tak wielu przydatnych kolekcji CS101. Nie wierzę, że Objective-C byłby szeroko rozpowszechnionym językiem bez Apple, w przeciwieństwie do C, C ++, Java, C #, które wielokrotnie sprawdzały się poza zakresem kompetencji ich twórców.
whitneyland
1
To. Jest. Niesamowite. Szkoda, że ​​do tej pory nie odkryłem tego szkieletu. Będzie to podstawa większości przyszłych projektów. Wielkie dzięki!
Dev Kanchen,
jeśli klucz to NSString, czy wykonanie następujących czynności byłoby problemem? utworzyć słownik oraz tablicę i mieć wspólną metodę dodawania i usuwania danych. Tablica zawiera listę kluczy NSString (w żądanej kolejności) Słownik zawiera klucze NSString i wartości
user1046037
15

Nie ma takiej wbudowanej metody, z której można to uzyskać. Ale wystarczy prosta logika. Podczas przygotowywania słownika możesz po prostu dodać kilka znaków numerycznych przed każdym klawiszem. Lubić

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]; 

Teraz, jeśli możesz użyć sortingArrayUsingSelector podczas pobierania wszystkich kluczy w tej samej kolejności, w jakiej umieścisz.

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

W miejscu, w którym chcesz wyświetlić klawisze w UIView, po prostu odetnij 3 przednie znaki.

Rajan Twanabashu
źródło
To pokazuje, jak usunąć pierwsze trzy znaki: stackoverflow.com/questions/1073872/ ...
Leon,
7

Jeśli zamierzasz podklasę NSDictionary, musisz zaimplementować co najmniej następujące metody:

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

Najłatwiejszym sposobem zrobienia tego, co chcesz, jest utworzenie podklasy NSMutableDictionary, która zawiera własny NSMutableDictionary, który manipuluje, oraz NSMutableArray do przechowywania uporządkowanego zestawu kluczy.

Jeśli nigdy nie zamierzasz kodować swoich obiektów, możesz sobie wyobrazić pominięcie implementacji -encodeWithCoder:i-initWithCoder:

Wszystkie implementacje metod w powyższych 10 metodach przeszłyby bezpośrednio przez Twój hostowany słownik lub uporządkowaną tablicę kluczy.

Ashley Clark
źródło
5
Jest jeszcze jeden, który nie jest oczywisty i zaskoczył mnie - jeśli zaimplementujesz NSCoding, nadpisz również - (Class) classForKeyedArchiver, aby zwrócić [własną klasę]. Klastry klas ustawiają tę opcję tak, aby zawsze zwracała abstrakcyjną klasę nadrzędną, więc jeśli to nie zmienisz, instancje będą zawsze dekodowane jako słownik NS (mutowalny).
Quinn Taylor
5

Mój mały dodatek: sortowanie według klucza numerycznego (używanie skrótów do mniejszego kodu)

// 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]];
}
BananaAcid
źródło
3

Szybko i brudno:

Jeśli chcesz zamówić słownik (zwany dalej „myDict”), zrób to:

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

Następnie, gdy chcesz zamówić słownik, utwórz indeks:

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

Teraz obiekt indeksu * będzie zawierał odpowiednie klucze we właściwej kolejności. Zauważ, że to rozwiązanie nie wymaga, aby wszystkie klucze istniały, co jest typową sytuacją, z którą mamy do czynienia ...

Adam Prall
źródło
A co z sortArrayUsingSelector dla kolejności tablic? developer.apple.com/library/ios/documentation/cocoa/Conceptual/…
Alex Zavatone
2

Dla Swift 3 . Wypróbuj następujące podejście

        //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)
Dinesh
źródło
2

Minimalna implementacja uporządkowanej podklasy NSDictionary (na podstawie https://github.com/nicklockwood/OrderedDictionary ). Zapraszam do przedłużenia dla swoich potrzeb:

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

stosowanie

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

Cel 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

stosowanie

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]);
}
Cœur
źródło
0

Nie bardzo lubię C ++, ale jednym z rozwiązań, których używam coraz częściej, jest użycie Objective-C ++ i std::mapze standardowej biblioteki szablonów. Jest to słownik, którego klucze są automatycznie sortowane po wstawieniu. Działa zaskakująco dobrze zarówno w przypadku typów skalarnych, jak i obiektów Objective-C, zarówno jako kluczy, jak i wartości.

Jeśli chcesz dołączyć tablicę jako wartość, po prostu użyj std::vectorzamiast NSArray.

Jedynym zastrzeżeniem jest to, że możesz chcieć udostępnić własną insert_or_assignfunkcję, chyba że możesz użyć C ++ 17 (zobacz tę odpowiedź ). Ponadto musisz mieć typedefswoje typy, aby zapobiec niektórym błędom kompilacji. Kiedy już dowiesz się, jak używać std::map, iteratorów itp., Jest to dość proste i szybkie.

Martin Winter
źródło