[英]Datastructures, C#: ~O(1) lookup with range keys?
我有一個數據集。 此數據集將提供查找表。 給定一個數字,我應該能夠查找該數字的相應值。
數據集(比如說它的CSV)雖然有一些注意事項。 代替:
1,ABC
2,XYZ
3,LMN
數字是范圍( - “通過”,而不是減號):
1-3,ABC // 1, 2, and 3 = ABC
4-8,XYZ // 4, 5, 6, 7, 8 = XYZ
11-11,LMN // 11 = LMN
所有數字都是簽名的。 沒有范圍與其他范圍重疊。 有一些差距; 有一些未在數據集中定義的范圍(如上面最后一個片段中的9和10)。 `
我如何在C#中對此數據集進行建模,以便在保持內存占用率較低的同時獲得最高性能的查找?
我想出的唯一選擇就是過度消耗內存。 假設我的數據集是:
1-2,ABC
4-6,XYZ
然后我創建一個Dictionary<int,string>()
其鍵/值為:
1/ABC
2/ABC
4/XYZ
5/XYZ
6/XYZ
現在我有哈希性能查找,但哈希表中浪費了大量空間。
有任何想法嗎? 也許只是使用PLINQ而希望獲得良好的性能? ;)
您可以創建雙向間接查找:
Dictionary<int, int> keys; Dictionary<int, string> values;
然后像這樣存儲數據:
keys.Add(1, 1); keys.Add(2, 1); keys.Add(3, 1); //... keys.Add(11, 3); values.Add(1, "ABC"); //... values.Add(3, "LMN");
然后查看數據:
var abc = "ABC";
var def = "ABC";
Console.WriteLine(ReferenceEquals(abc, def));
我不確定用瑣碎的字符串可以節省多少內存,但是一旦超出“ABC”就應該有所幫助。
編輯
在丹濤的評論之后,我回去檢查了他的問題。 以下代碼:
var abc = "ABC"; var def = "ABC"; Console.WriteLine(ReferenceEquals(abc, def));
將“True”寫入控制台。 這意味着編譯器或運行時(澄清?)是保持對“ABC”的引用,並將其指定為兩個變量的值。
在閱讀了更多關於Intern
字符串的內容之后,如果你使用字符串文字來填充字典,或者Intern
計算字符串,實際上它將比原始字典花費更多的空間來實現我的建議。 如果您沒有使用Intern
ed字符串,那么我的解決方案應占用更少的空間。
最終編輯
如果你正確處理你的字符串,原始的Dictionary<int, string>
應該沒有多余的內存使用量Dictionary<int, string>
因為你可以將它們分配給一個變量,然后將該引用分配為值(或者,如果你需要,因為你可以Intern
他們)
只需確保您的分配代碼包含一個中間變量賦值:
while (thereAreStringsLeftToAssign) { var theString = theStringToAssign; foreach (var i in range) { strings.Add(i, theString); } }
如果您的字典將真正存儲各種鍵值,那么將所有可能范圍擴展為顯式鍵的方法將快速消耗比可能的更多內存。
您最好的選擇是使用支持二進制搜索(或其他O(log N)查找技術)的一些變體的數據結構。 這是一個指向.NET的通用RangeDictionary的鏈接 ,它在內部使用OrderedList,並具有O(log N)性能。
實現恆定時間O(1)查找需要將所有范圍擴展為顯式鍵。 這需要大量內存,並且當您需要拆分或插入新范圍時,實際上會降低性能。 這可能不是你想要的。
正如arootbeer在他的回答中提到的 ,以下代碼不會創建字符串“ABC”的多個實例; 相反,它實例化一個實例並將該實例的引用分配給dictionary
每個KeyValuePair<int, string>
:
var dictionary = new Dictionary<int, string>();
dictionary[0] = "ABC";
dictionary[1] = "ABC";
dictionary[2] = "ABC";
// etc.
好的,所以在字符串文字的情況下,每個鍵范圍只使用一個string
實例。 是否存在不會出現這種情況的情況 - 也就是說,您將為范圍內的每個鍵使用單獨的string
實例(這是我假設您在談到“過度消費”時所關注的記憶”)?
老實說,我不這么認為。 有些情況下可能會創建多個等效的字符串實例而沒有實習的好處,是的。 但我無法想象這些情況會影響你在這里嘗試做什么。
我的理由是:你想為不同的鍵范圍分配某些值,對吧? 所以,任何你定義這種一鍵程-值對的時候,你有一個單一的 價值和幾個 按鍵 。 單個部分使我懷疑你將擁有相同字符串的多個實例,除非它被定義為多個范圍的值。
為了說明:是的,以下代碼將實例化兩個相同的字符串:
string x = "ABC";
Console.Write("Type 'ABC' and press Enter: ");
string y = Console.ReadLine();
Console.WriteLine(Equals(x, y));
Console.WriteLine(ReferenceEquals(x, y));
假設用戶遵循指令並輸入“ABC”,上述程序輸出True
,然后輸出False
。 所以你可能會想,“啊,所以當一個字符串只在運行時提供時,它不會被實習!所以這可能是我的值可以重復的地方!”
但是......再說一遍: 我不這么認為 。 這一切都回到了這樣一個事實,即您將為一系列鍵分配單個值。 所以我們說你的價值來自用戶輸入; 然后你的代碼看起來像這樣:
var dictionary = new Dictionary<int, string>();
int start, count;
GetRange(out start, out count);
string value = GetValue();
foreach (int key in Enumerable.Range(start, count))
{
// Look, you're using the same string instance to assign
// to each key... how could it be otherwise?
dictionary[key] = value;
}
現在,如果您實際上更多地考慮LBushkin在其答案中提到的內容 - 您可能有大范圍,那么為該范圍內的每個鍵定義KeyValuePair<int, string>
是不切實際的(例如,如果你的范圍是1-1000000) - 那么我同意你最好使用某種基於二進制搜索查找的數據結構。 如果那更像你的情景,那就這么說,我很樂意在這方面提供更多的想法。 (或者你可以看看LBushkin已發布的鏈接。)
arootbeer有一個很好的解決方案,但你可能會覺得混淆不清楚。
另一種選擇是使用引用類型而不是字符串,以便指向相同的引用
class StringContainer {
public string Value { get; set; }
}
Dictionary<int, StringContainer> values;
var value1 = new StringContainer { Value = "ABC" };
values.Add(1, value1);
values.Add(2, value1);
它們都指向StringContainer的相同實例
編輯:感謝大家的評論。 此方法處理除string之外的值類型,因此它可能比給定示例更有用。 另外,我的理解是字符串並不總是以您期望的參考值的方式運行,但我可能是錯的。
使用平衡有序樹(或類似的)將范圍開始映射到范圍結束和數據。 對於非重疊范圍,這將很容易實現。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.