Journal Articles

CVu Journal Vol 8, #1 - Feb 1996 + Programming Topics
Browse in : All > Journals > CVu > 081 (7)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Sets a-la Pascal

Author: Administrator

Date: 03 February 1996 13:15:26 +00:00 or Sat, 03 February 1996 13:15:26 +00:00

Summary: 

Body: 

Many programmers who have been satisfied with Pascal for years but for some reason want to switch to C++, will miss the Set facility from Pascal. For a Pascal programmer it may seem strange that the enum type exists, but not the Set.

Fortunately C++ is a language which permits easy construction of new types. The following is my own implementation as I prefer it. In Pascal there exist operations on sets, such as +, - and *. I've always found it confusing - I prefer something easier to remember.

A Set is a string of bits that may be set or removed according to some state of the program or its data. Sets in Pascal are of fixed size. In C++ the size may be determined at run-time.

In class Set the bits are stored in a whole number of bytes, each byte containing 8 bits. Bit manipulations are performed by the binary operators available in C++. Bits are numbered 76543210.

Bitwise operations

If for example bit 4 (00010000) has to be added to the byte b the following operation is performed: b = b | (1 << 4), or shorter:

b |= (1 << 4)

If the 4th bit is already set, the Set remains unchanged.

To remove a bit, the operation is a little more tricky. For example to remove bit 4 the following operation, we cannot just OR with a zero bit as this would remove other bits in the byte. We have to AND with 11101111 in order to remove bit 4 and preserve all other bits. The expression b &= ~(1 << 4) does it.

When a set contains more than a byte, we must find the byte number and, within the byte, the bitnumber in order to handle a single bit. To find the byte to operate on is easy. It is BitNum div 8. The bit is BitNum mod 8.

All in all, these operations are (surprisingly?) easily managed by the bitwise operators in C++.

Almost like Pascal

In class Set the operator << is for adding a bit and >> for removing a bit. This is convenient as I can write:

enum Color { Green, Blue, Yellow, Red } ;
Set Colors(10); // define a set with 10 bits
  s << Red << Green << Blue;
  if (Colors[Blue]) print("Blue was in the Set s");

It is not quite like Pascal, but the same functionality is there.

I've also made a function that adds one set to another set, eventually of different size. As far as I remember Pascal, my function works as in Pascal. As an example like the above may be found in most Pascal books, I've also found it natural to include an exercise for the reader. See the SET.CPP file included in this article. Don't worry - it is not too difficult.

