Truy cập khóa Dictionary.Keys thông qua một chỉ mục số


160

Tôi đang sử dụng một Dictionary<string, int>nơi intlà một số trong những chìa khóa.

Bây giờ, tôi cần truy cập Khóa được chèn cuối cùng bên trong Từ điển, nhưng tôi không biết tên của nó. Nỗ lực rõ ràng:

int LastCount = mydict[mydict.keys[mydict.keys.Count]];

không hoạt động, vì Dictionary.Keyskhông triển khai [] -indexer.

Tôi chỉ tự hỏi nếu có bất kỳ lớp học tương tự? Tôi đã nghĩ về việc sử dụng Stack, nhưng điều đó chỉ lưu trữ một chuỗi. Bây giờ tôi có thể tạo cấu trúc của riêng mình và sau đó sử dụng một Stack<MyStruct>, nhưng tôi tự hỏi liệu có một cách thay thế nào khác, về cơ bản là một Từ điển thực hiện một [] -exexer trên các phím không?


1
Điều gì xảy ra nếu bạn đóng hộp biến đó?
Paul Prewett

Câu trả lời:


222

Như @Falanwe chỉ ra trong một bình luận, làm điều gì đó như thế này là không chính xác :

int LastCount = mydict.Keys.ElementAt(mydict.Count -1);

Bạn không nên phụ thuộc vào thứ tự các phím trong Từ điển. Nếu bạn cần đặt hàng, bạn nên sử dụng OrderedDipedia , như được đề xuất trong câu trả lời này . Các câu trả lời khác trên trang này cũng thú vị.


1
dường như không hoạt động với HashTableSystem.Collections.ICollection 'không chứa định nghĩa cho' ElementAt 'và không có phương thức mở rộng' ElementAt 'chấp nhận đối số đầu tiên của loại' System.Collections.ICollection '
v.oddou

Bạn có thể sử dụng ElementAtOrDefaultphiên bản để làm việc với phiên bản ngoại lệ.
Tarık zgün Güner

22
Thật đáng sợ khi thấy một câu trả lời sai lầm trắng trợn như vậy được chấp nhận và nâng cao đến mức đó. Điều đó là sai bởi vì, như Dictionary<TKey,TValue>tài liệu ghi rõ "Thứ tự của các khóa trong Dictionary<TKey, TValue>.KeyCollectionlà không xác định." Thứ tự không được xác định, bạn không có cách nào để biết chắc chắn đó là vị trí cuối cùng ( mydict.Count -1)
Falanwe

Điều này thật đáng sợ ... nhưng hữu ích cho tôi vì tôi đang tìm kiếm xác nhận về sự nghi ngờ của tôi rằng bạn không thể tin vào đơn đặt hàng !!! Cảm ơn @Falanwe
Charlie

3
Đối với một số thứ tự không liên quan - thực tế là bạn đã trải qua tất cả các khóa.
Royi Mindel 7/12/2016

59

Bạn có thể sử dụng một OrderedDipedia .

Đại diện cho một tập hợp các cặp khóa / giá trị mà khóa hoặc chỉ mục có thể truy cập được.


42
Erhm, sau 19 lần nâng cấp, không ai đề cập rằng OrderedDipedia vẫn không cho phép lấy khóa theo chỉ mục?
Lazlo

1
Bạn có thể truy cập một giá trị với chỉ mục số nguyên bằng OrderedDixi , nhưng không truy cập bằng System.Collections.Generic.SortDixi <TKey, TValue> trong đó chỉ mục cần phải là TKey
Maxence

Tên OrderedDixi có liên quan đến chức năng thu thập này để duy trì các thành phần theo cùng thứ tự mà chúng đã được thêm vào. Trong một số trường hợp, một đơn đặt hàng có ý nghĩa tương tự như một loại, nhưng không có trong bộ sưu tập này.
Sharunas Bielskis

18

Từ điển là Bảng Hash, vì vậy bạn không biết thứ tự chèn!

