web-dev-qa-db-fra.com

NSDictionary avec clés ordonnées

J'ai un NSDictionary (stocké dans une liste) que j'utilise essentiellement comme tableau associatif (chaînes comme clés et valeurs). Je veux utiliser le tableau de clés dans le cadre de mon application, mais j'aimerais qu'elles soient dans un ordre spécifique (pas vraiment un ordre dans lequel je peux écrire un algorithme pour les trier). Je pourrais toujours stocker un tableau séparé des clés, mais cela semble un peu kludgey car je devrais toujours mettre à jour les clés du dictionnaire ainsi que les valeurs du tableau, et m'assurer qu'elles correspondent toujours. Actuellement, j'utilise simplement [myDictionary allKeys], mais cela les renvoie évidemment dans un ordre arbitraire et non garanti. Existe-t-il une structure de données dans Objective-C qui me manque? Quelqu'un a-t-il des suggestions sur la façon de le faire de manière plus élégante?

78
Andy Bourassa

La solution d'avoir un NSMutableArray de clés associé n'est pas si mauvaise. Cela évite de sous-classer NSDictionary, et si vous faites attention à l'écriture d'accesseurs, il ne devrait pas être trop difficile de rester synchronisé.

23
Abizern

Je suis en retard dans le jeu avec une réponse réelle, mais vous pourriez être intéressé à enquêter CHOrderedDictionary . C'est une sous-classe de NSMutableDictionary qui encapsule une autre structure pour maintenir l'ordre des clés. (Cela fait partie de CHDataStructures.framework .) Je trouve que c'est plus pratique que de gérer un dictionnaire et un tableau séparément.

Divulgation: Ceci est du code open-source que j'ai écrit. J'espère juste que cela peut être utile à d'autres personnes confrontées à ce problème.

19
Quinn Taylor

Il n'y a pas une telle méthode intégrée à partir de laquelle vous pouvez l'acquérir. Mais une logique simple fonctionne pour vous. Vous pouvez simplement ajouter quelques textes numériques devant chaque touche pendant que vous préparez le dictionnaire. Comme

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

Maintenant, si vous pouvez utiliser sortingArrayUsingSelector tout en obtenant toutes les clés dans le même ordre que vous placez.

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

À l'endroit où vous souhaitez afficher les clés dans UIView, coupez simplement le caractère avant 3.

15
Rajan Twanabashu

Si vous allez sous-classer NSDictionary, vous devez implémenter ces méthodes au minimum:

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

La façon la plus simple de faire ce que vous voulez est de créer une sous-classe de NSMutableDictionary qui contient son propre NSMutableDictionary qu'il manipule et un NSMutableArray pour stocker un ensemble ordonné de clés.

Si vous ne codez jamais vos objets, vous pouvez imaginer ignorer l'implémentation de -encodeWithCoder: et -initWithCoder:

Toutes vos implémentations de méthode dans les 10 méthodes ci-dessus passeraient alors directement par votre dictionnaire hébergé ou votre tableau de clés ordonné.

7
Ashley Clark

Mon petit plus: le tri par clé numérique (en utilisant des notations abrégées pour un code plus petit)

// 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]];
}
5
BananaAcid

Rapide et sale:

Lorsque vous devez commander votre dictionnaire (appelé ici "myDict"), procédez comme suit:

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

Ensuite, lorsque vous devez commander votre dictionnaire, créez un index:

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

Maintenant, l'objet * index contiendra les clés appropriées dans le bon ordre. Notez que cette solution ne nécessite pas que toutes les clés existent nécessairement, ce qui est la situation habituelle avec laquelle nous avons affaire ...

3
Adam Prall

Pour, Swift. Veuillez essayer l'approche suivante

        //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
Dinesh

Implémentation minimale d'une sous-classe ordonnée de NSDictionary (basée sur https://github.com/nicklockwood/OrderedDictionary ). N'hésitez pas à étendre vos besoins:

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

usage

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

Objectif 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

usage

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]);
}
1
Cœur

Je n'aime pas beaucoup C++, mais une solution que je me vois de plus en plus utiliser est d'utiliser Objective-C++ et std::map à partir de la bibliothèque de modèles standard. Il s'agit d'un dictionnaire dont les clés sont automatiquement triées lors de l'insertion. Cela fonctionne étonnamment bien avec des types scalaires ou des objets Objective-C à la fois comme clés et comme valeurs.

Si vous devez inclure un tableau en tant que valeur, utilisez simplement std::vector au lieu de NSArray.

Une mise en garde est que vous voudrez peut-être fournir votre propre insert_or_assign, sauf si vous pouvez utiliser C++ 17 (voir cette réponse ). De plus, vous devez typedef vos types pour éviter certaines erreurs de build. Une fois que vous avez compris comment utiliser std::map, itérateurs, etc., c'est assez simple et rapide.

0
Martin Winter