Bit Array

P
// Original Author: Christian Bender
// Class: BitArray
//
// implements IComparable, ICloneable, IEnumerator, IEnumerable
//
// This class implements a bit-array and provides some
// useful functions/operations to deal with this type of
// data structure. You see a overview about the functionality, below.
//
//
// Overview
//
// Constructor (N : int)
// The constructor receives a length (N) of the to create bit-field.
//
// Constructor (sequence : string)
// setups the array with the input sequence.
// assumes: the sequence may only be allowed contains onese or zeros.
//
// Constructor (bits : bool[] )
// setups the bit-field with the input array.
//
// Compile(sequence : string)
// compiles a string sequence of 0's and 1's in the inner structure.
// assumes: the sequence may only be allowed contains onese or zeros.
//
// Compile (number : int)
// compiles a positive integer number in the inner data structure.
//
// Compile (number : long)
// compiles a positive long integer number in the inner data structure.
//
// ToString ()
// returns a string representation of the inner structure.
// The returned string is a sequence of 0's and 1's.
//
// Length : int
// Is a property that returns the length of the bit-field.
//
// Indexer : bool
// indexer for selecting the individual bits of the bit array.
//
// NumberOfOneBits() : int
// returns the number of One-bits.
//
// NumberOfZeroBits() : int
// returns the number of Zero-Bits.
//
// EvenParity() : bool
// returns true if parity is even, otherwise false.
//
// OddParity() : bool
// returns true if parity is odd, otherwise false.
//
// ToInt64() : long
// returns a long integer representation of the bit-array.
// assumes: the bit-array length must been smaller or equal to 64 bit.
//
// ToInt32() : int
// returns a integer representation of the bit-array.
// assumes: the bit-array length must been smaller or equal to 32 bit.
//
// ResetField() : void
// sets all bits on false.
//
// SetAll(flag : bool) : void
// sets all bits on the value of the flag.
//
// GetHashCode() : int
// returns hash-code (ToInt32())
//
// Equals (other : Object) : bool
// returns true if there inputs are equal otherwise false.
// assumes: the input bit-arrays must have same length.
//
// CompareTo (other : Object) : int  (interface IComparable)
// output:  0 - if the bit-arrays a equal.
// -1 - if this bit-array is smaller.
// 1 - if this bit-array is greater.
// assumes: bit-array lentgh must been smaller or equal to 64 bit
//
// Clone () : object
// returns a copy of this bit-array
//
// Current : object
// returns the current selected bit.
//
// MoveNext() : bool
// purpose: increases the position of the enumerator
// returns true if 'position' successful increased otherwise false.
//
// Reset() : void
// resets the position of the enumerator.
//
// GetEnumerator() : IEnumerator
// returns a enumerator for this BitArray-object.
//
// Operations:
//
// & bitwise AND
// | bitwise OR
// ~ bitwise NOT
// >> bitwise shift right
// >> bitwise shift left
// ^ bitwise XOR
//
// Each operation (above) returns a new BitArray-object.
//
// == equal operator. : bool
// returns true if there inputs are equal otherwise false.
// assumes: the input bit-arrays must have same length.
//
// != not-equal operator : bool
// returns true if there inputs aren't equal otherwise false.
// assumes: the input bit-arrays must have same length.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DataStructures;

/// <summary>
///     This class implements a bit-array and provides some
///     useful functions/operations to deal with this type of
///     data structure.
/// </summary>
public sealed class BitArray : ICloneable, IEnumerator<bool>, IEnumerable<bool>
{
    private readonly bool[] field; // the actual bit-field
    private int position = -1; // position for enumerator

    /// <summary>
    ///     Initializes a new instance of the <see cref="BitArray" /> class.
    ///     setups the array with false-values.
    /// </summary>
    /// <param name="n">length of the array.</param>
    public BitArray(int n)
    {
        if (n < 1)
        {
            field = new bool[0];
        }

        field = new bool[n];
    }

    /// <summary>
    ///     Initializes a new instance of the <see cref="BitArray" /> class.
    ///     Setups the array with the input sequence.
    ///     purpose: Setups the array with the input sequence.
    ///     assumes: sequence must been greater or equal to 1.
    ///     the sequence may only contain ones or zeros.
    /// </summary>
    /// <param name="sequence">A string sequence of 0's and 1's.</param>
    public BitArray(string sequence)
    {
        // precondition I
        if (sequence.Length <= 0)
        {
            throw new ArgumentException("Sequence must been greater than or equal to 1");
        }

        // precondition II
        ThrowIfSequenceIsInvalid(sequence);

        field = new bool[sequence.Length];
        Compile(sequence);
    }

