如何正确实现有限的排序列表

时间:2016-03-09 12:57:53

标签: c# .net multithreading sorting collections

我想实现一个固定大小的列表,它按顺序包含N个值。我写了一些代码,它有时会工作,但通常会产生不正确的结果。我尝试多次更改但没有任何成功。

对于我的小测试一切正常但是当我试图在big数据上测试它(不是很大,但是生成并且无法手动检查)我得到的结果不正确。

这里有什么不对?我甚至尝试用Add包裹lock方法,但它没有用,所以它不是多线程的情况:

public class LimitedSizeSortedList<T> : IEnumerable<T> 
{
    private readonly IComparer<T> _comparer;
    private readonly T[] _items;
    private readonly Dictionary<T, int> _itemsIndices;  
    public int Size => _items.Length;
    public int Count { get; private set; }
    public T Minimal => _items[Count - 1];

    public LimitedSizeSortedList(IComparer<T> comparer, IEqualityComparer<T> eqComparer, int size)
    {
        if (size < 1)
            throw new ArgumentException();
        _comparer = comparer;
        _items = new T[size];
       _itemsIndices = new Dictionary<T, int>(eqComparer);
    }

    public void Add(T item)
    {
        if (Count < Size)
            Count++;
        else if (IsBigger(Minimal, item))
            return;
        int alreadyExistingIndex;
        if (_itemsIndices.TryGetValue(item, out alreadyExistingIndex)) // already present in collection so we replace it 
        {
            _items[alreadyExistingIndex] = item;
            return;
        }

        int index = FindInsertIndex(item);
        _itemsIndices.Remove(Minimal);
        for (int i = _items.Length - 1; i > index; i--)
        {
            MoveToNewIndex(_items[i - 1], i);
        }
        MoveToNewIndex(item, index);
    }

    private void MoveToNewIndex(T item, int index)
    {
        _items[index] = item;
        _itemsIndices[item] = index;
    }

    private int FindInsertIndex(T item)
    {
        int lo = 0, hi = Count - 1;
        while (lo < hi)
        {
            int m = lo + (hi - lo)/2; // (hi + lo)/2 без переполнения
            if (IsBigger(_items[m], item))
                lo = m + 1;
            else
                hi = m - 1;
        }
        if (IsBigger(_items[lo], item))
            lo++;
        return lo;
    }

    private bool IsBigger(T x, T y)
    {
        return _comparer.Compare(x, y) > 0;
    }

    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < Count; i++)
        {
            yield return _items[i];
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    public void Clear()
    {
        Count = 0;
        _itemsIndices.Clear();
    }
}

测试代码:

[TestClass]
public class LimitedSizeSortedListTest
{
    [TestMethod]
    public void Insert()
    {
        int[] items = {4, 1, 7, 2, 3};
        var result = FromArray(items);

        bool sequenceEqual = result.SequenceEqual(items.OrderByDescending(x => x));
        Assert.IsTrue(sequenceEqual);
    }

    [TestMethod]
    public void Clear()
    {
        int[] items = { 4, 1, 7, 2, 3 };
        var result = FromArray(items);
        result.Clear();

        bool sequenceEqual = result.SequenceEqual(Enumerable.Empty<int>());
        Assert.IsTrue(sequenceEqual);
    }

    [TestMethod]
    public void TestMultiple()
    {
        KeyValuePair<char, int>[] items =
        {
            new KeyValuePair<char, int>('C', 1032508),
            new KeyValuePair<char, int>('E', 1609137),
            new KeyValuePair<char, int>('D', 1236174),
            new KeyValuePair<char, int>('_', 568439),
            new KeyValuePair<char, int>('\\', 287371),
            new KeyValuePair<char, int>('[', 1006805),
            new KeyValuePair<char, int>('A', 680143),
            new KeyValuePair<char, int>('L', 155975),
            new KeyValuePair<char, int>('I', 974892),
            new KeyValuePair<char, int>('F', 1197310),
            new KeyValuePair<char, int>('M', 1201940),
            new KeyValuePair<char, int>('B', 1820738),
            new KeyValuePair<char, int>('N', 640575),
            new KeyValuePair<char, int>('S', 1221010),
            new KeyValuePair<char, int>('R', 926485),
            new KeyValuePair<char, int>('U', 1742070),
            new KeyValuePair<char, int>('P', 602809),
            new KeyValuePair<char, int>('X', 886691),
            new KeyValuePair<char, int>('Y', 3020863),
            new KeyValuePair<char, int>('W', 1091417),
            new KeyValuePair<char, int>('Z', 834877),
            new KeyValuePair<char, int>('V', 82777),
            new KeyValuePair<char, int>('H', 920902),
            new KeyValuePair<char, int>('O', 288008),
            new KeyValuePair<char, int>('G', 616626)
        };

        var result = new LimitedSizeSortedList<KeyValuePair<char, int>>(FrequencyComparer.Instance, FrequencyComparer.Instance, 5);
        foreach (var pair in items)
        {
            result.Add(pair);
        }
        var expected = items.OrderByDescending(x => x.Value).Take(5).ToArray();

        bool sequenceEqual = result.SequenceEqual(expected);
        Assert.IsTrue(sequenceEqual);
    }


    private static LimitedSizeSortedList<T> FromArray<T>(T[] items) where T: IComparable<T>
    {
        var list1 = XLimitedSizeSortedList.FromComparable<T>(items.Length);
        foreach (T item in items)
        {
            list1.Add(item);
        }
        return list1;
    }
}

public class FrequencyComparer : IComparer<KeyValuePair<char, int>>, IEqualityComparer<KeyValuePair<char, int>>
{
    public int Compare(KeyValuePair<char, int> x, KeyValuePair<char, int> y)
    {
        return x.Value.CompareTo(y.Value);
    }

    public bool Equals(KeyValuePair<char, int> x, KeyValuePair<char, int> y)
    {
        return x.Key == y.Key;
    }

    public int GetHashCode(KeyValuePair<char, int> obj)
    {
        return obj.Key.GetHashCode();
    }
    public static FrequencyComparer Instance { get; } = new FrequencyComparer();
}

1 个答案:

答案 0 :(得分:1)

假设您不想要重复(检查代码),我认为您最好使用SortedSet<T>来实现此功能。

这看起来像这样:

public sealed class LimitedSizeSortedList<T>: IEnumerable<T>
{
    readonly int _size;
    readonly SortedSet<T> _items;

    public LimitedSizeSortedList(IComparer<T> comparer, int size)
    {
        if (size < 1)
            throw new ArgumentException();

        _size  = size;
        _items = new SortedSet<T>(comparer);
    }

    public void Add(T item)
    {
        if (_items.Contains(item))
            return;

        if (_items.Count < _size)
        {
            _items.Add(item);
            return;
        }

        if (_items.Comparer.Compare(item, _items.Min) <= 0)
            return;

        _items.Remove(_items.Min);
        _items.Add(item);
    }

    public void Clear()
    {
        _items.Clear();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}