NSDictionary mit bestellten Schlüsseln

81

Ich habe ein NSDictionary (in einer Liste gespeichert), das ich grundsätzlich als assoziatives Array verwende (Zeichenfolgen als Schlüssel und Werte). Ich möchte das Array von Schlüsseln als Teil meiner Anwendung verwenden, möchte jedoch, dass sie in einer bestimmten Reihenfolge vorliegen (nicht wirklich in einer Reihenfolge, in der ich einen Algorithmus zum Sortieren schreiben kann). Ich könnte immer ein separates Array der Schlüssel speichern, aber das scheint irgendwie kludgey, weil ich immer die Schlüssel des Wörterbuchs sowie die Werte des Arrays aktualisieren und sicherstellen müsste, dass sie immer übereinstimmen. Momentan verwende ich nur [myDictionary allKeys], aber dies gibt sie offensichtlich in einer beliebigen, nicht garantierten Reihenfolge zurück. Gibt es eine Datenstruktur in Objective-C, die mir fehlt? Hat jemand Vorschläge, wie man das eleganter macht?

Andy Bourassa
quelle

Antworten:

23

Die Lösung eines zugeordneten NSMutableArray von Schlüsseln ist nicht so schlecht. Es wird vermieden, dass NSDictionary in Unterklassen unterteilt wird. Wenn Sie beim Schreiben von Accessoren vorsichtig sind, sollte es nicht zu schwierig sein, die Synchronisierung aufrechtzuerhalten.

Abizern
quelle
2
Eine Gefahr besteht darin, dass NS (Mutable) Dictionary Kopien von Schlüsseln erstellt, sodass die Objekte im Schlüsselarray unterschiedlich sind. Dies kann problematisch sein, wenn ein Schlüssel mutiert ist.
Quinn Taylor
1
Das ist ein guter Punkt, wird aber gemildert, wenn die Veränderlichkeit darauf beschränkt ist, Objekte zum Array hinzuzufügen und daraus zu entfernen, anstatt die darin enthaltenen Elemente zu ändern.
Abizern
1
Ich bin mir nicht sicher, wie Sie jemanden daran hindern würden, einen Schlüssel zu ändern, für den er überhaupt einen Zeiger bereitgestellt hat. Das Array speichert nur einen Zeiger auf diesen Schlüssel und weiß nicht, ob er sich ändert oder nicht. Wenn der Schlüssel geändert wird, können Sie den hinzugefügten Eintrag möglicherweise überhaupt nicht aus dem Array entfernen. Dies kann ein echtes Synchronisationsproblem sein, wenn das Array Schlüssel enthält, die nicht mehr im Wörterbuch enthalten sind, oder umgekehrt ...: -)
Quinn Taylor
2
Übrigens, ich entschuldige mich, wenn ich zu kritisch klinge. Ich habe mich selbst mit dem gleichen Problem befasst, aber da es sich um ein Framework handelt, versuche ich, es so zu gestalten, dass es in jeder denkbaren Situation robust und korrekt ist. Sie haben Recht, dass ein separates Array nicht so schlecht ist, aber es ist nicht so einfach wie eine Klasse, die es für Sie verfolgt. Darüber hinaus bietet eine solche Klasse zahlreiche Vorteile, wenn sie das NS-Wörterbuch (Mutable Dictionary) erweitert, da sie überall dort verwendet werden kann, wo ein Cocoa-Wörterbuch erforderlich ist.
Quinn Taylor
1
Die Diskussion macht mir nichts aus. Mein Vorschlag war nur eine Problemumgehung. Das Schreiben eines Frameworks ist eine völlig andere Aussage und erfordert sorgfältige Überlegungen. Persönlich finde ich die Veränderbarkeit aus genau diesem Grund problematisch und versuche, die Verwendung dieser Klassen in meinen eigenen Programmen zu minimieren.
Abizern
19

Ich bin zu spät zum Spiel mit einer tatsächlichen Antwort, aber Sie könnten interessiert sein, CHOrderedDictionary zu untersuchen . Es ist eine Unterklasse von NSMutableDictionary, die eine andere Struktur zur Aufrechterhaltung der Schlüsselreihenfolge kapselt. (Es ist Teil von CHDataStructures.framework .) Ich finde es bequemer, als ein Wörterbuch und ein Array getrennt zu verwalten.

Offenlegung: Dies ist Open-Source-Code, den ich geschrieben habe. Ich hoffe nur, dass es für andere nützlich sein kann, die mit diesem Problem konfrontiert sind.