Nếu bạn muốn biết khóa được chèn cuối cùng, tôi khuyên bạn nên mở rộng Từ điển để bao gồm giá trị LastKeyInserted.

Ví dụ:

public MyDictionary<K, T> : IDictionary<K, T>
{
    private IDictionary<K, T> _InnerDictionary;

    public K LastInsertedKey { get; set; }

    public MyDictionary()
    {
        _InnerDictionary = new Dictionary<K, T>();
    }

    #region Implementation of IDictionary

    public void Add(KeyValuePair<K, T> item)
    {
        _InnerDictionary.Add(item);
        LastInsertedKey = item.Key;

    }

    public void Add(K key, T value)
    {
        _InnerDictionary.Add(key, value);
        LastInsertedKey = key;
    }

    .... rest of IDictionary methods

    #endregion

}

Bạn sẽ gặp vấn đề tuy nhiên khi bạn sử dụng .Remove()để khắc phục điều này, bạn sẽ phải giữ một danh sách các phím được chèn theo thứ tự.


8

Tại sao bạn không mở rộng lớp từ điển để thêm thuộc tính được chèn khóa cuối cùng. Một cái gì đó như sau có thể?

public class ExtendedDictionary : Dictionary<string, int>
{
    private int lastKeyInserted = -1;

    public int LastKeyInserted
    {
        get { return lastKeyInserted; }
        set { lastKeyInserted = value; }
    }

    public void AddNew(string s, int i)
    {
        lastKeyInserted = i;

        base.Add(s, i);
    }
}

2
Bạn đang đặt lastKeyInserted vào giá trị cuối cùng được chèn. Bạn có nghĩa là đặt nó vào khóa cuối cùng được chèn hoặc bạn cần tên tốt hơn cho biến và thuộc tính.
Fantius

6

Bạn luôn có thể làm điều này:

string[] temp = new string[mydict.count];
mydict.Keys.CopyTo(temp, 0)
int LastCount = mydict[temp[mydict.count - 1]]

Nhưng tôi sẽ không đề nghị nó. Không có gì đảm bảo rằng khóa được chèn cuối cùng sẽ ở cuối mảng. Thứ tự cho Khóa trên MSDN là không xác định và có thể thay đổi. Trong thử nghiệm rất ngắn gọn của tôi, nó dường như được sắp xếp theo thứ tự chèn, nhưng bạn nên xây dựng sổ sách đúng cách như một chồng - như bạn đề xuất (mặc dù tôi không thấy sự cần thiết của một cấu trúc dựa trên các câu lệnh khác) - hoặc bộ đệm đơn biến nếu bạn chỉ cần biết khóa mới nhất.


5

Tôi nghĩ rằng bạn có thể làm một cái gì đó như thế này, cú pháp có thể sai, đã không sử dụng C # trong một thời gian Để có được mục cuối cùng

Dictionary<string, int>.KeyCollection keys = mydict.keys;
string lastKey = keys.Last();

hoặc sử dụng Max thay vì Last để lấy giá trị tối đa, tôi không biết cái nào phù hợp với mã của bạn hơn.


2
Tôi sẽ thêm rằng vì "Last ()" là một phương thức mở rộng, bạn sẽ cần .NET Framework 3.5 và để thêm "bằng System.Linq" ở đầu tệp .cs của bạn.
SuperOli

Hãy thử điều này lần cuối (khi sử dụng một chuỗi <chuỗi, chuỗi> rõ ràng :-) KeyValuePair <chuỗi, chuỗi> last = oAuthPairs.Last (); if (kvp.Key! = last.Key) {_oauth_ParamString = _oauth_ParamString + "&"; }
Tim Windsor

4

Tôi đồng ý với phần thứ hai của câu trả lời của Patrick. Ngay cả trong một số thử nghiệm dường như vẫn giữ trật tự chèn, tài liệu (và hành vi bình thường đối với từ điển và băm) nói rõ thứ tự là không xác định.

