NSDictionary con claves ordenadas

81

Tengo un NSDictionary (almacenado en un plist) que básicamente estoy usando como una matriz asociativa (cadenas como claves y valores). Quiero usar la matriz de claves como parte de mi aplicación, pero me gustaría que estuvieran en un orden específico (no realmente un orden en el que pueda escribir un algoritmo para ordenarlas). Siempre podría almacenar una matriz separada de las claves, pero eso parece un poco complicado porque siempre tendría que actualizar las claves del diccionario, así como los valores de la matriz, y asegurarme de que siempre correspondan. Actualmente solo uso [myDictionary allKeys], pero obviamente esto las devuelve en un orden arbitrario y no garantizado. ¿Hay una estructura de datos en Objective-C que me falta? ¿Alguien tiene alguna sugerencia sobre cómo hacer esto de manera más elegante?

Andy Bourassa
fuente

Respuestas:

23

La solución de tener un NSMutableArray de claves asociado no es tan malo. Evita subclasificar NSDictionary y, si tiene cuidado con la escritura de los descriptores de acceso, no debería ser demasiado difícil mantener la sincronización.

Abizern
fuente
2
Un error es que NS (Mutable) Dictionary hace copias de claves, por lo que los objetos en la matriz de claves serán diferentes. Esto puede resultar problemático si se muta una clave.
Quinn Taylor
1
Ese es un buen punto, pero se mitiga si la mutabilidad se limita a agregar y eliminar objetos de la matriz en lugar de cambiar los elementos dentro de ellos.
Abizern
1
No estoy seguro de cómo restringiría que alguien modifique una clave para la que proporcionó un puntero en primer lugar. La matriz solo almacena un puntero a esa clave y no sabrá si cambia o no. Si se cambia la clave, es posible que ni siquiera pueda eliminar la entrada agregada de la matriz, lo que puede ser un problema de sincronización real si la matriz mantiene claves que ya no están en el diccionario, o viceversa ...: -)
Quinn Taylor
2
Por cierto, mis disculpas si sueno demasiado crítico. Yo mismo he estado lidiando con este mismo problema, pero como es para un marco, estoy tratando de diseñarlo para que sea robusto y correcto en cada situación concebible. Tiene razón en que una matriz separada no es tan mala, pero no es tan simple como una clase que la rastrea por usted. Además, existen numerosos beneficios si dicha clase extiende el Diccionario NS (Mutable), ya que podría usarse en cualquier lugar donde se requiera un diccionario Cocoa.
Quinn Taylor
1
No me importa la discusión. Mi sugerencia fue solo una solución. Escribir un marco es una propuesta completamente diferente y es algo que necesita una reflexión cuidadosa. Personalmente, encuentro la mutabilidad problemática por esta misma razón, y trato de minimizar el uso de esas clases en mis propios programas.
Abizern
19

Llego tarde al juego con una respuesta real, pero es posible que le interese investigar CHOrderedDictionary . Es una subclase de NSMutableDictionary que encapsula otra estructura para mantener el orden de las claves. (Es parte de CHDataStructures.framework .) Me parece más conveniente que administrar un diccionario y una matriz por separado.

Divulgación: este es el código de fuente abierta que escribí. Solo espero que pueda ser útil para otras personas que enfrentan este problema.

Quinn Taylor
fuente
1
+1: todavía no lo he probado, pero he revisado los documentos y parece que ha hecho un buen trabajo aquí. Creo que es un poco vergonzoso que Apple se pierda tantos de estos fundamentos.
Whitneyland
No creo que sea vergonzoso, es solo una cuestión de cómo quieren que se desarrolle su marco. Específicamente, las nuevas clases deben agregar mucho valor para ser consideradas para su inclusión en Foundation. No es tan complicado emparejar un diccionario con una matriz ordenada de claves, y Apple intenta contener el tamaño (y la capacidad de mantenimiento) del marco no exagerando con cosas que algunas personas podrían usar.
Quinn Taylor
3
No es solo que falte un diccionario ordenado, es que faltan tantas colecciones útiles de CS101. No creo que Objective-C sería un lenguaje de gran éxito sin Apple, a diferencia de C, C ++, Java, C #, que han tenido éxito muchas veces fuera del alcance de sus creadores.
Whitneyland
1
Esta. Es. Increíble. Lástima que no hubiera descubierto este marco hasta ahora. Este será un pilar en la mayoría de los proyectos futuros. ¡Muchas gracias!
Dev Kanchen
si la clave es una NSString, ¿sería un problema hacer lo siguiente? cree un diccionario y una matriz y tenga un método común para agregar y eliminar los datos. La matriz contiene una lista de claves NSString (en el orden deseado) El diccionario contiene las claves NSString y los valores
user1046037
15

