CS140  Lab 5
 CS140  Data Structures
 James S. Plank
 Spring, 2014
 Directory for files, makefile, executables and gradescript:
/home/plank/cs140/Labs/Lab5
 This is a two week lab, but parts of it are due after week 1.
 For week 1, cases 128 and 101120 must work (you might find that
a few other cases work, but that will be pure luck).
You are not allowed...
You are not allowed to modify bitmatrix.h or
bitmatrix_editor.cpp.
Bitmatrices
This lab is all about bitmatrices. These are matrices whose values can only be
zero or one, and arithmetic is modulo 2. You would be surprised how powerful
these are. They are used in faulttolerant storage applications, among other
things, which is why I am fond of them.
Let's take some simple examples. Here are four bitmatrices:
When you add bitmatrices, you simply add elements in corresponding rows and columns, modulo
two. So, for example:
and
Bitmatrix multiplication is just like standard matrix multiplication,
except that addition and multiplication of the individual elements is
done modulo two. For example, multiplying
RV.txt and
CV.txt will yield a 1 X 1 matrix whose value is:
(1*1) + (0*0) + (1*1) + (0*1) + (0*1) + (0*0) + (0*0) + (1*1)
This is 1+0+1+0+0+0+0+1 which equals 3%2 which equals 1. Thus:
Similarly:
and
That last example is interesting  when the product of two square matrices
is equal to the identity matrix, then these two matrices are inverses
of each other. Some matrices, like the one below, have no inverses:
Bitmatrix.h
You have a big job. Behold
bitmatrix.h:
#include <string>
#include <iostream>
#include <vector>
using namespace std;
class Bitmatrix {
public:
Bitmatrix(string fn); // Read from a file
Bitmatrix(int rows, int cols); // Create an empty bitmatrix
void Print(int w); // Print it on standard ouptput with spaces & blank lines
void Write(string fn); // Write it to a file either as hex or zeros & ones
void PGM(string fn, int pixels, int border); // Write it to a pgm file
int Rows();
int Cols();
void Set(int row, int col, char val); // Set the specified element to val
char Val(int row, int col); // Return the specified element
void Swap_Rows(int r1, int r2);
void R1_Plus_Equals_R2(int r1, int r2);
Bitmatrix *Copy();
protected:
vector <string> M; // The matrix. Elements are '0' or '1'.
};
Bitmatrix *Sum(Bitmatrix *a1, Bitmatrix *a2);
Bitmatrix *Product(Bitmatrix *a1, Bitmatrix *a2);
Bitmatrix *Sub_Matrix(Bitmatrix *a1, vector <int> &rows);
Bitmatrix *Inverse(Bitmatrix *m);
class HTE {
public:
string key;
Bitmatrix *bm;
};
typedef vector <HTE *> HTVec;
class BM_Hash {
public:
BM_Hash(int size);
void Store(string &key, Bitmatrix *bm);
Bitmatrix *Recall(string &key);
HTVec All();
protected:
vector <HTVec> table;
};

The Bitmatrix class implements methods that allow you to create, read, write
and manipulate bitmatrices. Here are the methods:
 Bitmatrix(int rows, int cols): Create a bitmatrix of the specified size
whose entries are all zeros. If rows or cols is less than or equal to
zero, print an error message, but still create a 1 X 1 bitmatrix whose entry is zero.
 Bitmatrix(string fn): Read a bitmatrix from a file. The file should contain
only zeros, ones, spaces and newlines, and each line should either be blank, or it
should contain the same number of nonspaces as all other lines. I've written this
one for you, so you don't need to sweat about it too much.
 void Write(string fn): Write the bitmatrix to a file. Here, just write
out the zeros and ones with no spaces. Each row of the bitmatrix gets one row of output.
 void Print(int w): Print the matrix on standard output. If w is less than
or equal to zero, print each row on its own line with no spaces. Otherwise, print a space
after every w columns and a blank line after every w rows. I've written
this one for you.
 void PGM(string fn, int pixels, int border): Create a PGM file from the bitmatrix.
This is how I made the pictures above. The zero entries are white (255) and the
one entries are gray (100). Each entry is a pixels by pixels square. If border
is greater than zero, then there should be a black border of that many pixels separating each
square and around the whole matrix. The above matrices were created with PGM(xxx, 10, 2).
 int Rows(): Return the number of rows in the bitmatrix.
 int Cols(): Return the number of columns in the bitmatrix.
 void Set(int row, int col, char val): Set the element in the specified row and