    /// <summary>
    ///     Initializes a new instance of the <see cref="BitArray" /> class.
    ///     Setups the bit-array with the input array.
    /// </summary>
    /// <param name="bits">A boolean array of bits.</param>
    public BitArray(bool[] bits) => field = bits;

    /// <summary>
    ///     Gets the length of the current bit array.
    /// </summary>
    private int Length => field.Length;

    /// <summary>
    ///     Gets element given an offset.
    /// </summary>
    /// <param name="offset">Position.</param>
    /// <returns>Element on array.</returns>
    public bool this[int offset]
    {
        get => field[offset];
        private set => field[offset] = value;
    }

    /// <summary>
    ///     Returns a copy of the current bit-array.
    /// </summary>
    /// <returns>Bit-array clone.</returns>
    public object Clone()
    {
        var theClone = new BitArray(Length);

        for (var i = 0; i < Length; i++)
        {
            theClone[i] = field[i];
        }

        return theClone;
    }

    /// <summary>
    ///     Gets a enumerator for this BitArray-Object.
    /// </summary>
    /// <returns>Returns a enumerator for this BitArray-Object.</returns>
    public IEnumerator<bool> GetEnumerator() => this;

    /// <summary>
    ///     Gets a enumerator for this BitArray-Object.
    /// </summary>
    /// <returns>Returns a enumerator for this BitArray-Object.</returns>
    IEnumerator IEnumerable.GetEnumerator() => this;

    /// <summary>
    ///     Gets a value indicating whether the current bit of the array is set.
    /// </summary>
    public bool Current => field[position];

    /// <summary>
    ///     Gets a value indicating whether the current bit of the array is set.
    /// </summary>
    object IEnumerator.Current => field[position];

    /// <summary>
    ///     MoveNext (for interface IEnumerator).
    /// </summary>
    /// <returns>Returns True if 'position' successful increased; False otherwise.</returns>
    public bool MoveNext()
    {
        if (position + 1 >= field.Length)
        {
            return false;
        }

        position++;
        return true;
    }

    /// <summary>
    ///     Resets the position of the enumerator.
    ///     Reset (for interface IEnumerator).
    /// </summary>
    public void Reset() => position = -1;

    /// <summary>
    ///     Disposes object, nothing to dispose here though.
    /// </summary>
    public void Dispose()
    {
        // Done
    }

    /// <summary>
    ///     Returns a bit-array that represents the bit by bit AND (&amp;).
    ///     Assumes arrays have the same length.
    /// </summary>
    /// <param name="one">First bit-array.</param>
    /// <param name="two">Second bit-array.</param>
    /// <returns>bit-array.</returns>
    public static BitArray operator &(BitArray one, BitArray two)
    {
        var sequence1 = one.ToString();
        var sequence2 = two.ToString();
        var result = new StringBuilder();
        var tmp = new StringBuilder();

        // for scaling of same length.
        if (one.Length != two.Length)
        {
            int difference;
            if (one.Length > two.Length)
            {
                // one is greater
                difference = one.Length - two.Length;

                // fills up with 0's
                for (var i = 0; i < difference; i++)
                {
                    tmp.Append('0');
                }

                tmp.Append(two);
                sequence2 = tmp.ToString();
            }
            else
            {
                // two is greater
                difference = two.Length - one.Length;

                // fills up with 0's
                for (var i = 0; i < difference; i++)
                {
                    tmp.Append('0');
                }

                tmp.Append(one);
                sequence1 = tmp.ToString();
            }
        } // end scaling

        var len = one.Length > two.Length ? one.Length : two.Length;
        var ans = new BitArray(len);

        for (var i = 0; i < one.Length; i++)
        {
            result.Append(sequence1[i].Equals('1') && sequence2[i].Equals('1') ? '1' : '0');
        }

        ans.Compile(result.ToString().Trim());

        return ans;
    }

