順序付けられたキーを持つNSDictionary


81

基本的に連想配列(キーと値としての文字列)として使用しているNSDictionary(plistに格納されている)があります。アプリケーションの一部としてキーの配列を使用したいのですが、特定の順序にしたいのです(実際には、キーを並べ替えるためのアルゴリズムを記述できる順序ではありません)。キーの個別の配列をいつでも格納できますが、辞書のキーと配列の値を常に更新し、それらが常に対応していることを確認する必要があるため、これはちょっと厄介なようです。現在、私は[myDictionary allKeys]を使用していますが、明らかにこれにより、任意の保証されていない順序でそれらが返されます。私が見逃しているObjective-Cのデータ構造はありますか?誰かがこれをよりエレガントに行う方法について何か提案がありますか?

回答:


23

キーのNSMutableArrayを関連付けるソリューションはそれほど悪くありません。NSDictionaryのサブクラス化を回避し、アクセサーの記述に注意すれば、同期を維持するのはそれほど難しくないはずです。


2
1つの落とし穴は、NS​​(Mutable)Dictionaryがキーのコピーを作成するため、キー配列内のオブジェクトが異なることです。キーが変更されている場合、これは問題になる可能性があります。
クインテイラー

1
これは良い点ですが、可変性が配列内の項目を変更するのではなく、配列へのオブジェクトの追加と削除に制限されている場合は軽減されます。
Abizern 2009年

1
そもそもポインタを提供したキーを変更する人をどのように制限するかわからない。配列はそのキーへのポインタを格納するだけで、それが変更されたかどうかはわかりません。キーが変更された場合、追加されたエントリを配列からまったく削除できない可能性があります。これは、配列が辞書に存在しなくなったキーを保持している場合、またはその逆の場合、実際の同期の問題になる可能性があります...: -)
クインテイラー

2
ところで、私が過度に批判的に聞こえたら、私の謝罪。私もこれと同じ問題に取り組んできましたが、フレームワーク用であるため、考えられるあらゆる状況で堅牢で正しいものになるように設計しようとしています。別の配列はそれほど悪くないというのは正しいですが、それを追跡するクラスほど単純ではありません。さらに、そのようなクラスがNS(Mutable)Dictionaryを拡張すると、Cocoa辞書が必要な場所ならどこでも使用できるため、多くの利点があります。
クインテイラー

1
私は議論を気にしません。私の提案は単なる回避策でした。フレームワークを書くことはまったく別の提案であり、慎重に考える必要があります。個人的には、この理由で可変性が面倒だと感じており、自分のプログラムでこれらのクラスの使用を最小限に抑えるようにしています。
Abizern 2009年

19

私は実際の答えでゲームに遅れていますが、CHOrderedDictionaryを調査することに興味があるかもしれません。これはNSMutableDictionaryのサブクラスであり、キーの順序を維持するための別の構造をカプセル化します。(これはCHDataStructures.frameworkの一部です。)辞書と配列を別々に管理するよりも便利だと思います。

開示:これは私が書いたオープンソースコードです。それがこの問題に直面している他の人に役立つかもしれないことを願っています。


1
+ 1-まだ試していませんが、ドキュメントを調べたところ、ここでいくつかのすばらしい作業を行ったようです。Appleがこれらのファンダメンタルズの多くを欠いているのは少し恥ずかしいことだと思います。
ホイットニーランド2011年

恥ずかしいことではないと思います。フレームワークをどのように開発してほしいかという問題です。具体的には、新しいクラスは、Foundationに含めることを検討するために多くの価値を追加する必要があります。辞書をソートされたキーの配列と組み合わせるのはそれほど複雑ではなく、Appleは、一部の人々が使用する可能性のあるもので行き過ぎないようにすることで、フレームワークのサイズ(および保守性)を抑えようとします。
クインテイラー

3
順序付けられた辞書が欠落しているだけでなく、非常に多くの有用なCS101コレクションが欠落しているということです。作成者の範囲外で何度も成功を収めてきたC、C ++、Java、C#とは対照的に、Objective-CがAppleなしで広く成功する言語になるとは思いません。
ホイットニーランド2011年

1
この。です。驚くばかり。残念私は今までこのフレームワークを発見していませんでした。これは、将来のほとんどのプロジェクトの主力になるでしょう。どうもありがとう!
Dev Kanchen 2012

キーがNSStringの場合、次のことを行うのは問題ですか?辞書と配列を作成し、データを追加および削除するための一般的な方法があります。配列にはNSStringキーのリストが(希望の順序で)含まれています辞書にはNSStringキーと値が含まれています
user1046037 2012年

15

これを取得できるような組み込みメソッドはありません。しかし、単純なロジックが役に立ちます。辞書を準備するときに、各キーの前にいくつかの数値テキストを追加するだけです。お気に入り

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文字を切り取ります。


この最初の3文字削除する方法を示します。stackoverflow.com/questions/1073872/...を
レオン

7

NSDictionaryをサブクラス化する場合は、少なくとも次のメソッドを実装する必要があります。

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

必要なことを行う最も簡単な方法は、操作する独自のNSMutableDictionaryと順序付けられたキーのセットを格納するNSMutableArrayを含むNSMutableDictionaryのサブクラスを作成することです。

オブジェクトをエンコードする予定がない場合は、実装-encodeWithCoder:をスキップして、-initWithCoder:

上記の10個のメソッドのすべてのメソッド実装は、ホストされているディクショナリまたは順序付けられたキー配列を直接通過します。


5
自明ではなく、私をつまずかせたものがもう1つあります。NSCodingを実装する場合は、-(Class)classForKeyedArchiverをオーバーライドして[selfclass]を返します。クラスクラスターは、これを常に抽象親クラスを返すように設定しているため、これを変更しない限り、インスタンスは常にNS(Mutable)Dictionaryとしてデコードされます。
クインテイラー

5

私のちょっとした追加:数字キーによるソート(小さいコードには省略表記を使用)

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

3

クイック '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];
            }
        }

これで、* indexオブジェクトに適切なキーが正しい順序で含まれるようになります。このソリューションでは、すべてのキーが必ずしも存在する必要はないことに注意してください。これは、私たちが扱っている通常の状況です...


配列の順序にsortedArrayUsingSelectorはどうですか? developer.apple.com/library/ios/documentation/cocoa/Conceptual/...
アレックスZavatone

2

の場合、Swift3。次のアプローチを試してください

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

NSDictionaryの順序付きサブクラスの最小限の実装(https://github.com/nicklockwood/OrderedDictionaryに基づく)。ニーズに合わせて自由に拡張してください。

スウィフト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) }

Objective-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]);
}

0

私はC ++があまり好きではありませんが、私がますます使用していると思う解決策の1つは、Objective-C ++とstd::map標準テンプレートライブラリを使用することです。挿入時にキーが自動的にソートされる辞書です。キーと値の両方として、スカラー型またはObjective-Cオブジェクトのいずれかで驚くほどうまく機能します。

値として配列を含める必要がある場合は、のstd::vector代わりに使用してくださいNSArray

注意点の1つは、insert_or_assignC ++ 17を使用できない場合を除いて、独自の関数を提供することをお勧めします(この回答を参照)。また、typedef特定のビルドエラーを防ぐためにタイプを設定する必要があります。std::mapイテレータなどの使用方法がわかれば、非常に簡単で高速です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.