#!/usr/bin/env python

###  BitVector.py

###  Version: 1.2
   
###  Author: Avinash Kak (kak@purdue.edu)


import sys
import array
import exceptions
import operator

_hexdict = { '0' : '0000', '1' : '0001', '2' : '0010', '3' : '0011',
             '4' : '0100', '5' : '0101', '6' : '0110', '7' : '0111',
             '8' : '1000', '9' : '1001', 'a' : '1010', 'b' : '1011',
             'c' : '1100', 'd' : '1101', 'e' : '1110', 'f' : '1111' }

# If this function can read all blocksize bits, it peeks ahead
# to see if there is anything more to be read in the file. It
# uses tell-read-seek mechanism for this in lines (R18) through
# (R21).  If there is nothing further to be read, it sets the
# more_to_read attribute of the bitvector object to False.
# Obviously, this can only be done for seekable streams such
# as those connected with disk files.  According to Blair Houghton,
# a similar feature could presumably be implemented for socket
# streams by using recv() or recvfrom() if you set the flags
# argument to MSG_PEEK. 
def _readblock( blocksize, bitvector ):                              #(R1)
    global hexdict                                                   #(R2)
    bitstring = ''                                                   #(R3)
    i = 0                                                            #(R4)
    while ( i < blocksize / 8 ):                                     #(R5)
        i += 1                                                       #(R6)
        byte = bitvector.FILEIN.read(1)                              #(R7)
        if byte == '':                                               #(R8)
            if len(bitstring) < blocksize:                           #(R9)
                bitvector.more_to_read = False                      #(R10)
            return bitstring                                        #(R11)
        hexvalue = hex( ord( byte ) )                               #(R12)
        hexvalue = hexvalue[2:]                                     #(R13)
        if len( hexvalue ) == 1:                                    #(R14)
            hexvalue = '0' + hexvalue                               #(R15)
        bitstring += _hexdict[ hexvalue[0] ]                        #(R16)
        bitstring += _hexdict[ hexvalue[1] ]                        #(R17)
    file_pos = bitvector.FILEIN.tell()                              #(R18)
    # peek at the next byte; moves file position only if a
    # byte is read
    next_byte = bitvector.FILEIN.read(1)                            #(R19)
    if next_byte:                                                   #(R20)
        # pretend we never read the byte                   
        bitvector.FILEIN.seek( file_pos )                           #(R21)
    else:                                                           #(R22)
        bitvector.more_to_read = False                              #(R23)
    return bitstring                                                #(R24)


#--------------------  BitVector Class Definition   ----------------------