    /// <summary>
    ///     Returns a bit-array that represents the bit by bit OR.
    ///     Assumes arrays have the same length.
    /// </summary>
    /// <param name="one">First bit-array.</param>
    /// <param name="two">Second bit-array.</param>
    /// <returns>bit-array that represents the bit by bit OR.</returns>
    public static BitArray operator |(BitArray one, BitArray two)
    {
        var sequence1 = one.ToString();
        var sequence2 = two.ToString();
        var result = string.Empty;
        var tmp = string.Empty;

        // for scaling of same length.
        if (one.Length != two.Length)
        {
            int difference;
            if (one.Length > two.Length)
            {
                // one is greater
                difference = one.Length - two.Length;

                // fills up with 0's
                for (var i = 0; i < difference; i++)
                {
                    tmp += '0';
                }

                tmp += two.ToString();
                sequence2 = tmp;
            }
            else
            {
                // two is greater
                difference = two.Length - one.Length;

                // fills up with 0's
                for (var i = 0; i < difference; i++)
                {
                    tmp += '0';
                }

                tmp += one.ToString();
                sequence1 = tmp;
            }
        } // end scaling

        var len = one.Length > two.Length ? one.Length : two.Length;
        var ans = new BitArray(len);

        for (var i = 0; i < len; i++)
        {
            result += sequence1[i].Equals('0') && sequence2[i].Equals('0') ? '0' : '1';
        }

        result = result.Trim();
        ans.Compile(result);

        return ans;
    }

    /// <summary>
    ///     Returns a bit-array that represents the operator ~ (NOT).
    ///     Assumes arrays have the same length.
    /// </summary>
    /// <param name="one">Bit-array.</param>
    /// <returns>bitwise not.</returns>
    public static BitArray operator ~(BitArray one)
    {
        var ans = new BitArray(one.Length);
        var sequence = one.ToString();
        var result = string.Empty;

        foreach (var ch in sequence)
        {
            if (ch == '1')
            {
                result += '0';
            }
            else
            {
                result += '1';
            }
        }

        result = result.Trim();
        ans.Compile(result);

        return ans;
    }

    /// <summary>
    ///     Returns a bit-array that represents bitwise shift left (&gt;&gt;).
    ///     Assumes arrays have the same length.
    /// </summary>
    /// <param name="other">Bit-array.</param>
    /// <param name="n">Number of bits.</param>
    /// <returns>Bitwise shifted BitArray.</returns>
    public static BitArray operator <<(BitArray other, int n)
    {
        var ans = new BitArray(other.Length + n);

        // actual shifting process
        for (var i = 0; i < other.Length; i++)
        {
            ans[i] = other[i];
        }

        return ans;
    }

    /// <summary>
    ///     Returns a bit-array that represents the bit by bit XOR.
    ///     Assumes arrays have the same length.
    /// </summary>
    /// <param name="one">First bit-array.</param>
    /// <param name="two">Second bit-array.</param>
    /// <returns>bit-array.</returns>
    public static BitArray operator ^(BitArray one, BitArray two)
    {
        var sequence1 = one.ToString();
        var sequence2 = two.ToString();
        var tmp = string.Empty;

        // for scaling of same length.
        if (one.Length != two.Length)
        {
            int difference;
            if (one.Length > two.Length)
            {
                // one is greater
                difference = one.Length - two.Length;

                // fills up with 0's
                for (var i = 0; i < difference; i++)
                {
                    tmp += '0';
                }

                tmp += two.ToString();
                sequence2 = tmp;
            }
            else
            {
                // two is greater
                difference = two.Length - one.Length;

                // fills up with 0's
                for (var i = 0; i < difference; i++)
                {
                    tmp += '0';
                }

                tmp += one.ToString();
                sequence1 = tmp;
            }
        } // end scaling

        var len = one.Length > two.Length ? one.Length : two.Length;
        var ans = new BitArray(len);

        var sb = new StringBuilder();

        for (var i = 0; i < len; i++)
        {
            _ = sb.Append(sequence1[i] == sequence2[i] ? '0' : '1');
        }

        var result = sb.ToString().Trim();
        ans.Compile(result);

        return ans;
    }

