cad.net ListMap插入序+LRU+LFU
插入序的Map
创建一个既有O(1)查找速度,也有顺序遍历的结构.
也就是JS和Python的字典结构,在C#上面需要通过两个结构进行.
让我们封装它们作为有序Map吧.
1,如果map用list的索引作为value,
那么list移除中间成员后,list是数组,后面成员会全部向前移动,
所以map的value记录的索引要更新,并且是遍历list移除位置后面的全部.
所以直接用成员指针记录.
2,如果用值类型struct作为value,例如Dictionary的KeyValuePair.
那么map可以很简单获取value并修改,
但是list获取则需要遍历,因为值类型拷贝了.
所以我才用了引用类型class作为value,
这样修改map.Value的值另一个list的也被改好.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
// 定义一个类来封装键值对
public class MapItem<TKey, TValue> {
public TKey Key { get; set; }
public TValue Value { get; set; }
public MapItem(TKey key, TValue value) {
Key = key;
Value = value;
}
}
public class ListMap<TKey, TValue> : IEnumerable<MapItem<TKey, TValue>>
where TKey : IEquatable<TKey>
where TValue : IEquatable<TValue> {
private readonly Dictionary<TKey, MapItem<TKey, TValue>> _cacheMap = new Dictionary<TKey, MapItem<TKey, TValue>>();
private readonly List<MapItem<TKey, TValue>> _cacheList = new List<MapItem<TKey, TValue>>();
public int Count => _cacheList.Count;
public TValue[] Values => _cacheList.Select(kv => kv.Value).ToArray();
public ListMap(){}
public ListMap(Dictionary<TKey, TValue> dict) {
foreach(var item in dict) {
Add(item.Key, item.Value);
}
}
public ListMap(SortedDictionary<TKey, TValue> dict) {
foreach(var item in dict) {
Add(item.Key, item.Value);
}
}
public ListMap(SortedList<TKey, TValue> dict) {
foreach(var item in dict) {
Add(item.Key, item.Value);
}
}
// 查找
public bool ContainsKey(TKey key) {
return _cacheMap.ContainsKey(key);
}
public MapItem<TKey, TValue> Find(TValue value) {
return _cacheList.Find(mapItem =>
mapItem.Value.GetHashCode() == value.GetHashCode()
&& mapItem.Value.Equals(value));
}
public bool TryGetValue(TKey key, out TValue value) {
if (_cacheMap.TryGetValue(key, out var mapItem)) {
value = mapItem.Value;
return true;
}
value = default(TValue);
return false;
}
// 尾加入
public MapItem<TKey, TValue> Add(TKey key, TValue value) {
if (_cacheMap.ContainsKey(key))
throw new ArgumentException($"已经存在key: {key}");
var item = new MapItem<TKey, TValue>(key, value);
_cacheList.Add(item);
_cacheMap.Add(key, item);
return item;
}
// 如果键已存在,更新值
public MapItem<TKey, TValue> AddOrUpdate(TKey key, TValue value) {
if (_cacheMap.TryGetValue(key, out var mapItem)) {
mapItem.Value = value; // 两个容器引用同一个
return mapItem;
}
return Add(key, value);
}
#if 记录移除
// 这个方案不行啊,索引计算有难度啊...
// 记录移除的索引
SortedSet<int> _setRemove = new SortedSet<int>();
void SetRemoveClear() {
var indexs = _setRemove.ToArray();
for(int i = indexs.Length-1; i >=0; i--) {
_cacheList.RemoveAt(indexs[i]);
}
_setRemove.Clear();
}
#endif
// 移除元素
public bool Remove(TKey key) {
if (_cacheMap.TryGetValue(key, out var mapItem)) {
_cacheMap.Remove(key);
int ix = _cacheList.IndexOf(mapItem);
#if 记录移除
_setRemove.Add(ix);
#else
_cacheList.RemoveAt(ix);
#endif
return true;
}
return false;
}
public void RemoveValues(TValue value) {
#if 记录移除
SetRemoveClear();
#endif
for(int i = _cacheList.Count-1; i >=0; i--) {
var mapItem = _cacheList[i];
if (mapItem.Value.GetHashCode() == value.GetHashCode()
&& mapItem.Value.Equals(value)) {
_cacheMap.Remove(_cacheList[i].Key);
_cacheList.RemoveAt(i);
}
}
}
// 泛型版本的GetEnumerator
public IEnumerator<MapItem<TKey, TValue>> GetEnumerator() {
#if 记录移除
SetRemoveClear();
#endif
return _cacheList.GetEnumerator();
}
// 非泛型版本的GetEnumerator,它委托给泛型版本的GetEnumerator
IEnumerator IEnumerable.GetEnumerator() {
return this.GetEnumerator();
}
// 插入
public MapItem<TKey, TValue> Insert(int index, TKey key, TValue value) {
if (index < 0 || index >= _cacheList.Count)
throw new IndexOutOfRangeException("索引超出范围");
if (_cacheMap.ContainsKey(key))
throw new ArgumentException($"已经存在key: {key}");
var item = new MapItem<TKey, TValue>(key, value);
_cacheMap.Add(key, item);
_cacheList.Insert(index, item);
return item;
}
// LRU结构
public MapItem<TKey, TValue> InsertOrUpdate(int index, TKey key, TValue value) {
if (index < 0 || index >= _cacheList.Count)
throw new IndexOutOfRangeException("索引超出范围");
if (_cacheMap.TryGetValue(key, out var mapItem)) {
mapItem.Value = value; // 两个容器引用同一个
// 原有list成员移动到index位置,相等已经由map处理了就不需要再处理.
int ix = _cacheList.IndexOf(mapItem);
_cacheList.RemoveAt(ix);
if (ix < index) {
_cacheList.Insert(index-1, mapItem);
}
else if (ix > index) {
_cacheList.Insert(index, mapItem);
}
return mapItem;
}
return Insert(index, key, value);
}
public MapItem<TKey, TValue> this[int index] {
get {
if (index < 0 || index >= _cacheList.Count)
throw new IndexOutOfRangeException("索引超出范围");
return _cacheList[index];
}
set {
if (index < 0 || index >= _cacheList.Count)
throw new IndexOutOfRangeException("索引超出范围");
_cacheList[index] = value;
_cacheMap[value.Key] = value;
}
}
}
LRU最久使用移除
public class LruMap<TKey, TValue> where TKey : IEquatable<TKey> where TValue : IEquatable<TValue> {
private ListMap<TKey, TValue> cacheMap;
private int capacity;
public LruMap(int capacity = 0) {
this.capacity = capacity;
cacheMap = new ListMap<TKey, TValue>();
}
// 获取元素
// 同时更新其为最近使用,将其移到列表末尾表示最近使用
public TValue Get(TKey key) {
if (cacheMap.TryGetValue(key, out TValue value)) {
cacheMap.InsertOrUpdate(cacheMap.Count - 1, key, value);
return value;
}
return default(TValue);
}
// 添加元素
// 超过容量则移除头部(最久未使用),然后新元素加入尾部
// 感觉这个虽然大部分场景没有性能问题.
// 但是从头部移除成员会引起集体移动,
// 虽然底层有SIMD帮助移动,但是还是存在风险.
// C#Queue也是利用有效头部记录方式来作为移除.
// https://referencesource.microsoft.com/#System/compmod/system/collections/generic/queue.cs,aa3beab99b2e0db2
// 1,用int作为有效首部记录,这样移除头部key只是记录+1.
// 每次索引就需要+标记,但是移除中间呢?依然存在高频移动这种问题.
// 2,用HashSet<int>记录要移除的索引,这样稀疏到阈值时候才进行处理,但是这样索引器有难度.
// 3,或者双向数组,两个数组从中间向两边生长.
// 4,换成链表,移除方便了,但是失去了遍历速度...IFox内部有
// 未来再完善吧
public MapItem<TKey, TValue> Put(TKey key, TValue value) {
if (cacheMap.Count > capacity) {
cacheMap.Remove(cacheMap[0].Key);
}
return cacheMap.Add(key, value);
}
}
LFU最少使用移除
using System;
using System.Collections.Generic;
// 定义泛型类,TKey表示键的类型,TValue表示值的类型
public class LfuMap<TKey, TValue>{
private int capacity;
private Dictionary<TKey, TValue> keyValueDict; // 存储键值对
private Dictionary<TKey, int> keyFrequencyDict; // 存储键对应的访问频率
private Dictionary<int, HashSet<TKey>> frequencyKeysDict; // 存储每个频率对应的键集合
private int minFrequency;
public LfuMap(int capacity = 0){
this.capacity = capacity;
keyValueDict = new Dictionary<TKey, TValue>();
keyFrequencyDict = new Dictionary<TKey, int>();
frequencyKeysDict = new Dictionary<int, HashSet<TKey>>();
minFrequency = 0;
}
public TValue Get(TKey key){
if (!keyValueDict.ContainsKey(key)){
return default(TValue);
}
int currentFrequency = keyFrequencyDict[key];
keyFrequencyDict[key] = currentFrequency + 1;
// 从当前频率对应的键集合中移除该键
frequencyKeysDict[currentFrequency].Remove(key);
// 如果当前频率对应的键集合为空,且当前最小频率就是这个频率,更新最小频率
if (frequencyKeysDict[currentFrequency].Count == 0
&& minFrequency == currentFrequency){
minFrequency++;
}
// 将键添加到新频率对应的键集合中,如果集合不存在则创建
if (!frequencyKeysDict.ContainsKey(currentFrequency + 1)){
frequencyKeysDict[currentFrequency + 1] = new HashSet<TKey>();
}
frequencyKeysDict[currentFrequency + 1].Add(key);
return keyValueDict[key];
}
public void Put(TKey key, TValue value){
if (capacity <= 0){
return;
}
if (keyValueDict.ContainsKey(key)){
keyValueDict[key] = value;
Get(key);
return;
}
if (keyValueDict.Count >= capacity){
// 获取最小频率对应的键集合
HashSet<TKey> keysToRemove = frequencyKeysDict[minFrequency];
// 随便选一个键移除(这里简单取第一个)
TKey keyToEvict = keysToRemove.First();
keysToRemove.Remove(keyToEvict);
keyValueDict.Remove(keyToEvict);
keyFrequencyDict.Remove(keyToEvict);
}
keyValueDict[key] = value;
keyFrequencyDict[key] = 1;
minFrequency = 1;
if (!frequencyKeysDict.ContainsKey(1)){
frequencyKeysDict[1] = new HashSet<TKey>();
}
frequencyKeysDict[1].Add(key);
}
}