У меня есть NSDictionary (хранящийся в списке), который я в основном использую как ассоциативный массив (строки как ключи и значения). Я хочу использовать массив ключей как часть своего приложения, но я бы хотел, чтобы они располагались в определенном порядке (на самом деле не в том порядке, в котором я могу написать алгоритм для их сортировки). Я всегда мог бы хранить отдельный массив ключей, но это кажется немного беспорядочным, потому что мне всегда приходилось обновлять ключи словаря, а также значения массива и следить за тем, чтобы они всегда соответствовали. В настоящее время я просто использую [myDictionary allKeys], но, очевидно, это возвращает их в произвольном, негарантированном порядке. Есть ли структура данных в Objective-C, которую мне не хватает? Есть ли у кого-нибудь предложения, как это сделать более элегантно?
NSDictionary с упорядоченными ключами
Ответы (9)
Решение иметь связанный NSMutableArray ключей не так уж и плохо. Он избегает создания подклассов NSDictionary, и если вы будете осторожны с написанием аксессоров, поддерживать синхронизацию не должно быть слишком сложно.
Я опаздываю в игру с фактическим ответом, но вам может быть интересно изучить CHOrderedDictionary. Это подкласс NSMutableDictionary, который инкапсулирует другую структуру для поддержания порядка ключей. (Это часть CHDataStructures.framework.) Я считаю, что это удобнее, чем управлять словарем и массивом по отдельности.
Раскрытие информации: это открытый код, который я написал. Просто надеюсь, что это может быть полезно другим, столкнувшимся с этой проблемой.
Нет такого встроенного метода, с помощью которого вы могли бы получить это. Но простая логика работает на вас. Вы можете просто добавить несколько числовых текстов перед каждой клавишей, пока готовите словарь. Нравится
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 символа.
Если вы собираетесь создать подкласс NSDictionary, вам необходимо реализовать как минимум эти методы:
- NSDictionary
-count
-objectForKey:
-keyEnumerator
- NSMutableDictionary
-removeObjectForKey:
-setObject:forKey:
- NSCopying/NSMutableCopying
-copyWithZone:
-mutableCopyWithZone:
- NSCoding
-encodeWithCoder:
-initWithCoder:
- NSFastEnumeration (for Leopard)
-countByEnumeratingWithState:objects:count:
Самый простой способ сделать то, что вы хотите, - создать подкласс NSMutableDictionary, который содержит собственный NSMutableDictionary, которым он управляет, и NSMutableArray для хранения упорядоченного набора ключей.
Если вы никогда не собираетесь кодировать свои объекты, вы можете пропустить реализацию -encodeWithCoder:
и -initWithCoder:
Все реализации ваших методов в 10 приведенных выше методах будут затем либо напрямую проходить через ваш размещенный словарь, либо через ваш упорядоченный массив ключей.
Мое небольшое дополнение: сортировка по числовому ключу (с использованием сокращенных обозначений для меньшего кода)
// 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]];
}
Быстро и грязно:
Если вам нужно заказать словарь (называемый здесь «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];
}
}
Теперь объект индекса * будет содержать соответствующие ключи в правильном порядке. Обратите внимание, что это решение не требует, чтобы все ключи обязательно существовали, что является обычной ситуацией, с которой мы имеем дело ...
Минимальная реализация упорядоченного подкласса NSDictionary (на основе https://github.com/nicklockwood/OrderedDictionary ). Не стесняйтесь расширять для своих нужд:
Swift 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]);
}
Для Swift 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)
Я не очень люблю C ++, но одно решение, которое я использую все чаще и чаще, - это использование Objective-C ++ и std::map
из стандартной библиотеки шаблонов. Это словарь, ключи которого автоматически сортируются при вставке. Он на удивление хорошо работает как со скалярными типами, так и с объектами Objective-C как в качестве ключей, так и в качестве значений.
Если вам нужно включить массив как значение, просто используйте std::vector
вместо NSArray
.
Одно предостережение: вы можете захотеть предоставить свою собственную insert_or_assign
функцию, если вы не можете использовать C ++ 17 (см. этот ответ ). Кроме того, вам нужно typedef
ваши типы, чтобы предотвратить определенные ошибки сборки. Как только вы поймете, как использовать std::map
, итераторы и т. Д., Это будет довольно просто и быстро.