    /// <summary>
    ///     Returns a bit-array that represents bitwise shift right (>>).
    ///     Assumes arrays have the same length.
    /// </summary>
    /// <param name="other">Bit-array.</param>
    /// <param name="n">Number of bits.</param>
    /// <returns>Bitwise shifted BitArray.</returns>
    public static BitArray operator >>(BitArray other, int n)
    {
        var ans = new BitArray(other.Length - n);

        // actual shifting process.
        for (var i = 0; i < other.Length - n; i++)
        {
            ans[i] = other[i];
        }

        return ans;
    }

    /// <summary>
    ///     Checks if both arrays are == (equal).
    ///     The input assumes arrays have the same length.
    /// </summary>
    /// <param name="one">First bit-array.</param>
    /// <param name="two">Second bit-array.</param>
    /// <returns>Returns True if there inputs are equal; False otherwise.</returns>
    public static bool operator ==(BitArray one, BitArray two)
    {
        if (ReferenceEquals(one, two))
        {
            return true;
        }

        if (one.Length != two.Length)
        {
            return false;
        }

        var status = true;
        for (var i = 0; i < one.Length; i++)
        {
            if (one[i] != two[i])
            {
                status = false;
                break;
            }
        }

        return status;
    }

    /// <summary>
    ///     Checks if both arrays are != (not-equal).
    ///     The input assumes arrays have the same length.
    /// </summary>
    /// <param name="one">First bit-array.</param>
    /// <param name="two">Second bit-array.</param>
    /// <returns>Returns True if there inputs aren't equal; False otherwise.</returns>
    public static bool operator !=(BitArray one, BitArray two) => !(one == two);

    /// <summary>
    ///     Compiles the binary sequence into the inner data structure.
    ///     The sequence must have the same length, as the bit-array.
    ///     The sequence may only be allowed contains ones or zeros.
    /// </summary>
    /// <param name="sequence">A string sequence of 0's and 1's.</param>
    public void Compile(string sequence)
    {
        // precondition I
        if (sequence.Length > field.Length)
        {
            throw new ArgumentException($"{nameof(sequence)} must be not longer than the bit array length");
        }

        // precondition II
        ThrowIfSequenceIsInvalid(sequence);

        // for appropriate scaling
        var tmp = string.Empty;
        if (sequence.Length < field.Length)
        {
            var difference = field.Length - sequence.Length;

            for (var i = 0; i < difference; i++)
            {
                tmp += '0';
            }

            tmp += sequence;
            sequence = tmp;
        }

        // actual compile procedure.
        for (var i = 0; i < sequence.Length; i++)
        {
            field[i] = sequence[i] == '1';
        }
    }

    /// <summary>
    ///     Compiles integer number into the inner data structure.
    ///     Assumes: the number must have the same bit length.
    /// </summary>
    /// <param name="number">A positive integer number.</param>
    public void Compile(int number)
    {
        var tmp = string.Empty;

        // precondition I
        if (number <= 0)
        {
            throw new ArgumentException($"{nameof(number)} must be positive");
        }

        // converts to binary representation
        var binaryNumber = Convert.ToString(number, 2);

        // precondition II
        if (binaryNumber.Length > field.Length)
        {
            throw new ArgumentException("Provided number is too big");
        }

        // for appropriate scaling
        if (binaryNumber.Length < field.Length)
        {
            var difference = field.Length - binaryNumber.Length;

            for (var i = 0; i < difference; i++)
            {
                tmp += '0';
            }

            tmp += binaryNumber;
            binaryNumber = tmp;
        }

        // actual compile procedure.
        for (var i = 0; i < binaryNumber.Length; i++)
        {
            field[i] = binaryNumber[i] == '1';
        }
    }

    /// <summary>
    ///     Compiles integer number into the inner data structure.
    ///     The number must have the same bit length.
    /// </summary>
    /// <param name="number">A positive long integer number.</param>
    public void Compile(long number)
    {
        var tmp = string.Empty;

        // precondition I
        if (number <= 0)
        {
            throw new ArgumentException($"{nameof(number)} must be positive");
        }

        // converts to binary representation
        var binaryNumber = Convert.ToString(number, 2);

        // precondition II
        if (binaryNumber.Length > field.Length)
        {
            throw new ArgumentException("Provided number is too big");
        }

        // for appropriate scaling
        if (binaryNumber.Length < field.Length)
        {
            var difference = field.Length - binaryNumber.Length;

            for (var i = 0; i < difference; i++)
            {
                tmp += '0';
            }

            tmp += binaryNumber;
            binaryNumber = tmp;
        }

        // actual compile procedure.
        for (var i = 0; i < binaryNumber.Length; i++)
        {
            field[i] = binaryNumber[i] == '1';
        }
    }

