Trường hợp truy cập không nhạy cảm cho từ điển chung


244

Tôi có một ứng dụng sử dụng dlls được quản lý. Một trong những dll đó trả về một từ điển chung:

Dictionary<string, int> MyDictionary;  

Từ điển chứa các khóa với chữ hoa và chữ thường

Mặt khác, tôi nhận được một danh sách các khóa (chuỗi) tiềm năng tuy nhiên tôi không thể đảm bảo trường hợp này. Tôi đang cố gắng để có được giá trị trong từ điển bằng cách sử dụng các phím. Nhưng tất nhiên những điều sau đây sẽ thất bại vì tôi có một trường hợp không khớp:

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

Tôi đã hy vọng TryGetValue sẽ có một cờ trường hợp bỏ qua như được đề cập trong tài liệu MSDN , nhưng có vẻ như điều này không hợp lệ đối với các từ điển chung.

Có cách nào để có được giá trị của từ điển đó mà bỏ qua trường hợp quan trọng không? Có cách giải quyết nào tốt hơn là tạo một bản sao mới của từ điển với tham số StringComparer.OrdinalIgnoreCase thích hợp không?


Câu trả lời:


514

Không có cách nào để xác định StringComparerđiểm tại nơi bạn cố gắng nhận giá trị. Nếu bạn nghĩ về nó, "foo".GetHashCode()"FOO".GetHashCode()hoàn toàn khác biệt, do đó, không có cách nào hợp lý để bạn có thể thực hiện một cách phân biệt chữ hoa chữ thường trên bản đồ băm phân biệt chữ hoa chữ thường.

Tuy nhiên, bạn có thể tạo một từ điển không phân biệt chữ hoa chữ thường ở vị trí đầu tiên bằng cách sử dụng: -

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

Hoặc tạo một từ điển không phân biệt chữ hoa chữ thường với nội dung của từ điển phân biệt chữ hoa chữ thường (nếu bạn chắc chắn không có va chạm trường hợp nào): -

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

Từ điển mới này sau đó sử dụng GetHashCode()thực hiện trên StringComparer.OrdinalIgnoreCasenên comparer.GetHashCode("foo")comparer.GetHashcode("FOO")cung cấp cho bạn cùng giá trị.

Cách khác, nếu chỉ có một vài yếu tố trong từ điển và / hoặc bạn chỉ cần tra cứu một hoặc hai lần, bạn có thể coi từ điển gốc là một IEnumerable<KeyValuePair<TKey, TValue>>và chỉ lặp đi lặp lại từ đó: -

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

Hoặc nếu bạn thích, không có 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;
  }
}

Điều này giúp bạn tiết kiệm chi phí tạo cấu trúc dữ liệu mới, nhưng bù lại, chi phí tra cứu là O (n) thay vì O (1).


Quả thực nó có ý nghĩa. Cảm ơn rất nhiều vì lời giải thích.
TocToc

1
Không có lý do gì để giữ từ điển cũ xung quanh và khởi tạo từ điển mới vì bất kỳ va chạm trường hợp nào cũng sẽ khiến nó phát nổ. Nếu bạn biết bạn sẽ không bị va chạm thì bạn cũng có thể sử dụng trường hợp không nhạy cảm ngay từ đầu.
Rhys Bevilaqua

2
Đã mười năm tôi sử dụng .NET và giờ tôi mới hiểu ra điều này !! Tại sao bạn sử dụng Ordinal thay vì CurrentCARM?
Jordan

Vâng, nó phụ thuộc vào hành vi bạn muốn. Nếu người dùng đang cung cấp khóa thông qua UI (hoặc nếu bạn cần xem xét ví dụ: ss và ß bằng nhau) thì bạn sẽ cần sử dụng một nền văn hóa khác, nhưng cho rằng giá trị đang được sử dụng làm khóa cho hashmap đến từ một sự phụ thuộc bên ngoài, tôi nghĩ rằng 'OrdinalCARM' là một giả định hợp lý.
Iain Galloway

1
default(KeyValuePair<T, U>)không phải null- đó là một KeyValuePairnơi Key=default(T)Value=default(U). Vì vậy, bạn không thể sử dụng ?.toán tử trong ví dụ LINQ; bạn sẽ cần phải lấy FirstOrDefault()và sau đó (đối với trường hợp cụ thể này) kiểm tra xem Key == null.
Asherber

38

Đối với bạn, LINQers không bao giờ sử dụng trình tạo từ điển thông thường:

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

8

Nó không thanh lịch lắm nhưng trong trường hợp bạn không thể thay đổi việc tạo từ điển, và tất cả những gì bạn cần là một bản hack bẩn, thì sao đây:

var item = MyDictionary.Where(x => x.Key.ToLower() == MyIndex.ToLower()).FirstOrDefault();
    if (item != null)
    {
        TheValue = item.Value;
    }

13
hoặc chỉ thế này: Từ điển mới <string, int> (otherDict, StringComparer.CiverseCARMIgnoreCase);
Jordan

6
Theo "Thực tiễn tốt nhất để sử dụng chuỗi trong .NET Framework", hãy sử dụng ToUpperInvariantthay vì ToLower. msdn.microsoft.com/en-us/l Library / dd465121% 28v = vs.110% 29.aspx
Fred

Điều này tốt cho tôi, nơi tôi phải kiểm tra lại các phím một cách không nhạy cảm. Tôi sắp xếp hợp lý hơn một chútvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay

Tại sao không chỉ dict.Keys.Contains("bla", appropriate comparer)? Hơn nữa, bạn sẽ không nhận được null cho FirstOrDefault vì keyvaluepair trong C # là một cấu trúc.
nawfal
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.