Bạn chỉ đang yêu cầu sự cố tùy thuộc vào thứ tự của các phím. Thêm sổ sách riêng của bạn (như Patrick nói, chỉ một biến duy nhất cho khóa được thêm cuối cùng) để đảm bảo. Ngoài ra, đừng bị cám dỗ bởi tất cả các phương pháp như Last và Max trên từ điển vì những phương pháp này có thể liên quan đến bộ so sánh chính (tôi không chắc về điều đó).


4

Trong trường hợp bạn quyết định sử dụng mã nguy hiểm có thể bị phá vỡ, chức năng mở rộng này sẽ lấy khóa từ một Dictionary<K,V>chỉ mục nội bộ của nó (mà đối với Mono và .NET hiện có vẻ theo thứ tự như bạn nhận được bằng cách liệt kê thuộc Keystính ).

Tốt hơn là sử dụng Linq : dict.Keys.ElementAt(i), nhưng chức năng đó sẽ lặp lại O (N); sau đây là O (1) nhưng với hình phạt hiệu suất phản chiếu.

using System;
using System.Collections.Generic;
using System.Reflection;

public static class Extensions
{
    public static TKey KeyByIndex<TKey,TValue>(this Dictionary<TKey, TValue> dict, int idx)
    {
        Type type = typeof(Dictionary<TKey, TValue>);
        FieldInfo info = type.GetField("entries", BindingFlags.NonPublic | BindingFlags.Instance);
        if (info != null)
        {
            // .NET
            Object element = ((Array)info.GetValue(dict)).GetValue(idx);
            return (TKey)element.GetType().GetField("key", BindingFlags.Public | BindingFlags.Instance).GetValue(element);
        }
        // Mono:
        info = type.GetField("keySlots", BindingFlags.NonPublic | BindingFlags.Instance);
        return (TKey)((Array)info.GetValue(dict)).GetValue(idx);
    }
};

Hmm, chỉnh sửa để cải thiện câu trả lời kiếm được một downvote. Tôi đã không làm rõ rằng mã là (rõ ràng) gớm ghiếc, và nên được xem xét cho phù hợp?
Glenn Slayden

4

Một thay thế sẽ là KeyedCollection nếu khóa được nhúng trong giá trị.

Chỉ cần tạo một triển khai cơ bản trong một lớp kín để sử dụng.

Vì vậy, để thay thế Dictionary<string, int>(đó không phải là một ví dụ rất tốt vì không có khóa rõ ràng cho một int).

private sealed class IntDictionary : KeyedCollection<string, int>
{
    protected override string GetKeyForItem(int item)
    {
        // The example works better when the value contains the key. It falls down a bit for a dictionary of ints.
        return item.ToString();
    }
}

KeyedCollection<string, int> intCollection = new ClassThatContainsSealedImplementation.IntDictionary();

intCollection.Add(7);

int valueByIndex = intCollection[0];

Về ý kiến ​​của bạn về chìa khóa, hãy xem câu trả lời tiếp theo của tôi cho câu hỏi này.
takrl

3

Cách bạn diễn đạt câu hỏi khiến tôi tin rằng int trong Từ điển chứa "vị trí" của mục trên Từ điển. Đánh giá từ xác nhận rằng các khóa không được lưu theo thứ tự chúng được thêm vào, nếu điều này là chính xác, điều đó có nghĩa là các khóa. Đếm (hoặc .Count - 1, nếu bạn đang sử dụng dựa trên zero) vẫn nên luôn luôn là số của khóa nhập cuối cùng?

Nếu đó là chính xác, có lý do nào bạn không thể sử dụng Từ điển <int, chuỗi> để bạn có thể sử dụng mydict [mydict.Keys.Count] không?


2

Tôi không biết điều này có hoạt động không vì tôi khá chắc chắn rằng các khóa không được lưu theo thứ tự chúng được thêm vào, nhưng bạn có thể chuyển KeysCollection vào Danh sách và sau đó lấy khóa cuối cùng trong danh sách ... nhưng nó sẽ có giá trị để có một cái nhìn.

