NSDictionary với các khóa có thứ tự


81

Tôi có một NSDictionary (được lưu trữ trong một plist) mà về cơ bản tôi đang sử dụng như một mảng kết hợp (chuỗi làm khóa và giá trị). Tôi muốn sử dụng mảng khóa như một phần của ứng dụng của mình, nhưng tôi muốn chúng theo một thứ tự cụ thể (không thực sự là thứ tự mà tôi có thể viết một thuật toán để sắp xếp chúng vào). Tôi luôn có thể lưu trữ một mảng khóa riêng biệt, nhưng điều đó có vẻ hơi khó vì tôi luôn phải cập nhật các khóa của từ điển cũng như các giá trị của mảng và đảm bảo chúng luôn tương ứng. Hiện tại tôi chỉ sử dụng [myDictionary allKeys], nhưng rõ ràng điều này trả về chúng theo thứ tự tùy ý, không đảm bảo. Có cấu trúc dữ liệu nào trong Objective-C mà tôi đang thiếu không? Có ai có bất kỳ đề xuất về cách thanh lịch hơn để làm điều này?

Câu trả lời:


23

Giải pháp có một NSMutableArray khóa được liên kết không quá tệ. Nó tránh phân lớp NSDictionary, và nếu bạn cẩn thận với việc viết các trình truy cập, sẽ không quá khó để duy trì đồng bộ hóa.


2
Một cạm bẫy là NS (Mutable) Dictionary tạo bản sao của các khóa, vì vậy các đối tượng trong mảng khóa sẽ khác nhau. Điều này có thể có vấn đề nếu một khóa bị đột biến.
Quinn Taylor

1
Đó là một điểm tốt, nhưng sẽ giảm nhẹ nếu khả năng thay đổi bị hạn chế trong việc thêm và xóa đối tượng khỏi mảng thay vì thay đổi các mục bên trong chúng.
Abizern

1
Bạn không chắc chắn về cách bạn hạn chế ai đó sửa đổi khóa mà họ đã cung cấp con trỏ ngay từ đầu. Mảng chỉ lưu trữ một con trỏ đến khóa đó và sẽ không biết liệu nó có thay đổi hay không. Nếu khóa bị thay đổi, bạn thậm chí không thể xóa mục nhập đã thêm khỏi mảng, đây có thể là sự cố đồng bộ hóa thực sự nếu mảng bạn giữ các khóa không còn trong từ điển hoặc ngược lại ...: -)
Quinn Taylor

2
BTW, tôi xin lỗi nếu tôi có vẻ chỉ trích quá mức. Bản thân tôi cũng đang giải quyết vấn đề này, nhưng vì nó dành cho một khung công tác, tôi đang cố gắng thiết kế nó trở nên mạnh mẽ và chính xác trong mọi tình huống có thể tưởng tượng được. Bạn đúng rằng một mảng riêng biệt không quá tệ, nhưng nó không đơn giản như một lớp theo dõi nó cho bạn. Thêm vào đó, có rất nhiều lợi ích nếu một lớp như vậy mở rộng Từ điển NS (Có thể thay đổi), vì nó có thể được sử dụng ở bất kỳ nơi nào cần có từ điển Ca cao.
Quinn Taylor

1
Tôi không bận tâm về cuộc thảo luận. Đề xuất của tôi chỉ là một cách giải quyết. Viết một khuôn khổ là một đề xuất hoàn toàn khác và nó là một cái gì đó cần được suy nghĩ cẩn thận. Cá nhân tôi thấy khả năng thay đổi rất rắc rối vì lý do này và tôi cố gắng giảm thiểu việc sử dụng các lớp đó trong các chương trình của riêng mình.
Abizern

19

Tôi đã muộn trò chơi với một câu trả lời thực tế, nhưng bạn có thể muốn điều tra CHOrderedDictionary . Đó là một lớp con của NSMutableDictionary đóng gói một cấu trúc khác để duy trì thứ tự khóa. (Nó là một phần của CHDataStructures.framework .) Tôi thấy nó thuận tiện hơn việc quản lý một từ điển và mảng riêng biệt.

Tiết lộ: Đây là mã nguồn mở mà tôi đã viết. Chỉ hy vọng nó có thể hữu ích cho những người khác đang đối mặt với vấn đề này.


1
+1 - Chưa thử nhưng đã xem qua tài liệu và có vẻ như bạn đã làm được một số việc tốt ở đây. Tôi nghĩ hơi xấu hổ khi Apple thiếu rất nhiều nguyên tắc cơ bản này.
whitneyland

Tôi không nghĩ điều đó đáng xấu hổ, đó chỉ là một câu hỏi về việc họ muốn framework của họ phát triển như thế nào. Cụ thể, các lớp mới phải bổ sung nhiều giá trị mới được xem xét đưa vào Foundation. Việc ghép nối một từ điển với một dãy khóa được sắp xếp không phức tạp như vậy và Apple cố gắng chứa kích thước (và khả năng bảo trì) của khung bằng cách không sử dụng quá nhiều thứ mà một số người có thể sử dụng.
Quinn Taylor,