No existe un método incorporado a partir del cual pueda adquirir esto. Pero una simple lógica funciona para ti. Simplemente puede agregar un poco de texto numérico delante de cada tecla mientras prepara el diccionario. Me gusta

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

Ahora, si puede usar sortingArrayUsingSelector mientras obtiene todas las claves en el mismo orden en que las coloca.

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

En el lugar donde desea mostrar las teclas en UIView, simplemente corte los 3 caracteres frontales.

Rajan Twanabashu
fuente
Esto muestra cómo eliminar los primeros tres caracteres: stackoverflow.com/questions/1073872/…
Leon
7

Si va a subclase NSDictionary, debe implementar estos métodos como mínimo:

  • NSDiccionario
    • -count
    • -objectForKey:
    • -keyEnumerator
  • NSMutableDiccionario
    • -removeObjectForKey:
    • -setObject:forKey:
  • NSCopying / NSMutableCopying
    • -copyWithZone:
    • -mutableCopyWithZone:
  • NSCoding
    • -encodeWithCoder:
    • -initWithCoder:
  • NSFastEnumeration (para Leopard)
    • -countByEnumeratingWithState:objects:count:

La forma más sencilla de hacer lo que desea es crear una subclase de NSMutableDictionary que contenga su propio NSMutableDictionary que manipule y un NSMutableArray para almacenar un conjunto ordenado de claves.

Si nunca va a codificar sus objetos, podría omitir la implementación -encodeWithCoder:y-initWithCoder:

Todas las implementaciones de sus métodos en los 10 métodos anteriores pasarían directamente a través de su diccionario alojado o su matriz de claves ordenada.

Ashley Clark
fuente
5
Hay uno más que no es obvio y me hizo tropezar - si implementas NSCoding, también anula - (Class) classForKeyedArchiver para devolver [self class]. Los clústeres de clases establecen esto para devolver siempre la clase principal abstracta, por lo que, a menos que cambie eso, las instancias siempre se decodificarán como un Diccionario NS (Mutable).
Quinn Taylor
5

Mi pequeña adición: ordenar por tecla numérica (usando notaciones abreviadas para códigos más pequeños)

// 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]];
}
PlátanoÁcido
fuente
3

Rápido y sucio:

Cuando necesite solicitar su diccionario (en adelante denominado "myDict"), haga lo siguiente:

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

Luego, cuando necesite ordenar su diccionario, cree un índice:

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

Ahora, el objeto * index contendrá las claves apropiadas en el orden correcto. Tenga en cuenta que esta solución no requiere que todas las claves existan necesariamente, que es la situación habitual con la que nos enfrentamos ...

Adam Prall
fuente
¿Qué pasa con sortedArrayUsingSelector para el orden de la matriz? developer.apple.com/library/ios/documentation/cocoa/Conceptual/…
Alex Zavatone
2

Para Swift 3 . Pruebe el siguiente enfoque

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

Implementación mínima de una subclase ordenada de NSDictionary (basada en https://github.com/nicklockwood/OrderedDictionary ). No dude en ampliar según sus necesidades:

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

uso

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 objetivo

@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

uso

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

No me gusta mucho C ++, pero una solución que me veo usando cada vez más es usar Objective-C ++ y std::mapde la biblioteca de plantillas estándar. Es un diccionario cuyas claves se ordenan automáticamente al insertarlas. Funciona sorprendentemente bien con tipos escalares u objetos Objective-C tanto como claves como valores.

Si necesita incluir una matriz como valor, use en std::vectorlugar de NSArray.

Una advertencia es que es posible que desee proporcionar su propia insert_or_assignfunción, a menos que pueda usar C ++ 17 (consulte esta respuesta ). Además, necesita typedefsus tipos para evitar ciertos errores de compilación. Una vez que descubra cómo usar los std::mapiteradores, etc., es bastante sencillo y rápido.

Martin Winter
fuente