column (zeroindexed, of course) to the given value. Val can be either 0 or '0' to
specify zero, and either 1 or '1' to specify one. The character your program
actually stores should be a '0' or a '1'. Hence if val is either the
integer 0 or 1, as opposed to the integer 48 or 49, which represent '0' and
'1', then
you need to convert it to '0' or '1'. You can do so by adding '0' to val.
If row or col are bad,
simply do nothing  your code should not be erroneous if given bad values.
 char Val(int row, int col): Return the specified value  '0' should
be returned as the integer 0 and '1' should be returned as the integer 1. Even
though Val returns a char, you can have your return statement return the
integer 0 or the integer 1 and it will work properly.
This also means that when you use Val to retrieve a value from
a matrix, and you want to store its return value back into another matrix
(e.g., the Copy method will need to do this),
you will need to first convert the return value to the character
'0' or '1'. You can do this by
adding '0' to the return value.
Val() should return 1 if given bad input.
 void Swap_Rows(int r1, int r2): Swap the specified rows. This should do nothing
if given bad values.
 void R1_Plus_Equals_R2(int r1, int r2) Set row r1 to be the sum of
rows r1 and r2. Again, this should do nothing if given bad values. It's
ok for r2 to equal r1.
 Bitmatrix *Copy() Create a new bitmatrix that is a copy of the given bitmatrix
and return a pointer to it. To do this, you'll have to create a new empty bitmatrix of
the proper size, and then use Set() to set its elements properly.
The only piece of data in a bitmatrix is a vector of strings named M.
If M is storing a r by c matrix, then it will contain r
strings, each of which has c characters. The characters are either '0' for zero
or '1' for one.
So, you have to implement 10 methods (there are 12, but I've implemented two of them
for you), and only one of them (PGM()) is difficult. Were I you, I'd wait until the
end to do PGM().
You have to implement four procedures which operate on pointers. Since these are not
part of the data structure, you have to use Rows() Cols(), Set(),
and Val() to implement them. You should not modify the input matrices
in any way.
 Bitmatrix *Sum(Bitmatrix *a1, Bitmatrix *a2): This creates a new bitmatrix which is the sum of a1 and a2. If a1 and a2 are
not the same size, return NULL.
 Bitmatrix *Product(Bitmatrix *a1, Bitmatrix *a2): This creates a new bitmatrix which is the product of a1 and a2. This product will have a1>Rows()
rows and a2>Cols() columns. If a1>Cols()
and a2>Rows() do not match, then return NULL.
 Bitmatrix *Sub_Matrix(Bitmatrix *a1, vector <int> &rows): This
creates a new bitmatrix composed of the specified rows of the given bitmatrix.
It is ok to repeat entries in rows. However, if rows is empty or contains
bad indices, return NULL.
 Bitmatrix *Inverse(Bitmatrix *a1): Create and return the inverse of a1.
To do this, you should also use the Swap_Rows() and R1_Plus_Equals_R2() methods.
I'll go into more detail on how to invert a bitmatrix below. If a1 is not square or
not invertible, return NULL.
The first three of these are very easy. Inverse() will be tougher.
Finally, you are also going to implement a hash table to store bitmatrices with keys
that are strings. You should use the djb_hash() function from class as the
hash function and you should use separate chaining as the collision resolution
mechanism. Your hash table is a vector of vectors of pointers to HTE's (hash
table entries). Each hash table entry stores a key and a pointer to a bitmatrix.
The BM_Hash class has four methods:
 BM_Hash(int size): The constructor specifies the size of the table.
 void Store(string &key, Bitmatrix *bm): Store the given key and bitmatrix
in the hash table. If the key is already there, replace the bitmatrix with the
given one.
 Bitmatrix *Recall(string &key); return the bitmatrix corresponding to the
given key. If the key is not in the hash table, then return NULL.
 HTVec All(): Return a vector of all hash table entries in the table. The
