การเข้าถึงแบบคำนึงถึงขนาดตัวพิมพ์เล็กและใหญ่สำหรับพจนานุกรมทั่วไป


244

ฉันมีแอปพลิเคชันที่ใช้ Dll ที่ได้รับการจัดการ หนึ่งใน Dll เหล่านั้นส่งคืนพจนานุกรมทั่วไป:

Dictionary<string, int> MyDictionary;  

พจนานุกรมประกอบด้วยปุ่มที่มีตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก

ในอีกด้านหนึ่งฉันได้รับรายการของคีย์ที่อาจเกิดขึ้น (สตริง) แต่ฉันไม่สามารถรับประกันกรณี ฉันพยายามรับค่าในพจนานุกรมโดยใช้ปุ่ม แต่แน่นอนว่าสิ่งต่อไปนี้จะล้มเหลวเนื่องจากฉันมีขนาดตัวพิมพ์ไม่ตรงกัน:

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

ฉันหวังว่า TryGetValue จะมีการตั้งค่าสถานะตัวพิมพ์เล็ก - ใหญ่เหมือนที่กล่าวไว้ในเอกสาร MSDNแต่ดูเหมือนว่านี่จะไม่ถูกต้องสำหรับพจนานุกรมทั่วไป

มีวิธีการรับค่าของพจนานุกรมที่ละเว้นกรณีสำคัญหรือไม่ มีวิธีแก้ปัญหาที่ดีกว่าการสร้างสำเนาใหม่ของพจนานุกรมด้วยพารามิเตอร์StringComparer.OrdinalIgnoreCaseที่เหมาะสมหรือไม่


คำตอบ:


514

ไม่มีวิธีระบุ a StringComparerณ จุดที่คุณพยายามรับค่า หากคุณคิดเกี่ยวกับมัน"foo".GetHashCode()และ"FOO".GetHashCode()แตกต่างกันโดยสิ้นเชิงดังนั้นจึงไม่มีวิธีที่เหมาะสมที่คุณสามารถใช้ตัวพิมพ์เล็กและใหญ่ได้

อย่างไรก็ตามคุณสามารถสร้างพจนานุกรมตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ได้ตั้งแต่แรกโดยใช้: -

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

หรือสร้างพจนานุกรมตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ใหม่ที่มีเนื้อหาของพจนานุกรมตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ที่มีอยู่ (ถ้าคุณแน่ใจว่าไม่มีการชนตัวพิมพ์ใหญ่และตัวพิมพ์ใหญ่): -

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

พจนานุกรมใหม่นี้จากนั้นจะใช้GetHashCode()การดำเนินการเกี่ยวกับการStringComparer.OrdinalIgnoreCaseเพื่อให้comparer.GetHashCode("foo")และcomparer.GetHashcode("FOO")ให้คุณมีค่าเท่ากัน

อีกทางเลือกหนึ่งหากมีเพียงไม่กี่องค์ประกอบในพจนานุกรมและ / หรือคุณต้องการค้นหาเพียงครั้งเดียวหรือสองครั้งคุณสามารถถือว่าพจนานุกรมต้นฉบับเป็นIEnumerable<KeyValuePair<TKey, TValue>>และเพียงแค่ทำซ้ำมัน: -

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

หรือหากคุณต้องการโดยไม่มี 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;
  }
}

สิ่งนี้ช่วยให้คุณประหยัดค่าใช้จ่ายในการสร้างโครงสร้างข้อมูลใหม่ แต่ในทางกลับกันค่าใช้จ่ายของการค้นหาคือ O (n) แทนที่จะเป็น O (1)


แน่นอนมันทำให้รู้สึก ขอบคุณมากสำหรับคำอธิบาย
TocToc

1
ไม่มีเหตุผลที่จะเก็บพจนานุกรมเก่าไว้รอบ ๆ และสร้างอินสแตนซ์ใหม่เนื่องจากการชนตัวพิมพ์เล็กและใหญ่จะทำให้เกิดการระเบิด หากคุณรู้ว่าคุณจะไม่ได้รับการชนคุณก็อาจใช้ตัวพิมพ์เล็กและตัวพิมพ์เล็กตั้งแต่ต้น
Rhys Bevilaqua

2
เป็นเวลาสิบปีแล้วที่ฉันใช้. NET และตอนนี้ฉันก็รู้เรื่องนี้แล้ว !! ทำไมคุณถึงใช้ Ordinal แทน CurrentCulture
Jordan

มันขึ้นอยู่กับพฤติกรรมที่คุณต้องการ หากผู้ใช้ระบุคีย์ผ่าน UI (หรือหากคุณจำเป็นต้องพิจารณาเช่น ss และßเท่ากัน) จากนั้นคุณจะต้องใช้วัฒนธรรมที่แตกต่าง แต่ให้ค่านั้นถูกใช้เป็นกุญแจสำหรับ hashmap ที่มาจาก การพึ่งพาจากภายนอกฉันคิดว่า 'OrdinalCulture' เป็นข้อสมมติที่สมเหตุสมผล
Iain Galloway

1
default(KeyValuePair<T, U>)ไม่null- มันเป็นKeyValuePairที่และKey=default(T) Value=default(U)ดังนั้นคุณไม่สามารถใช้?.โอเปอเรเตอร์ในตัวอย่าง LINQ ได้ คุณจะต้องคว้าFirstOrDefault()แล้ว (สำหรับกรณีนี้โดยเฉพาะ) Key == nullตรวจสอบเพื่อดูว่า
asherber

38

สำหรับคุณที่มี LINQers ที่ไม่เคยใช้ตัวสร้างพจนานุกรมปกติ:

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

8

มันไม่ได้สวยงามมาก แต่ในกรณีที่คุณไม่สามารถเปลี่ยนการสร้างพจนานุกรมและสิ่งที่คุณต้องการก็คือแฮ็คที่สกปรกวิธีการเกี่ยวกับสิ่งนี้:

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

13
หรือเพียงแค่นี้: พจนานุกรมใหม่ <string, int> (otherDict, StringComparer.CurrentCultureIgnoreCase);
Jordan

6
เป็นต่อ "วิธีปฏิบัติที่ดีที่สุดสำหรับการใช้สายใน .NET Framework" ใช้แทนToUpperInvariant msdn.microsoft.com/en-us/library/dd465121%28v=vs.110%29.aspxToLower
Fred

นี่เป็นสิ่งที่ดีสำหรับฉันที่ฉันต้องตรวจสอบกุญแจย้อนหลังในลักษณะที่ไม่ตอบสนอง ฉันปรับปรุงมันอีกเล็กน้อยvar item = MyDictionary.FirstOrDefault(x => x.Key.ToUpperInvariant() == keyValueToCheck.ToUpperInvariant());
Jay

ทำไมไม่เพียง dict.Keys.Contains("bla", appropriate comparer)? นอกจากนี้คุณจะไม่ได้รับค่า null สำหรับ FirstOrDefault เนื่องจาก keyvaluepair ใน C # เป็นโครงสร้าง
nawfal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.