// SET.H  -- Set operations a-la Pascal
typedef unsigned int Word;
typedef unsigned char Byte;
const Word ByteSize = 8;
class Set { // Set operations a-la Pascal
  Byte *BitStr;
  Word  BitStrSize;
  Word  BitsInUse;
public:
  Set(Word MaxBits);
  Set(const Set & TheSet);
  virtual ~Set();
  int SetSize(void);
  void ClearSet(void);
  int operator[](Word BitNum);          // test if a bit is present
  Set & operator =(const Set &TheSet);  // assignment of sets
  Set & operator <<(Word BitNum);       // add a bit
  Set & operator >>(Word BitNum);       // remove a bit
  int   operator ==(Set &TheSet);       // compare sets
  int   operator !=(Set &TheSet);       // compare sets
  void AddBitSet(Set &TheSet);
  void RemoveBitSet(Set &TheSet);
}; // class Set
In SET.CPP
//         SET.CPP  -- Set operations a-la Pascal
//        Written by:  John Plate <plate@infotek.dk>
// Date:        August 1995
// Compiler:    Borland Turbo C++
// Copyright:   The code is in the public domain
#include "set.h"
#include <values.h>
#include <mem.h>
Set::Set(Word MaxBits) { // MaxBits is number of bits in the Set
  BitsInUse = MaxBits;
  if (MaxBits > MAXINT || MaxBits == 0) { // i.e. impossible values  
    BitStr = 0;
    BitStrSize = 0;
  }
  else {
    BitStrSize = (BitsInUse - 1) / ByteSize + 1;
    BitStr = new Byte(BitStrSize);
    ClearSet();
  }
} // constructor
Set::Set(const Set & TheSet){ // Copy constructor
  BitStr = new Byte[BitStrSize = TheSet.BitStrSize];
  BitsInUse = TheSet.BitsInUse;
  if (BitStr) memcpy(BitStr, TheSet.BitStr, BitStrSize);
} // Copy constructor
Set::~Set(){ // De-allocate the BitStr array
  if (BitStr) delete BitStr;
} // destructor
void Set::ClearSet(void){ // Zero fill the whole Set one byte at a time
  for (int i = 0; i < BitStrSize; i++) BitStr[i] = 0;
} // ClearSet
int Set::SetSize(void){ // Return the bits used, i.e. the size of the Set
  return(BitsInUse);
} // SetSize
int Set::operator[](Word BitNum){ // Return the value of bit BitNum. 
                                  // Notice: Zero is returned if BitNum is not within the Set.
  if (BitNum <= BitsInUse) {
    return(BitStr[BitNum / ByteSize] & (1 << (BitNum % ByteSize)));
  }
  else return(0);
} // operator []
Set & Set::operator =(const Set & TheSet){ //copy assignment 
  if (this != &TheSet) {
    delete(BitStr);
    BitStr = new Byte[BitStrSize = TheSet.BitStrSize];
    BitsInUse = TheSet.BitsInUse;
    if (BitStr) memcpy(BitStr, TheSet.BitStr, BitStrSize);
  }
  return(*this);
} // operator =
int Set::operator ==(Set & TheSet){ // Compare two sets
  int Equal = BitStrSize == TheSet.BitStrSize &&
    BitsInUse == TheSet.BitsInUse &&
    memcmp(BitStr, TheSet.BitStr, BitStrSize) == 0;
  return(Equal);
} // operator ==
int Set::operator !=(Set & TheSet){ // Compare two sets
  return(! (*this == TheSet));
} // operator !=
Set & Set::operator <<(Word BitNum){ // Set the bit, return 'this Set'
  if (BitNum < BitsInUse) {
    BitStr[BitNum / ByteSize] |= (1 << (BitNum % ByteSize));
  }
  return(*this);
} // operator <<
Set & Set::operator >>(Word BitNum){ // Remove the bit, return 'this Set'
  if (BitNum < BitsInUse) {
    BitStr[BitNum / ByteSize] &= ~(1 << (BitNum % ByteSize));
  }
  return(*this);
} // operator >>
void Set::AddBitSet(Set &TheSet){ 
    // Add bits from TheSet, i.e. perform an OR operation on both sets.
    // Notice that the sets may be of different size.
  int BitsUsed = min(TheSet.BitsInUse, BitsInUse);
  int Bytes = BitsUsed / ByteSize;     // no of whole bytes
  for (int i = Bytes; i >= 0; i--) BitStr[i] |= TheSet.BitStr[i];
  int Bits = BitsInUse % ByteSize;     // the rest
  if (Bits > 0) { // mask out relevant bits before the OR operation
    BitStr[Bytes+1] |= (~0 << (ByteSize - Bits)) & TheSet.BitStr[Bytes+1];
  }
} // AddBitSet
void Set::RemoveBitSet(Set &TheSet){
  // This left as an excercise for the reader :-)
  // Hint: See the functions above.
} // RemoveBitSet

Finally, something to think about. What operators should be provided (if any) and how would they best be provided. Perhaps the design experts might like to critique the design. Any thoughts on the variety of ways that << and >> are used in the context of this design? Material such as this is presented to provoke your thoughts and develop ideas and understanding, not as recipes for definitive answers. Francis.

Notes: 

More fields may be available via dynamicdata ..