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);
    }
}
posted @ 2024-12-31 16:37  惊惊  阅读(37)  评论(0编辑  收藏  举报