    /// <summary>
    ///     Is the opposit of the Compile(...) method.
    /// </summary>
    /// <returns>Returns a string representation of the inner data structure.</returns>
    public override string ToString()
    {
        // creates return-string
        return field.Aggregate(string.Empty, (current, t) => current + (t ? "1" : "0"));
    }

    /// <summary>
    ///     Gets the number of one-bits in the field.
    /// </summary>
    /// <returns>quantity of bits in current bit-array.</returns>
    public int NumberOfOneBits()
    {
        // counting one-bits.
        return field.Count(bit => bit);
    }

    /// <summary>
    ///     Gets the number of zero-bits in the field.
    /// </summary>
    /// <returns>quantity of bits.</returns>
    public int NumberOfZeroBits()
    {
        // counting zero-bits
        return field.Count(bit => !bit);
    }

    /// <summary>
    ///     To check for even parity.
    /// </summary>
    /// <returns>Returns True if parity is even; False otherwise.</returns>
    public bool EvenParity() => NumberOfOneBits() % 2 == 0;

    /// <summary>
    ///     To check for odd parity.
    /// </summary>
    /// <returns>Returns True if parity is odd; False otherwise.</returns>
    public bool OddParity() => NumberOfOneBits() % 2 != 0;

    /// <summary>
    ///     Returns a long integer representation of the bit-array.
    ///     Assumes the bit-array length must been smaller or equal to 64 bit.
    /// </summary>
    /// <returns>Long integer array.</returns>
    public long ToInt64()
    {
        // Precondition
        if (field.Length > 64)
        {
            throw new InvalidOperationException("Value is too big to fit into Int64");
        }

        var sequence = ToString();
        return Convert.ToInt64(sequence, 2);
    }

    /// <summary>
    ///     Returns a long integer representation of the bit-array.
    ///     Assumes the bit-array length must been smaller or equal to 32 bit.
    /// </summary>
    /// <returns>integer array.</returns>
    public int ToInt32()
    {
        // Precondition
        if (field.Length > 32)
        {
            throw new InvalidOperationException("Value is too big to fit into Int32");
        }

        var sequence = ToString();
        return Convert.ToInt32(sequence, 2);
    }

    /// <summary>
    ///     Sets all bits on false.
    /// </summary>
    public void ResetField()
    {
        for (var i = 0; i < field.Length; i++)
        {
            field[i] = false;
        }
    }

    /// <summary>
    ///     Sets all bits on the value of the flag.
    /// </summary>
    /// <param name="flag">Bollean flag (false-true).</param>
    public void SetAll(bool flag)
    {
        for (var i = 0; i < field.Length; i++)
        {
            field[i] = flag;
        }
    }

    /// <summary>
    ///     Checks if bit-array are equal.
    ///     Assumes the input bit-arrays must have same length.
    /// </summary>
    /// <param name="obj">Bit-array object.</param>
    /// <returns>Returns true if there inputs are equal otherwise false.</returns>
    public override bool Equals(object? obj)
    {
        if (obj is null)
        {
            return false;
        }

        var otherBitArray = (BitArray)obj;

        if (Length != otherBitArray.Length)
        {
            return false;
        }

        for (var i = 0; i < Length; i++)
        {
            if (field[i] != otherBitArray[i])
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    ///     Gets has-code of bit-array.
    ///     Assumes bit-array length must been smaller or equal to 32.
    /// </summary>
    /// <returns>hash-code for this BitArray instance.</returns>
    public override int GetHashCode() => ToInt32();

    private static void ThrowIfSequenceIsInvalid(string sequence)
    {
        if (!Match(sequence))
        {
            throw new ArgumentException("The sequence may only contain ones or zeros");
        }
    }

    /// <summary>
    ///     Utility method foir checking a given sequence contains only zeros and ones.
    ///     This method will used in Constructor (sequence : string) and Compile(sequence : string).
    /// </summary>
    /// <param name="sequence">String sequence.</param>
    /// <returns>returns True if sequence contains only zeros and ones; False otherwise.</returns>
    private static bool Match(string sequence) => sequence.All(ch => ch == '0' || ch == '1');
}