vector should be ordered just like the hash table. In other words, suppose "A" hashes to 5,
"B" hashes to 1 and "C" hashes to 1. And suppose that "B" was added to the table before "C".
Then All() should return the HTE's in the order "B", "C", "A".
You should not call new or delete on bitmatrices when you implement any
of the hash table methods. You should call new when you create a new HTE.
That is the only time that you will call new in the hash table methods.
Starter Code
The file
bitmatrix_start.cpp
provides dummy implementations for everything in the lab, with the exception of
Bitmatrix(string fn) and Print(). The nice thing about it is that
it will compile with any program that uses bitmatrix.h. However, it won't
run correctly. It's a good starting place for you. Make sure that you
copy bitmatrix_start.cpp to bitmatrix.cpp before you submit your code!.
The testing program matrix_editor.cpp
I've written
bitmatrix_editor.cpp as a program that uses
all of the features of bitmatrix.h. You run it with an optional prompt
as a command line argument. If you don't specify a prompt, it will not print a
prompt. This is nice because you can treat the editor as an interactive editor,
or you can write scripts for it.
Copy bitmatrix_editor.cpp, bitmatrix.h and makefile to your
directory. Then
copy bitmatrix_start.cpp to bitmatrix.cpp and type make:
UNIX> make
g++ c bitmatrix_editor.cpp
g++ c bitmatrix.cpp
g++ o bitmatrix_editor bitmatrix_editor.o bitmatrix.o
UNIX>
Now you have a version of bitmatrix_editor that only works for what
I have implemented. However, let's explore those. Go ahead and copy
the following matrix files too:
C003.txt,
C244.txt,
CM4_3.txt,
CV.txt,
RV.txt,
t3.txt and
t4.txt.
Bitmatrix_editor reads lines of text from standard input. Blank lines
and lines that begin with '#' are ignored. Otherwise, the first word on a line
is a command and the remaining words are arguments.
At all times, there is one "current matrix." You may also store and recall matrices
with singleword keys. Three simple commands are:
 READ filename: This reads a bitmatrix from a given file using the Read()
method. If there is an error, it will return a singleelement matrix whose element is zero.
 PRINT [w]: This calls the Print() method on the current matrix.
If an argument is specified, it is the value of w that is passed to the Print()
method. Otherwise, 0 is passed.
 QUIT: Exits. You can also simply close standard input with CNTLD.
Since I have implemented these for you in bitmatrix_start.cpp, they will work
without any extra effort from you:
UNIX> bitmatrix_editor "BMEditor>"
BMEditor> READ C003.txt
BMEditor> PRINT
10000001
11000000
01100001
00110001
00011001
00001100
00000110
00000011
BMEditor> PRINT 4
1000 0001
1100 0000
0110 0001
0011 0001
0001 1001
0000 1100
0000 0110
0000 0011
BMEditor> READ RV.txt
BMEditor> PRINT
10100001
BMEditor> PRINT 4
1010 0001
BMEditor> QUIT
UNIX>
bitmatrix_editor also implements the following commands, which of course will only
work when you implement the proper methods and procedures:
 EMPTY rows cols: Creates a bitmatrix of the given size whose
values are all zeros. It sets the current matrix to this matrix.
 SET row col value: Sets the given element of the current
matrix to the given value.
 VAL row col: Returns the value of the given element.
 SWAP r1 r2: Swaps the given rows of the current matrix using Swap_Rows().
 += r1 r2: Sets r1 of the current matrix
equal to r1+r2 using the
R1_Plus_Equals_R2() method.
 WRITE fn: Calls the Write() method to write the current matrix
to the specified file.
 PGM fn pixels border: Calls the PGM() method
to create a PGM picture of the current matrix.
 STORE key: Makes a copy of the current matrix and stores it into the
hash table with the given key. If the key is already in the hash table, it will delete
the bitmatrix before putting the copy into the hash table.
 RECALL key: If the key is in the hash table, this will set the
current matrix to a copy of the bitmatrix stored in the hash table.
 ALL: This calls All() and prints out each key, along with
the dimensions of the bitmatrix stored there.
 SUM key1 key2: This adds the two matrices stored with the
given keys and puts the sum into the current matrix.
 PRODUCT key1 key2: This multiplies the two matrices stored with the
given keys and puts the product into the current matrix.
 SUBMATRIX row1 row2 ...: This creates a submatrix of the
current matrix with the specified rows, and replaces the current matrix with the submatrix.
 INVERT: This inverts the current matrix and replaces it with the inverse.