Điều khác duy nhất tôi có thể nghĩ đến là lưu trữ các khóa trong danh sách tra cứu và thêm các khóa vào danh sách trước khi bạn thêm chúng vào từ điển ... nó không đẹp lắm.


@Juan: không có phương pháp .Last () trên KeyCollection
lomaxx

Tôi đã không kiểm tra mã, nhưng phương thức được ghi lại trên [MSDN] [1] có thể là phiên bản khác của khung không? [1]: msdn.microsoft.com/en-us/l Library / bb908406.aspx
Juan

Trễ 2 năm nhưng nó có thể giúp ai đó ... xem câu trả lời của tôi cho bài viết của Juan bên dưới. Last () là một phương thức mở rộng.
SuperOli

2

Để mở rộng bài đăng của Daniels và nhận xét của anh ấy về khóa, vì dù sao khóa cũng được nhúng trong giá trị, bạn có thể sử dụng KeyValuePair<TKey, TValue>giá trị này làm giá trị. Lý do chính cho điều này là, nói chung, Khóa không nhất thiết phải có nguồn gốc trực tiếp từ giá trị.

Sau đó, nó sẽ trông như thế này:

public sealed class CustomDictionary<TKey, TValue>
  : KeyedCollection<TKey, KeyValuePair<TKey, TValue>>
{
  protected override TKey GetKeyForItem(KeyValuePair<TKey, TValue> item)
  {
    return item.Key;
  }
}

Để sử dụng điều này như trong ví dụ trước, bạn sẽ làm:

CustomDictionary<string, int> custDict = new CustomDictionary<string, int>();

custDict.Add(new KeyValuePair<string, int>("key", 7));

int valueByIndex = custDict[0].Value;
int valueByKey = custDict["key"].Value;
string keyByIndex = custDict[0].Key;

2

Một từ điển có thể không trực quan lắm để sử dụng chỉ mục để tham khảo nhưng, bạn có thể có các hoạt động tương tự với một mảng KeyValuePair :

Ví dụ. KeyValuePair<string, string>[] filters;


2

Bạn cũng có thể sử dụng SortedList và bản sao chung của nó. Hai lớp này và trong câu trả lời của Andrew Peters đã đề cập OrderedDixi là các lớp từ điển trong đó các mục có thể được truy cập theo chỉ mục (vị trí) cũng như theo khóa. Cách sử dụng các lớp này bạn có thể tìm thấy: ClassedList Class , SortedList Generic Class .


1

UserVoice của Visual Studio cung cấp một liên kết đến việc triển khai OrderedDixi chung bằng dotmore.

Nhưng nếu bạn chỉ cần lấy cặp khóa / giá trị theo chỉ mục và không cần lấy giá trị theo khóa, bạn có thể sử dụng một mẹo đơn giản. Khai báo một số lớp chung (tôi gọi nó là ListArray) như sau:

class ListArray<T> : List<T[]> { }

Bạn cũng có thể khai báo nó với các hàm tạo:

class ListArray<T> : List<T[]>
{
    public ListArray() : base() { }
    public ListArray(int capacity) : base(capacity) { }
}

Ví dụ: bạn đọc một số cặp khóa / giá trị từ một tệp và chỉ muốn lưu trữ chúng theo thứ tự chúng đã được đọc để có được chúng sau này theo chỉ mục:

ListArray<string> settingsRead = new ListArray<string>();
using (var sr = new StreamReader(myFile))
{
    string line;
    while ((line = sr.ReadLine()) != null)
    {
        string[] keyValueStrings = line.Split(separator);
        for (int i = 0; i < keyValueStrings.Length; i++)
            keyValueStrings[i] = keyValueStrings[i].Trim();
        settingsRead.Add(keyValueStrings);
    }
}
// Later you get your key/value strings simply by index
string[] myKeyValueStrings = settingsRead[index];

Như bạn có thể nhận thấy, bạn không nhất thiết chỉ là các cặp khóa / giá trị trong ListArray của bạn. Các mảng vật phẩm có thể có độ dài bất kỳ, như trong mảng răng cưa.

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.