Quinn Taylor
quelle
1
+1 - Ich habe es noch nicht ausprobiert, habe aber die Dokumente durchgesehen und es scheint, dass Sie hier gute Arbeit geleistet haben. Ich finde es etwas peinlich, dass Apple so viele dieser Grundlagen vermisst.
Whitneyland
Ich finde es nicht peinlich, es ist nur eine Frage, wie sich ihr Framework entwickeln soll. Insbesondere müssen neue Klassen viel Wert hinzufügen, um für die Aufnahme in Foundation berücksichtigt zu werden. Es ist nicht so kompliziert, ein Wörterbuch mit einem sortierten Array von Schlüsseln zu koppeln, und Apple versucht, die Größe (und Wartbarkeit) des Frameworks einzudämmen, indem es nicht mit Dingen über Bord geht, die manche Leute möglicherweise verwenden.
Quinn Taylor
3
Es fehlt nicht nur ein geordnetes Wörterbuch, sondern auch so viele nützliche CS101-Sammlungen. Ich glaube nicht, dass Objective-C ohne Apple eine weithin erfolgreiche Sprache wäre, im Gegensatz zu C, C ++, Java, C #, die sich außerhalb des Zuständigkeitsbereichs ihrer Entwickler oft als erfolgreich erwiesen haben.
Whitneyland
1
Dies. Ist. Genial. Schade, dass ich diesen Rahmen bis jetzt nicht entdeckt hatte. Dies wird eine Hauptstütze in den meisten zukünftigen Projekten sein. Danke vielmals!
Dev Kanchen
Wenn der Schlüssel ein NSString ist, wäre es ein Problem, Folgendes zu tun? Erstellen Sie ein Wörterbuch sowie ein Array und verwenden Sie eine gemeinsame Methode zum Hinzufügen und Entfernen der Daten. Array enthält Liste der NSString-Schlüssel (in der gewünschten Reihenfolge) Wörterbuch enthält die NSString-Schlüssel und die Werte
user1046037
15

Es gibt keine solche eingebaute Methode, mit der Sie diese erwerben können. Aber eine einfache Logik funktioniert für Sie. Sie können einfach einige numerische Texte vor jede Taste einfügen, während Sie das Wörterbuch vorbereiten. Mögen

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

Wenn Sie jetzt sortingArrayUsingSelector verwenden können, während Sie alle Schlüssel in derselben Reihenfolge abrufen, in der Sie sie platziert haben.

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

An der Stelle, an der Sie Tasten in UIView anzeigen möchten, hacken Sie einfach die vorderen 3 Zeichen ab.

Rajan Twanabashu
quelle
Dies zeigt, wie die ersten drei Zeichen entfernt werden: stackoverflow.com/questions/1073872/…
Leon
7

Wenn Sie NSDictionary in Unterklassen unterteilen möchten, müssen Sie mindestens folgende Methoden implementieren:

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

Der einfachste Weg, das zu tun, was Sie wollen, besteht darin, eine Unterklasse von NSMutableDictionary zu erstellen, die ein eigenes NSMutableDictionary enthält, das manipuliert wird, und ein NSMutableArray, um einen geordneten Satz von Schlüsseln zu speichern.

Wenn Sie Ihre Objekte niemals codieren möchten, können Sie die Implementierung -encodeWithCoder:und überspringen-initWithCoder:

Alle Ihre Methodenimplementierungen in den 10 oben genannten Methoden würden dann entweder direkt durch Ihr gehostetes Wörterbuch oder Ihr geordnetes Schlüsselarray gehen.

Ashley Clark
quelle
5
Es gibt noch eine, die nicht offensichtlich ist und mich auslöste - wenn Sie NSCoding implementieren, überschreiben Sie auch - (Class) classForKeyedArchiver, um [self class] zurückzugeben. Klassencluster setzen dies so, dass immer die abstrakte übergeordnete Klasse zurückgegeben wird. Wenn Sie dies nicht ändern, werden Instanzen immer als NS-Wörterbuch (veränderbar) dekodiert.
Quinn Taylor
5

Mein kleiner Zusatz: Sortieren nach Zifferntasten (Verwenden von Kurzschreibweisen für kleineren Code)

// 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
quelle
3

Schnell und schmutzig:

Wenn Sie Ihr Wörterbuch bestellen müssen (hier als "myDict" bezeichnet), gehen Sie folgendermaßen vor:

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

Wenn Sie dann Ihr Wörterbuch bestellen müssen, erstellen Sie einen Index:

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

Jetzt enthält das Indexindexobjekt * die entsprechenden Schlüssel in der richtigen Reihenfolge. Beachten Sie, dass für diese Lösung nicht unbedingt alle Schlüssel vorhanden sein müssen. Dies ist die übliche Situation, mit der wir es zu tun haben ...

Adam Prall
quelle
Was ist mit sortedArrayUsingSelector für die Array-Reihenfolge? developer.apple.com/library/ios/documentation/cocoa/Conceptual/…
Alex Zavatone
2

Für Swift 3 . Bitte probieren Sie den folgenden Ansatz aus

        //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
quelle
2

Minimale Implementierung einer geordneten Unterklasse von NSDictionary (basierend auf https://github.com/nicklockwood/OrderedDictionary ). Fühlen Sie sich frei, für Ihre Bedürfnisse zu erweitern:

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

Verwendung

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

Ziel 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

Verwendung

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
quelle
0

Ich mag C ++ nicht sehr, aber eine Lösung, die ich immer häufiger benutze, ist die Verwendung von Objective-C ++ und std::mapaus der Standardvorlagenbibliothek. Es ist ein Wörterbuch, dessen Schlüssel beim Einfügen automatisch sortiert werden. Es funktioniert überraschend gut mit Skalartypen oder Objective-C-Objekten sowohl als Schlüssel als auch als Werte.

Wenn Sie ein Array als Wert einfügen müssen, verwenden Sie einfach std::vectoranstelle von NSArray.

Eine Einschränkung ist, dass Sie möglicherweise Ihre eigene insert_or_assignFunktion bereitstellen möchten , es sei denn, Sie können C ++ 17 verwenden (siehe diese Antwort ). Außerdem müssen Sie typedefIhre Typen eingeben, um bestimmte Buildfehler zu vermeiden. Sobald Sie herausgefunden haben, wie man es benutzt std::map, Iteratoren usw., ist es ziemlich einfach und schnell.

Martin Winter
quelle