With these last four commands, bitmatrix_editor checks to make sure that you haven't
modified the input matrices.
Some examples with the above matrices:
UNIX> bitmatrix_editor 'BMEditor>'
BMEditor> READ C003.txt
BMEditor> STORE C003
BMEditor> READ C244.txt
BMEditor> STORE C244
BMEditor> READ CV.txt
BMEditor> STORE CV
BMEditor> READ RV.txt
BMEditor> STORE RV
BMEditor> READ t3.txt
BMEditor> STORE t3
BMEditor> READ t4.txt
BMEditor> STORE t4
BMEditor> RECALL RV
BMEditor> PRINT
10100001
BMEditor> ALL
CV 8 X 1
RV 1 X 8
t3 16 X 8
t4 8 X 16
C003 8 X 8
C244 8 X 8
BMEditor> SUM C003 C003
BMEditor> PRINT
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
BMEditor> SUM C003 C244
BMEditor> PRINT
11111110
11111111
10000001
00111110
11100001
11110000
11111000
11111100
BMEditor> PRODUCT RV CV
BMEditor> PRINT
1
BMEditor> PRODUCT C003 C244
BMEditor> PRINT
10000000
01000000
00100000
00010000
00001000
00000100
00000010
00000001
BMEditor> PRODUCT C003 CV
BMEditor> PRINT
0
1
0
1
1
1
0
1
BMEditor> RECALL t3
BMEditor> PRINT 8
10000001
11000000
01100001
00110001
00011001
00001100
00000110
00000011
01111111
00111111
11100000
00001111
11111000
11111100
11111110
11111111
BMEditor> RECALL t4
BMEditor> PRINT 8
10000001 01111111
11000000 00111111
01100001 11100000
00110001 00001111
00011001 11111000
00001100 11111100
00000110 11111110
00000011 11111111
BMEditor> PRODUCT t3 t4
BMEditor> PRINT 8
10000010 10000000
01000001 01000000
10100010 00100000
01010011 00010000
00101011 00001000
00010101 00000100
00001010 00000010
00000101 00000001
10000000 11010101
01000000 11101010
00100000 10100000
00010000 00000101
00001000 01010111
00000100 10101011
00000010 01010101
00000001 10101010
BMEditor> PRODUCT t4 t3
BMEditor> PRINT 8
01010111
10101011
00000010
01010110
01111100
10111110
01011111
10101111
BMEditor> RECALL C003
BMEditor> INVERT
BMEditor> PRINT
01111111
00111111
11100000
00001111
11111000
11111100
11111110
11111111
BMEditor> RECALL C244
BMEditor> INVERT
BMEditor> PRINT
10000001
11000000
01100001
00110001
00011001
00001100
00000110
00000011
BMEditor> PRODUCT CV RV
BMEditor> PRINT
10100001
00000000
10100001
10100001
10100001
00000000
00000000
10100001
BMEditor> INVERT
Matrix not invertible.
BMEditor> QUIT
UNIX>
Inverting a bitmatrix
Inverting a bitmatrix is easier than doing a general matrix inversion.
However, the steps are the same. Suppose you want to invert the square
matrix M. What you do is make a copy of M, and create
an identity matrix of the same size as M. Call this matrix Inv.
Then you perform "SWAP"
and "+=" operations on the copy of M to turn it into an identity matrix.
You perform the exact same operations on Inv. When you're done, the
inverse of the original matrix is in Inv
Let's perform an example on the matrix in
InvEx.txt:
10110
01011
10011
11001
01100

You first go through each row of M from the first to the last, doing the
following steps:
 Suppose you are at row i. If M[i][i] is not one, then find
a row j where j > i such that M[j][i] equals one, and
swap rows i and j. If you can't find such a row, the matrix is
not invertible.
 Now, look at every row j such that j > i. If M[j][i] equals
one, then set row j equal to the sum of rows i and j.
We'll do this for our example:
Action 
M 
Inv 
Start 


i=0
No swap necesary.
Set row 2 = row 2 + row 0. 


i=0 still.
Set row 3 = row 3 + row 0. 


i=1
No swap necesary.
Set row 3 = row 3 + row 1. 


i=1 still.
Set row 4 = row 4 + row 1. 


i=2
Swap row 2 and row 3 


i=2 still.
Set row 4 = row 4 + row 2. 


i=3
Swap row 3 and row 4 


When you're done with this pass, M will be an uppertriangular matrix.
Now, you start with the last row and go to the first row. Suppose you are
in row i:
 If there is any j > i where M[i][j] equals one, replace
row i with the sum of row i and row j.
When you are done with this step, M will be the identity matrix, and Inv
will be the inverse of the original M:
Action 
M 
Inv 
Start:
i=4
No action necessary. 


i=3
Set row 3 = row 3 + row 4. 


i=2
No action necessary. 


i=1
Set row 1 = row 1 + row 3. 


i=1 still.
Set row 1 = row 1 + row 4. 


i=0
Set row 0 = row 0 + row 2. 


i=0 still.
Set row 0 = row 0 + row 3. 


Finally, let's doublecheck ourselves:
BMEditor> READ InvEx.txt
BMEditor> STORE InvEx
BMEditor> INVERT
BMEditor> PRINT
01101
11011
11010
00111
10100
BMEditor> STORE Inv
BMEditor> PRODUCT Inv InvEx
BMEditor> PRINT
10000
01000
00100
00010
00001
BMEditor>
Gradescript Rubric
First, this lab will count double.
The gradescript will have up to 200 test cases. 30 percent of them will test matrix inversion
and 20 percent of them will test the PGM file. Your PGM doesn't have to match mine in format;
however, its size and pixels must match mine exactly.