3
Không chỉ là thiếu từ điển có thứ tự mà còn thiếu rất nhiều bộ sưu tập CS101 hữu ích. Tôi không tin Objective-C sẽ là một ngôn ngữ thành công rộng rãi nếu không có Apple, trái ngược với C, C ++, Java, C #, những ngôn ngữ đã chứng tỏ thành công nhiều lần ngoài tầm nhìn của người tạo ra chúng.
whitneyland

1
Điều này. Là. Tuyệt vời. Thật xấu hổ vì tôi đã không phát hiện ra khuôn khổ này cho đến bây giờ. Đây sẽ là trụ cột chính trong hầu hết các dự án trong tương lai. Cảm ơn rất nhiều!
Dev Kanchen

nếu khóa là một NSString, có vấn đề gì không khi làm như sau? tạo một từ điển cũng như một mảng và có một phương thức chung để thêm và xóa dữ liệu. Mảng chứa danh sách các phím NSString (theo thứ tự mong muốn) từ điển chứa các phím NSString và các giá trị
user1046037

15

Không có phương pháp sẵn có nào như vậy mà bạn có thể có được điều này. Nhưng một logic đơn giản làm việc cho bạn. Bạn chỉ cần thêm một vài văn bản số vào trước mỗi phím trong khi chuẩn bị từ điển. Giống

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

Bây giờ nếu bạn có thể sử dụng sortingArrayUsingSelector trong khi nhận tất cả các khóa theo thứ tự như bạn đặt.

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

Tại nơi muốn hiển thị các phím trong UIView, bạn chỉ cần chặt 3 ký tự phía trước.


Điều này cho thấy cách xóa ba ký tự đầu tiên: stackoverflow.com/questions/1073872/…
Leon

7

Nếu bạn định phân lớp NSDictionary, bạn cần tối thiểu triển khai các phương pháp này:

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

Cách dễ nhất để thực hiện những gì bạn muốn là tạo một lớp con của NSMutableDictionary có chứa 'NSMutableDictionary của chính nó mà nó thao tác và một NSMutableArray để lưu trữ một bộ khóa có thứ tự.

Nếu bạn không bao giờ mã hóa các đối tượng của mình, bạn có thể hình dung việc bỏ qua việc triển khai -encodeWithCoder:-initWithCoder:

Tất cả các triển khai phương thức của bạn trong 10 phương thức trên sau đó sẽ đi trực tiếp qua từ điển được lưu trữ của bạn hoặc mảng khóa có thứ tự của bạn.


5
Còn một điều nữa không rõ ràng và khiến tôi vấp phải - nếu bạn triển khai NSCoding, cũng ghi đè - (Class) classForKeyedArchiver để trả về [self class]. Các cụm lớp đặt điều này luôn trả về lớp cha trừu tượng, vì vậy trừ khi bạn thay đổi điều đó, các thể hiện sẽ luôn được giải mã dưới dạng Từ điển NS (Có thể thay đổi).
Quinn Taylor

5

Bổ sung nhỏ của tôi: sắp xếp theo phím số (Sử dụng ký hiệu viết tắt cho mã nhỏ hơn)

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

Nhanh 'n bẩn:

Khi bạn cần đặt từ điển của mình (ở đây gọi là “myDict”), hãy làm như sau:

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

Sau đó, khi bạn cần sắp xếp từ điển của mình, hãy tạo một chỉ mục:

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

Bây giờ, đối tượng chỉ mục * sẽ chứa các khóa thích hợp theo đúng thứ tự. Lưu ý rằng giải pháp này không yêu cầu tất cả các khóa nhất thiết phải tồn tại, đó là tình huống thông thường mà chúng tôi đang giải quyết ...


Điều gì về sortedArrayUsingSelector cho thứ tự mảng? developer.apple.com/library/ios/documentation/cocoa/Conceptual/…
Alex Zavatone

2

Đối với, Swift 3 . Hãy thử cách tiếp cận sau

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

Triển khai tối thiểu lớp con có thứ tự của NSDictionary (dựa trên https://github.com/nicklockwood/OrderedDictionary ). Vui lòng mở rộng cho các nhu cầu của bạn:

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

sử dụng

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

sử dụng

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

Tôi không thích C ++ cho lắm, nhưng một giải pháp mà tôi thấy mình ngày càng sử dụng nhiều hơn là sử dụng Objective-C ++ và std::maptừ Thư viện mẫu chuẩn. Nó là một từ điển có các khóa được sắp xếp tự động khi chèn. Nó hoạt động tốt một cách đáng ngạc nhiên với các kiểu vô hướng hoặc các đối tượng Objective-C vừa là khóa vừa là giá trị.

Nếu bạn cần bao gồm một mảng dưới dạng giá trị, chỉ cần sử dụng std::vectorthay vì NSArray.

Một lưu ý là bạn có thể muốn cung cấp insert_or_assignhàm của riêng mình , trừ khi bạn có thể sử dụng C ++ 17 (xem câu trả lời này ). Ngoài ra, bạn cần typedefloại của mình để ngăn chặn một số lỗi xây dựng nhất định. Sau khi bạn tìm ra cách sử dụng std::map, các trình vòng lặp, v.v., nó khá đơn giản và nhanh chóng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.