class BitVector( object ):                                           #(A1)

    def __init__( self, *args, **kwargs ):                           #(A2)
        if args:                                                     #(A3)
               raise ValueError(
                      '''BitVector constructor can only be called
                         with keyword arguments for the following
                         keywords: filename, fp (for fileobject),
                         size, intValue, bitlist (for a list or
                         tuple of bits, or bitstring)''')
        filename = fp = size = intVal = bitlist = bitstring = None   #(A4)
        if kwargs.has_key('filename'):filename=kwargs.pop('filename')#(A5)
        if kwargs.has_key('fp'):           fp = kwargs.pop('fp')     #(A6)
        if kwargs.has_key('size'):       size = kwargs.pop('size')   #(A7)
        if kwargs.has_key('intVal'):   intVal = kwargs.pop('intVal') #(A8)
        if kwargs.has_key('bitlist'): bitlist = kwargs.pop('bitlist')#(A9)
        if kwargs.has_key('bitstring') :
                               bitstring = kwargs.pop('bitstring')  #(A10)

        if filename:                                                #(A11)
            if fp or size or intVal or bitlist or bitstring:        #(A12)
                raise ValueError(                                   #(A13)
                  '''When filename is specified, you cannot
                     give values to any other constructor args''')
            self.filename = filename                                #(A14)
            self.FILEIN = open( filename, 'rb' )                    #(A15)
            self.size = 0                                           #(A16)
            self.more_to_read = True                                #(A17)
            return                                                  #(A18)
        elif fp:                                                    #(A19)
            if filename or size or intVal or bitlist or bitstring:  #(A20)
                raise ValueError(                                   #(A21)
                  '''When fileobject is specified, you cannot      
                     give values to any other constructor args''')
            bits = self.read_bits_from_fileobject( fp )             #(A22)
            bitlist =  map( lambda x: int(x), bits )                #(A23)
            self.size = len( bitlist )                              #(A24)
        elif size >= 0:                                             #(A25)
            if filename or fp or intVal or bitlist or bitstring:    #(A26)
                raise ValueError(                                   #(A27)
                  '''When size is specified, you cannot
                     give values to any other constructor args''')
            self.size = size                                        #(A28)
            bitlist = tuple( [0] * size )                           #(A29)
        elif intVal or intVal == 0:                                 #(A30)
            if filename or fp or size or bitlist or bitstring:      #(A31)
                raise ValueError(                                   #(A32)
                  '''When intVal is specified, you cannot
                     give values to any other constructor args''')
            if intVal == 0:                                         #(A33)
                bitlist = [0]                                       #(A34)
                self.size = 1                                       #(A35)
            else:                                                   #(A36)
                hexVal = hex( intVal )                              #(A37)
                hexVal = hexVal[2:]                                 #(A38)
                if len( hexVal ) == 1:                              #(A39)
                    hexVal = '0' + hexVal                           #(A40)
                bitlist = []                                        #(A41)
                for item in hexVal[:]:                              #(A42)
                    bitlist += _hexdict[item][:]                    #(A43)
                bitlist =  map( lambda x: int(x), bitlist )         #(A44)
                i = 0                                               #(A45)
                while ( i < len( bitlist ) ):                       #(A46)
                    if bitlist[i] == 1: break                       #(A47)
                    i += 1                                          #(A48)
                del bitlist[0:i]                                    #(A49)
                self.size = len( bitlist )                          #(A50)
        elif bitstring or bitstring == '':                          #(A51)
            if filename or fp or size or intVal or bitlist:         #(A52)
                raise ValueError(                                   #(A53)
                  '''When a bitstring is specified, you cannot
                     give values to any other constructor args''')
            bitlist =  map( lambda x: int(x), list(bitstring) )     #(A54)
            self.size = len( bitlist )                              #(A55)
        elif bitlist:                                               #(A56)
            if filename or fp or size or intVal or bitstring:       #(A57)
                raise ValueError(                                   #(A58)
                  '''When bits are specified, you cannot
                     give values to any other constructor args''')
            self.size = len( bitlist )                              #(A59)
        else:                                                       #(A60)
            raise ValueError("wrong arg(s) for constructor")        #(A61) 
        two_byte_ints_needed = (len(bitlist) + 15) // 16            #(A62)
        self.vector = array.array( 'H', [0]*two_byte_ints_needed )  #(A63)
        map( self._setbit, enumerate(bitlist), bitlist)             #(A64)

    # Set the bit at the designated position to the value shown:
    def _setbit( self, posn, val ):                                  #(B1)
        if val not in (0, 1):                                        #(B2)
            raise ValueError( "incorrect value for a bit" )          #(B3)
        if isinstance( posn, (tuple) ):                              #(B4)
            posn = posn[0]                                           #(B5)
        if  posn >= self.size or posn < -self.size:                  #(B6)
            raise ValueError( "index range error" )                  #(B7)   
        if posn < 0: posn = self.size + posn                         #(B8)
        block_index = posn // 16                                     #(B9)
        shift = posn & 15                                           #(B10)
        cv = self.vector[block_index]                               #(B11)
        if ( cv >> shift ) & 1 != val:                              #(B12)
            self.vector[block_index] = cv ^ (1 << shift)            #(B13)

    # Get the bit from the designated position:
    def _getbit( self, posn ):                                       #(C1)
        if  posn >= self.size or posn < -self.size:                  #(C2)
            raise ValueError( "index range error" )                  #(C3)   
        if posn < 0: posn = self.size + posn                         #(C4)
        return ( self.vector[posn//16] >> (posn&15) ) & 1            #(C5)

    # Take a bitwise 'xor' of the bit vector on which the method is
    # invoked with the argument bit vector.  Return the result as
    # a new bit vector.  If the two bit vectors are not of the same
    # size, pad the shorter one with zero's from the left.        
    def __xor__(self, other):                                        #(E1)
        if self.size < other.size:                                   #(E2)
            bv1 = self._resize_pad_from_left(other.size - self.size) #(E3)
            bv2 = other                                              #(E4)
        else:                                                        #(E5)
            bv1 = self                                               #(E6)
            bv2 = other._resize_pad_from_left(self.size - other.size)#(E7)
        res = BitVector( size = bv1.size )                           #(E8)
        res.vector = map(operator.__xor__, bv1.vector, bv2.vector)   #(E9) 
        return res                                                  #(E10)

    # Take a bitwise 'and' of the bit vector on which the method is
    # invoked with the argument bit vector.  Return the result as
    # a new bit vector.  If the two bit vectors are not of the same
    # size, pad the shorter one with zero's from the left.    
    def __and__(self, other):                                        #(F1)
        if self.size < other.size:                                   #(F2)
            bv1 = self._resize_pad_from_left(other.size - self.size) #(F3)
            bv2 = other                                              #(F4)
        else:                                                        #(F5)
            bv1 = self                                               #(F6)
            bv2 = other._resize_pad_from_left(self.size - other.size)#(F7)
        res = BitVector( size = bv1.size )                           #(F8)
        res.vector = map(operator.__and__, bv1.vector, bv2.vector)   #(F9)
        return res                                                  #(F10)

    # Take a bitwise 'or' of the bit vector on which the method is
    # invoked with the argument bit vector.  Return the result as
    # a new bit vector.  If the two bit vectors are not of the same
    # size, pad the shorter one with zero's from the left.
    def __or__(self, other):                                         #(G1)
        if self.size < other.size:                                   #(G2)
            bv1 = self._resize_pad_from_left(other.size - self.size) #(G3)
            bv2 = other                                              #(G4)
        else:                                                        #(G5)
            bv1 = self                                               #(G6)
            bv2 = other._resize_pad_from_left(self.size - other.size)#(G7)
        res = BitVector( size = bv1.size )                           #(G8)
        res.vector = map( operator.__or__, bv1.vector, bv2.vector)   #(G9)
        return res                                                  #(G10)

    # Invert the bits in the bit vector on which the method is
    # invoked and return the result as a new bit vector.
    def __invert__(self):                                            #(H1)
        res = BitVector( size = self.size )                          #(H2)
        res.vector = map( operator.__inv__, self.vector )            #(H3)
        return res                                                   #(H4)

    # Concatenate the argument bit vector with the bit vector
    # on which the method is invoked.  Return the concatenated
    # bit vector as a new BitVector object:
    def __add__(self, other):                                        #(J1)
        i = 0                                                        #(J2)
        outlist = []                                                 #(J3)
        while ( i < self.size ):                                     #(J4)
            outlist.append( self[i] )                                #(J5)
            i += 1                                                   #(J6)
        i = 0                                                        #(J7)
        while ( i < other.size ):                                    #(J8)
            outlist.append( other[i] )                               #(J9)
            i += 1                                                  #(J10)
        return BitVector( bitlist = outlist )                       #(J11)

    # Return the number of bits in a bit vector:
    def _getsize(self):                                              #(K1)
        return self.size                                             #(K2)

    # Read blocksize bits from a disk file and return a
    # BitVector object containing the bits.  If the file
    # contains fewer bits than blocksize, construct the
    # BitVector object from however many bits there are
    # in the file.  If the file contains zero bits, return
    # a BitVector object of size attribute set to 0.
    def read_bits_from_file(self, blocksize):                        #(L1)
        error_str = '''You need to first construct a BitVector
        object with a filename as  argument'''                       #(L2)
        if self.filename == None:                                    #(L3)
            raise SyntaxError( error_str )                           #(L4)
        if blocksize % 8 != 0:                                       #(L5)
            raise ValueError( "block size must be a multiple of 8" ) #(L6)
        bitstr = _readblock( blocksize, self )                       #(L7)
        if len( bitstr ) == 0:                                       #(L8)
            return BitVector( size = 0 )                             #(L9)
        else:                                                       #(L10)
            return BitVector( bitstring = bitstr )                  #(L11)

    # This function is meant to read a bit string from a
    # file like object.
    def read_bits_from_fileobject( self, fp ):                       #(M1)
        bitlist = []                                                 #(M2)
        while True:                                                  #(M3)
            bit = fp.read()                                          #(M4)
            if bit == '': return bitlist                             #(M5)
            bitlist += bit                                           #(M6)

    # This function is meant to write a bit vector directly to
    # a file like object.  Note that whereas 'write_to_file'
    # method creates a memory footprint that corresponds exactly
    # to the bit vector, the 'write_bits_to_fileobject' actually
    # writes out the 1's and 0's as individual items to the
    # file object.  That makes this method convenient for
    # creating a string representation of a bit vector,
    # especially if you use the StringIO class, as shown in
    # the test code.
    def write_bits_to_fileobject( self, fp ):                        #(N1)
        for bit_index in range(self.size):                           #(N2)
            if self[bit_index] == 0:                                 #(N3)
                fp.write( '0' )                                      #(N4)
            else:                                                    #(N5)
                fp.write( '1' )                                      #(N6)

    # Divides an even-sized bit vector into two and returns
    # the two halves as a list of two bit vectors.
    def divide_into_two(self):                                       #(P1)
        if self.size % 2 != 0:                                       #(P2)
            raise ValueError( "must have even num bits" )            #(P3)
        i = 0                                                        #(P4)
        outlist1 = []                                                #(P5)
        while ( i < self.size /2 ):                                  #(P6)
            outlist1.append( self[i] )                               #(P7)
            i += 1                                                   #(P8)
        outlist2 = []                                                #(P9)
        while ( i < self.size ):                                    #(P10)
            outlist2.append( self[i] )                              #(P11)
            i += 1                                                  #(P12)
        return [ BitVector( bitlist = outlist1 ),
                 BitVector( bitlist = outlist2 ) ]                  #(P13)

    # Permute a bit vector according to the indices shown in the
    # second argument list.  Return the permuted bit vector as a
    # new bit vector.
    def permute(self, permute_list):                                 #(Q1)
        if max(permute_list) > self.size -1:                         #(Q2)
            raise ValueError( "Bad permutation index" )              #(Q3)
        outlist = []                                                 #(Q4)
        i = 0                                                        #(Q5)
        while ( i < len( permute_list ) ):                           #(Q6)
            outlist.append( self[ permute_list[i] ] )                #(Q7)
            i += 1                                                   #(Q8)
        return BitVector( bitlist = outlist )                        #(Q9)

    # Unpermute the bit vector according to the permutation list
    # supplied as the second argument.  If you first permute a
    # bit vector by using permute() and then unpermute() it using
    # the same permutation list, you will get back the original bit
    # vector.
    def unpermute(self, permute_list):                               #(S1)
        if max(permute_list) > self.size -1:                         #(S2)
            raise exceptions.ValueError, "Bad permutation index"     #(S3)
        if self.size != len( permute_list ):                         #(S4)
            raise exceptions.ValueError,"Bad size for permute list"  #(S5)
        out_bv = BitVector( size = self.size )                       #(S6)
        i = 0                                                        #(S7)
        while ( i < len(permute_list) ):                             #(S8)
            out_bv[ permute_list[i] ] = self[i]                      #(S9)
            i += 1                                                  #(S10)
        return out_bv                                               #(S11)

    # (Contributed by Joe Davidson) Write the bitvector to the file
    # object file_out.  (A file object is returned by a call to open()).
    # Since all file I/O is byte oriented, the bitvector must be
    # multiple of 8 bits. Each byte treated as MSB first (0th index)
    def write_to_file(self, file_out):                               #(T1)
        err_str = '''Only a bit vector whose length is a multiple of 8
            can be written to a file.  Use the padding functions
            to satisfy this constraint.'''                           #(T2)
        if self.size % 8:                                            #(T3)
            raise exceptions.ValueError, err_str                     #(T4)
        for byte in range(self.size/8 ):                             #(T5)
            value = 0                                                #(T6)
            for bit in range(8):                                     #(T7)
                value += (self._getbit( byte*8 + (7 - bit) ) << bit )#(T8)
            file_out.write( chr(value) )                             #(T9)

    # For closing a file object that was used for reading
    # the bits into one or more BitVector objects
    def close_file_object(self):                                     #(U1)
        if self.filename == None:                                    #(U2)
            raise exceptions.SyntaxError, "No associated open file"  #(U3)
        self.FILEIN.close()                                          #(U4)

    # Return the integer value of a bitvector:
    def intValue(self):                                              #(V1)
        intVal = 0                                                   #(V2)
        for i in range(self.size):                                   #(V3)
            intVal += self[i] * (2 ** (self.size - i - 1))           #(V4)
        return intVal                                                #(V5)
            
    # For an in-place left circular shift by n bit positions:
    def __lshift__( self, n ):                                       #(W1)
        for i in range(n):                                           #(W2)
            self.circular_rotate_left_by_one()                       #(W3)
    # For an in-place right circular shift by n bit positions:
    def __rshift__( self, n ):                                       #(W4)
        for i in range(n):                                           #(W5)
            self.circular_rotate_right_by_one()                      #(W6)

    # For a one-bit in-place left circular shift:
    def circular_rotate_left_by_one(self):                           #(X1)
        size = len(self.vector)                                      #(X2)
        bitstring_leftmost_bit = self.vector[0] & 1                  #(X3)
        left_most_bits = map(operator.__and__, self.vector, [1]*size)#(X4)
        left_most_bits.append(left_most_bits[0])                     #(X5)
        del(left_most_bits[0])                                       #(X6)
        self.vector = map(operator.__rshift__, self.vector, [1]*size)#(X7)
        self.vector = map( operator.__or__, self.vector, \
             map(operator.__lshift__, left_most_bits, [15]*size) )   #(X8)
        self._setbit(self.size -1, bitstring_leftmost_bit)           #(X9)

    # For a one-bit in-place right circular shift:
    def circular_rotate_right_by_one(self):                          #(Y1)
        size = len(self.vector)                                      #(Y2)
        bitstring_rightmost_bit = self[self.size - 1]                #(Y3)
        right_most_bits = map( operator.__and__,
                               self.vector, [0x8000]*size )          #(Y4)
        map( operator.__and__, self.vector, [~0x8000]*size )         #(Y5)
        right_most_bits.insert(0, bitstring_rightmost_bit)           #(Y6)
        right_most_bits.pop()                                        #(Y7)
        self.vector = map(operator.__lshift__, self.vector, [1]*size)#(Y8)
        self.vector = map( operator.__or__, self.vector, \
             map(operator.__rshift__, right_most_bits, [15]*size) )  #(Y9)
        self._setbit(0, bitstring_rightmost_bit)                    #(Y10)

    # This is merely another implementation of the method
    # circular_rotate_left_by_one() shown above.  This one
    # does NOT use map functions.  This method carries out a
    # one-bit left circular shift of a bit vector.
    def circular_rot_left(self):                                     #(Z1)
        max_index = (self.size -1)  // 16                            #(Z2)
        left_most_bit = self.vector[0] & 1                           #(Z3)
        self.vector[0] = self.vector[0] >> 1                         #(Z4)
        for i in range(1, max_index + 1):                            #(Z5)
            left_bit = self.vector[i] & 1                            #(Z6)
            self.vector[i] = self.vector[i] >> 1                     #(Z7)
            self.vector[i-1] |= left_bit << 15                       #(Z8)
        self._setbit(self.size -1, left_most_bit)                    #(Z9)

    # This is merely another implementation of the method
    # circular_rotate_right_by_one() shown above.  This one
    # does NOT use map functions.  This method does a one-bit
    # right circular shift of a bit vector.
    def circular_rot_right(self):                                    #(a1)
        max_index = (self.size -1)  // 16                            #(a2)
        right_most_bit = self[self.size - 1]                         #(a3)
        self.vector[max_index] &= ~0x8000                            #(a4)
        self.vector[max_index] = self.vector[max_index] << 1         #(a5)
        for i in range(max_index-1, -1, -1):                         #(a6)
            right_bit = self.vector[i] & 0x8000                      #(a7)
            self.vector[i] &= ~0x8000                                #(a8)
            self.vector[i] = self.vector[i] << 1                     #(a9)
            self.vector[i+1] |= right_bit >> 15                     #(a10)
        self._setbit(0, right_most_bit)                             #(a11)


    # Allow array like subscripting for getting and setting:
    __getitem__ = _getbit                                            #(b1)
    __setitem__ = _setbit                                            #(b2)

    # Allow slicing with [i:j], [:], etc.
    def __getslice__(self, i, j):                                    #(c1)
        slicebits = []                                               #(c2)
        if j > self.size: j = self.size                              #(c3)
        for x in range(i,j):                                         #(c4)
            slicebits.append( self[x] )                              #(c5)
        return BitVector( bitlist = slicebits )                      #(c6)

    # Allow len() to work:
    __len__ = _getsize                                               #(d1)
    # Allow int() to work:
    __int__ = intValue                                               #(d2)
    # To allow iterations over a bit vector:
    def __iter__( self ):                                            #(d3)
        return BitVectorIterator( self )                             #(d4)

    # To create a print representation
    def __str__( self ):                                             #(e1)
        if self.size == 0:                                           #(e2)
            return ''                                                #(e3)
        return ''.join( map( lambda x: str(x), self ) )              #(e4)

    # Compare two bit vectors:
    def __eq__(self, other):                                         #(f1)
        if self.size != other.size:                                  #(f2)
            return False                                             #(f3)
        i = 0                                                        #(f4)
        outlist = []                                                 #(f5)
        while ( i < self.size ):                                     #(f6)
            if (self[i] != other[i]): return False                   #(f7)
            i += 1                                                   #(f8)
        return True                                                  #(f9)
    def __ne__(self, other):                                        #(f10)
        return not self == other                                    #(f11)
    def __lt__(self, other):                                        #(f12)
        return self.intValue() < other.intValue()                   #(f13)
    def __le__(self, other):                                        #(f14)
        return self.intValue() <= other.intValue()                  #(f15)
    def __gt__(self, other):                                        #(f16)
        return self.intValue() > other.intValue()                   #(f17)
    def __ge__(self, other):                                        #(f18)
        return self.intValue() >= other.intValue()                  #(f19)

    # Some additional utility functions:
    # Make a deep copy of a bit vector:
    def _make_deep_copy( self ):                                     #(g1)
        copy = str( self )                                           #(g2)
        return BitVector( bitstring = copy )                         #(g3)
    # Resize a bit vector by padding with n 0's from the left
    # Return the result as a new bit vector:
    def _resize_pad_from_left( self, n ):                            #(g4)
        new_str = '0'*n + str( self )                                #(g5)
        return BitVector( bitstring = new_str )                      #(g6)
    # Do the same by padding from the right:
    def _resize_pad_from_right( self, n ):                           #(g7)
        new_str = str( self ) + '0'*n                                #(g8)
        return BitVector( bitstring = new_str )                      #(g9)

    # Pad a bit vector with n zeros from the left
    def pad_from_left( self, n ):                                   #(g10)
        new_str = '0'*n + str( self )                               #(g11)
        bitlist =  map( lambda x: int(x), list(new_str) )           #(g12)
        self.size = len( bitlist )                                  #(g13)
        two_byte_ints_needed = (len(bitlist) + 15) // 16            #(g14)
        self.vector = array.array( 'H', [0]*two_byte_ints_needed )  #(g15)
        map( self._setbit, enumerate(bitlist), bitlist)             #(g16)

    # Pad a bit vector with n zeros from the right
    def pad_from_right( self, n ):                                  #(g17)
        new_str = str( self ) + '0'*n                               #(g18)
        bitlist =  map( lambda x: int(x), list(new_str) )           #(g19)
        self.size = len( bitlist )                                  #(g20)
        two_byte_ints_needed = (len(bitlist) + 15) // 16            #(g21)
        self.vector = array.array( 'H', [0]*two_byte_ints_needed )  #(g22)
        map( self._setbit, enumerate(bitlist), bitlist)             #(g23)


#-----------------------  BitVectorIterator Class -----------------------

class BitVectorIterator:                                             #(h1)
    def __init__( self, bitvec ):                                    #(h2)
        self.items = []                                              #(h3)
        for i in range( bitvec.size ):                               #(h4)
            self.items.append( bitvec._getbit(i) )                   #(h5)
        self.index = -1                                              #(h6)
    def __iter__( self ):                                            #(h7)
        return self                                                  #(h8)
    def next( self ):                                                #(h9)
        self.index += 1                                             #(h10)
        if self.index < len( self.items ):                          #(h11)
            return self.items[ self.index ]                         #(h12)
        else:                                                       #(h13)
            raise StopIteration                                     #(h14)

       
#------------------------  End of Class Definition -----------------------


#------------------------     Test Code Follows    -----------------------

if __name__ == '__main__':

    # Construct a bit vector of size 0
    bv1 = BitVector( size = 0 )
    print bv1                                   # no output

    # Construct a bit vector of size 1
    bv2 = BitVector( size = 2 )
    print bv2                                   # 00

    # Joining two bit vectors:
    print bv1 + bv2                             # 00

    # Construct a bit vector with a tuple of bits:
    bv = BitVector( bitlist = (1, 0, 0, 1) )
    print bv                                    # 1001

    # Construct a bit vector with a list of bits:    
    bv = BitVector( bitlist = [1, 1, 0, 1] )
    print bv                                    # 1101

    # Construct a bit vector from an integer
    bv = BitVector( intVal = 5678 )
    print bv                                    # 0001011000101110
    bv = BitVector( intVal = 0 )
    print bv                                    # 0
    bv = BitVector( intVal = 2 )
    print bv                                    # 10
    bv = BitVector( intVal = 3 )
    print bv                                    # 11
    bv = BitVector( intVal = 123456 )
    print bv                                    # 11110001001000000
    print bv.intValue()                         # 123456
    print int( bv )

    # Construct a bit vector directly from a file-like object:
    import StringIO
    x = "111100001111"
    fp_read = StringIO.StringIO( x )
    bv = BitVector( fp = fp_read )
    print bv                                    # 111100001111 

    # Construct a bit vector directly from a bit string:
    bv = BitVector( bitstring = '00110011' )
    print bv                                    # 00110011

    bv = BitVector( bitstring = '' )
    print bv                                    # nothing

    # Get the integer value of a bit vector:
    print bv.intValue()                         # 51

    # Test array-like indexing for a bit vector:
    bv = BitVector( bitstring = '110001' )
    print bv[0], bv[1], bv[2], bv[3], bv[4], bv[5]       # 1 1 0 0 0 1
    print bv[-1], bv[-2], bv[-3], bv[-4], bv[-5], bv[-6] # 1 0 0 0 1 1

    # Test setting bit values with positive and negative
    # accessors:
    bv = BitVector( bitstring = '1111' )
    print bv                                    # 1111
    bv[0]=0;bv[1]=0;bv[2]=0;bv[3]=0        
    print bv                                    # 0000
    bv[-1]=1;bv[-2]=1;bv[-4]=1
    print bv                                    # 1011

    # Check equality and inequality operators:
    bv1 = BitVector( bitstring = '00110011' )
    bv2 = BitVector( bitlist = [0,0,1,1,0,0,1,1] )
    print bv1 == bv2                            # True
    print bv1 != bv2                            # False
    print bv1 < bv2                             # False
    print bv1 <= bv2                            # True
    bv3 = BitVector( intVal = 5678 )
    print bv3.intValue()                        # 5678
    print bv3                                   # 0010110000101110
    print bv1 == bv3                            # False
    print bv3 > bv1                             # True
    print bv3 >= bv1                            # True


    # Create a string representation of a bit vector:
    fp_write = StringIO.StringIO()
    bv.write_bits_to_fileobject( fp_write )
    print fp_write.getvalue()                   # 111100001111 

    # Experiments with bit-wise logical operations:
    bv3 = bv1 | bv2                              
    print bv3                                   # 1101
    bv3 = bv1 & bv2
    print bv3                                   # 1001    
    bv3 = bv1 + bv2
    print bv3                                   # 10011101
    bv4 = BitVector( size = 3 )
    print bv4                                   # 000
    bv5 = bv3 + bv4
    print bv5                                   # 10011101000
    bv6 = ~bv5
    print bv6                                   # 01100010111
    bv7 = bv5 & bv6
    print bv7                                   # 00000000000
    bv7 = bv5 | bv6
    print bv7                                   # 11111111111

    # Try logical operations on bit vectors of different sizes:
    print BitVector( intVal = 6 ) ^ BitVector( intVal = 13 )   # 1011
    print BitVector( intVal = 6 ) & BitVector( intVal = 13 )   # 0100
    print BitVector( intVal = 6 ) | BitVector( intVal = 13 )   # 1111

    print BitVector( intVal = 1 ) ^ BitVector( intVal = 13 )   # 1100
    print BitVector( intVal = 1 ) & BitVector( intVal = 13 )   # 0001
    print BitVector( intVal = 1 ) | BitVector( intVal = 13 )   # 1101

    # Try setbit and getsize:
    bv7[7] = 0
    print bv7                                   # 11111110111
    print len( bv7 )                            # 11
    bv8 = (bv5 & bv6) ^ bv7
    print bv8                                   # 11111110111
    
    # Construct a bit vector from a LIST of bits:
    bv = BitVector( bitlist= [0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1] )
    print bv                                    # 0010010100101001

    # Construct a bit vector from a file:
    bv = BitVector( filename = 'TestBitVector/testinput1.txt' )
    print bv                                    # nothing to show
    bv1 = bv.read_bits_from_file(64)    
    print bv1
         # 0100000100100000011010000111010101101110011001110111001001111001
    bv2 = bv.read_bits_from_file(64)    
    print bv2
         # 0010000001100010011100100110111101110111011011100010000001100110
    bv3 = bv1 ^ (bv2)
    print bv3
         # 0110000101000010000110100001101000011001000010010101001000011111

    # Divide into two bit vectors:
    [bv4, bv5] = bv3.divide_into_two()
    print bv4                            # 01100001010000100001101000011010
    print bv5                            # 00011001000010010101001000011111

    # Permute a bit vector:
    bv1 = BitVector( bitlist = [1, 0, 0, 1, 1, 0, 1] )
    print bv1                                    # 1001101
    
    bv2 = bv1.permute( [6, 2, 0, 1] )
    print bv2                                    # 1010
    bv3 = BitVector( bitlist = [1, 1, 0, 0, 0, 1, 1] )
    print bv3                                    # 1100011
    bv4 = bv1 & bv3 
    print bv4                                    # 1000001
    print

    # Read a file from the beginning to end:
    bv = BitVector( filename = 'TestBitVector/testinput4.txt' )
    while (bv.more_to_read):
        bv_read = bv.read_bits_from_file( 64 )
        print bv_read
    print

    # Experiment with closing a file object and start
    # extracting bit vectors from the file from
    # the beginning again:
    bv.close_file_object()
    bv = BitVector( filename = 'TestBitVector/testinput4.txt' )
    bv1 = bv.read_bits_from_file(64)        
    print bv1           
    FILEOUT = open( 'TestBitVector/testinput5.txt', 'w' )
    bv1.write_to_file( FILEOUT )
    FILEOUT.close()

    # Experiment in 64-bit permutation and unpermutation:
    # The permutation array was generated separately by the
    # Fisher-Yates shuffle algorithm:
    bv2 = bv1.permute( [22, 47, 33, 36, 18, 6, 32, 29, 54, 62, 4,
                        9, 42, 39, 45, 59, 8, 50, 35, 20, 25, 49,
                        15, 61, 55, 60, 0, 14, 38, 40, 23, 17, 41,
                        10, 57, 12, 30, 3, 52, 11, 26, 43, 21, 13,
                        58, 37, 48, 28, 1, 63, 2, 31, 53, 56, 44, 24,
                        51, 19, 7, 5, 34, 27, 16, 46] )
    print bv2

    bv3 = bv2.unpermute( [22, 47, 33, 36, 18, 6, 32, 29, 54, 62, 4,
                          9, 42, 39, 45, 59, 8, 50, 35, 20, 25, 49,
                          15, 61, 55, 60, 0, 14, 38, 40, 23, 17, 41,
                          10, 57, 12, 30, 3, 52, 11, 26, 43, 21, 13,
                          58, 37, 48, 28, 1, 63, 2, 31, 53, 56, 44, 24,
                          51, 19, 7, 5, 34, 27, 16, 46] )    
    print bv3

    print
    print
    
    # Try circular shifts to the left and to the right
    print bv3
    bv3 << 7
    print bv3
    bv3 >> 7
    print bv3

    # Test len()    
    print len( bv3 )                      # 64

    # Test slicing
    bv4 = bv3[5:22]
    print bv4                             # 00100100000011010

    # Test the iterator:
    for item in bv4:
        print item,                       # 0 0 1 0 0 1 0 0 0 0 0 0 1 1 0 1 0
    print
    
    # Demonstrate padding a bit vector from left
    bv = BitVector( bitstring = '101010' )
    bv.pad_from_left( 4 )
    print bv                              # 0000101010
    # Demonstrate padding a bit vector from right
    bv.pad_from_right( 4 )
    print bv                              # 00001010100000