ObjectBase.cs

Base Object Array Class

Updated: May-04,2021

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using System.Threading;
/// <summary>
///     Name: ObjectBase.cs
///     Date: January-1, 2021
///     Note: Map indexed object array.
/// </summary>
[DebuggerDisplay("Count = {" + nameof(Count) + "}")]
[Serializable]
public class ObjectBase : MonitorActionFuncWrapper, IEnumerable<object>
{
    [NonSerialized] private readonly SizeHelper32      _sh = new();
    [NonSerialized] private readonly SerializationInfo _sInfo;
    private volatile                 object[]          _array;
    [NonSerialized] private          HashAlgorithm     _hash;
    [NonSerialized] private volatile Set20B            _map;
    public volatile                  int               Count;
    public ObjectBase() : this(1024)
    {
    }
    public ObjectBase(int size, int bitWidth = 64)
    {
        BitWidth = bitWidth;
        SelectHashAlgorithm(bitWidth);
        _array = new object[size];
        _map   = new Set20B(size);
    }
    protected ObjectBase(SerializationInfo info, StreamingContext context)
    {
        _sInfo = info;
    }
    public object this[int index]
    {
        get
        {
            return Lock(this, () =>
            {
                var array = _array;
                if (index >= Count)
                    throw new Exception("Error: Index out of range.");
                return array[index];
            });
        }
    }
    public int BitWidth
    {
        get;
        private set;
    }
    public IEnumerator<object> GetEnumerator()
    {
        return Lock(this, () =>
        {
            return GetEnum();
        });
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return Lock(this, () =>
        {
            return GetEnum();
        });
    }
    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("BitWidth", BitWidth);
        info.AddValue("Array",    _array, typeof(object[]));
    }
    public void OnDeserialization(object sender)
    {
        if (_sInfo == null)
            return;
        BitWidth = _sInfo.GetInt32("BitWidth");
        Count    = 0;
        var array = (object[]) _sInfo.GetValue("Array", typeof(object[]));
        if (array == null)
            throw new Exception("Array cannot be null.");
        foreach (var t in array)
            Add(t);
    }
    public bool Add(object item)
    {
        return Lock(this, () =>
        {
            var (idx, exists) = GetIndex(item);
            if (idx >= _array.Length)
                Array.Resize(ref _array, (int) _sh.GetNewSize((ulong) _array.Length));
            if (exists == false)
            {
                _array[idx] = item;
                Interlocked.Increment(ref Count);
            }
            return exists == false;
        });
    }
    public void Clear()
    {
        Lock(this, () =>
        {
            _array = new object[_array.Length];
            _map   = new Set20B(_array.Length);
        });
    }
    private IEnumerator<object> GetEnum()
    {
        var ary = _array;
        var cnt = Count;
        for (var i = 0; i < cnt; i++)
            yield return ary[i];
    }
    public object[] ToArray()
    {
        return Lock(this, () =>
        {
            var newArray = new object[Count];
            using (var en = GetEnumerator())
            {
                var ptr = 0;
                while (en.MoveNext())
                {
                    var value = en.Current;
                    if (value == null)
                        break;
                    newArray[ptr++] = value;
                }
                return newArray;
            }
        });
    }
    public bool Contains(object item)
    {
        return Lock(this, () =>
        {
            return MapContains(item);
        });
    }
    private bool MapContains(object obj)
    {
        if (obj == null)
            throw new ArgumentNullException(nameof(obj));
        var bytes = obj.GetBytes();
        var hash  = _hash.ComputeHash(bytes, 0, bytes.Length);
        return _map.Contains(hash);
    }
    internal (int idx, bool exists) GetIndex(object obj, bool add = true)
    {
        return Lock(this, () =>
        {
            var bytes = obj.GetBytes();
            var hash  = new byte[0];
            hash = _hash.ComputeHash(bytes, 0, bytes.Length);
            var exists = false;
            if (!_map.Contains(hash))
            {
                if (add)
                    _map.Add(hash);
            }
            else
            {
                exists = true;
            }
            return (_map.FindEntry(hash), exists);
        });
    }
    private void SelectHashAlgorithm(int bitWidth)
    {
        BitWidth = bitWidth;
        switch (bitWidth)
        {
            case 64:
                _hash = new FNV1a64();
                break;
            case 128:
                _hash = new Murmur3();
                break;
            case 256:
                _hash = new SHA256Managed();
                break;
            case 512:
                _hash = new SHA512Managed();
                break;
            case 768:
                _hash = new SHA3Managed(768);
                break;
            default:
                throw new ArgumentException("Supported bit widths are: 64, 128, 256, 512 and 768 Bits.");
        }
    }
    internal class Set20B
    {
        private readonly IEqualityComparer<byte[]> _comparer;
        private          int                       _count;
        private          int[]                     _hashBuckets;
        private          Slot[]                    _slots;
        public Set20B(int size)
        {
            _comparer    = new ArrayComparer();
            _hashBuckets = new int[size];
            _slots       = new Slot[size];
            _count       = 0;
        }
        private IEnumerator<byte[]> GetEnumerator()
        {
            for (var i = 0; i < _count; i++)
                if (_slots[i].HashCode > 0)
                    yield return _slots[i].Value;
        }
        public bool Add(byte[] item)
        {
            var hashCode = _comparer.GetHashCode(item) & int.MaxValue;
            if (FindEntry(item, hashCode) != -1)
                return true;
            if (_count >= _slots.Length)
                Resize();
            var hashPos = hashCode % _hashBuckets.Length;
            _slots[_count].Next     = _hashBuckets[hashPos] - 1;
            _slots[_count].Value    = item;
            _slots[_count].HashCode = hashCode;
            _hashBuckets[hashPos]   = _count + 1;
            ++_count;
            return false;
        }
        private void Resize()
        {
            var newSize        = (_hashBuckets.Length + _hashBuckets.Length / 4 * 3) | 1;
            var newSlots       = new Slot[newSize];
            var newHashBuckets = new int[newSize];
            var newCount       = 0;
            var en             = GetEnumerator();
            while (en.MoveNext())
            {
                var item     = en.Current;
                var hashCode = _comparer.GetHashCode(item) & int.MaxValue;
                var hashPos  = hashCode % newHashBuckets.Length;
                newSlots[newCount].Next     = newHashBuckets[hashPos] - 1;
                newSlots[newCount].Value    = item;
                newSlots[newCount].HashCode = hashCode;
                newHashBuckets[hashPos]     = newCount + 1;
                ++newCount;
            }
            _slots       = newSlots;
            _hashBuckets = newHashBuckets;
            _count       = newCount;
        }
        private int FindEntry(byte[] item, int hashCode)
        {
            for (var position = _hashBuckets[hashCode % _hashBuckets.Length] - 1; position >= 0; position = _slots[position].Next)
                if (_slots[position].HashCode == hashCode && _comparer.Equals(_slots[position].Value, item))
                    return position;
            return -1;
        }
        public int FindEntry(byte[] item)
        {
            var hashCode = _comparer.GetHashCode(item) & int.MaxValue;
            for (var position = _hashBuckets[hashCode % _hashBuckets.Length] - 1; position >= 0; position = _slots[position].Next)
                if (_slots[position].HashCode == hashCode && _comparer.Equals(_slots[position].Value, item))
                    return position;
            return -1;
        }
        public bool Contains(byte[] item)
        {
            return FindEntry(item, _comparer.GetHashCode(item) & int.MaxValue) != -1;
        }
        private struct Slot
        {
            public int    HashCode;
            public int    Next;
            public byte[] Value;
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *