Akses case sensitif untuk kamus umum

244

Saya memiliki aplikasi yang menggunakan dll yang dikelola. Salah satu dari itu dll mengembalikan kamus generik:

Dictionary<string, int> MyDictionary;  

Kamus berisi tombol dengan huruf besar dan kecil.

Di sisi lain saya mendapatkan daftar kunci potensial (string) namun saya tidak dapat menjamin kasus ini. Saya mencoba mendapatkan nilai dalam kamus menggunakan tombol. Tetapi tentu saja hal berikut ini akan gagal karena saya memiliki kasus ketidakcocokan:

bool Success = MyDictionary.TryGetValue( MyIndex, out TheValue );  

Saya berharap TryGetValue akan memiliki flag kasus abaikan seperti yang disebutkan dalam dokumen MSDN , tetapi tampaknya ini tidak valid untuk kamus umum.

Apakah ada cara untuk mendapatkan nilai dari kamus itu dengan mengabaikan case utama? Apakah ada solusi yang lebih baik daripada membuat salinan kamus baru dengan parameter StringComparer.OrdinalIgnoreCase yang tepat ?

TocToc
sumber

Jawaban:

514

Tidak ada cara untuk menentukan StringComparerpada titik di mana Anda mencoba untuk mendapatkan nilai. Jika Anda memikirkannya, "foo".GetHashCode()dan"FOO".GetHashCode() sama sekali berbeda sehingga tidak ada cara yang masuk akal Anda bisa menerapkan get-case sensitif pada peta hash yang case-sensitive.

Anda dapat, bagaimanapun, membuat kamus case-sensitive di tempat pertama menggunakan: -

var comparer = StringComparer.OrdinalIgnoreCase;
var caseInsensitiveDictionary = new Dictionary<string, int>(comparer);

Atau buat kamus case-sensitive baru dengan isi dari kamus case-sensitive yang ada (jika Anda yakin tidak ada tabrakan kasus): -

var oldDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var newDictionary = new Dictionary<string, int>(oldDictionary, comparer);

Kamus baru ini kemudian menggunakan GetHashCode()implementasinya StringComparer.OrdinalIgnoreCasejadi comparer.GetHashCode("foo")dan comparer.GetHashcode("FOO")memberi Anda nilai yang sama.

Sebagai alternatif, jika hanya ada beberapa elemen dalam kamus, dan / atau Anda hanya perlu mencari sekali atau dua kali, Anda dapat memperlakukan kamus asli sebagai IEnumerable<KeyValuePair<TKey, TValue>>dan hanya mengulanginya: -

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
var value = myDictionary.FirstOrDefault(x => String.Equals(x.Key, myKey, comparer)).Value;

Atau jika Anda suka, tanpa LINQ: -

var myKey = ...;
var myDictionary = ...;
var comparer = StringComparer.OrdinalIgnoreCase;
int? value;
foreach (var element in myDictionary)
{
  if (String.Equals(element.Key, myKey, comparer))
  {
    value = element.Value;
    break;
  }
}

Ini menghemat biaya pembuatan struktur data baru, tetapi sebagai imbalannya biaya pencarian adalah O (n) bukan O (1).

Iain Galloway
sumber
Memang masuk akal. Terima kasih banyak atas penjelasannya.
TocToc
1
Tidak ada alasan untuk menjaga kamus lama dan instantiate yang baru karena setiap kasus tabrakan akan menyebabkannya meledak. Jika Anda tahu Anda tidak akan mendapatkan tabrakan maka Anda mungkin juga menggunakan huruf besar-kecil dari awal.
Rhys Bevilaqua
2
Sudah sepuluh tahun saya menggunakan NET. Dan sekarang saya baru tahu ini !! Mengapa Anda menggunakan Ordinal alih-alih CurrentCulture?
Jordan
Yah, itu tergantung pada perilaku yang Anda inginkan. Jika pengguna menyediakan kunci melalui UI (atau jika Anda perlu mempertimbangkan misalnya ss dan ß sama) maka Anda harus menggunakan budaya yang berbeda, tetapi mengingat bahwa nilai tersebut digunakan sebagai kunci untuk hashmap yang berasal dari ketergantungan eksternal, saya pikir 'OrdinalCulture' adalah asumsi yang masuk akal.
Iain Galloway
1
default(KeyValuePair<T, U>)bukan null- itu adalah di KeyValuePairmana Key=default(T)dan Value=default(U). Jadi Anda tidak dapat menggunakan ?.operator dalam contoh LINQ; Anda harus mengambil FirstOrDefault()dan kemudian (untuk kasus khusus ini) periksa untuk melihat apakah Key == null.
asherber
38

Bagi Anda para LINQer di luar sana yang tidak pernah menggunakan konstruktor kamus reguler:

myCollection.ToDictionary(x => x.PartNumber, x => x.PartDescription, StringComparer.OrdinalIgnoreCase)
Derpy
sumber
8

Ini tidak terlalu elegan tetapi jika Anda tidak dapat mengubah pembuatan kamus, dan yang Anda butuhkan adalah hack kotor, bagaimana dengan ini:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }
Shoham
sumber
13
atau hanya ini: Kamus baru <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Jordan
6
Sesuai "Praktik Terbaik untuk Menggunakan Strings dalam .NET Framework" gunakan ToUpperInvariantsebagai gantinya ToLower. msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspx
Fred
Ini bagus untuk saya, di mana saya harus secara retrospektif memeriksa kunci dengan cara yang tidak sensitif. Saya merampingkannya sedikit lebihvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay
Kenapa tidak adil dict.Keys.Contains("bla", appropriate comparer)? Selain itu, Anda tidak akan mendapatkan null untuk FirstOrDefault karena keyvaluepair di C # adalah sebuah struct.
nawfal