Re-doing my college data structures homeworks for no reason

date: 1/13/2025

Over the last 2 weeks, I've been redoing my Data Structures homework from college to keep up my C++ skills. It has actually been super rewarding, and so far, rather than spending the better part of a week on it, I can knock each of them out in an afternoon. I've learned so much since then, and when I first got my current job, I actually coded in C++ quite a bit! I tried to use ChatGPT as little as possible, and mostly used it to explore topics further that I already implemented (memory management mostly). Anyways, below are the first 4 homeworks  (minus the third one that one seemed boring).

Homework 1-Ascii Art

homework pdf

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <cassert>
#include <string>
#include <iomanip>
#include <cmath>
#include <cstdlib>
#include <map>
#include <algorithm>


void LOG(const std::string_view message){
  std::cout<< message << std::endl;
}


// ======================================================================================

// Helper function to read the provided font from a file.  The format
// of the font file is described in comments below.  The width,
// height, and bitmap_letters variables are set by this function.
void ReadFont(const std::string &font_file, 
	      int &width,
	      int &height,
	      std::vector<std::vector<std::string> > &bitmap_letters) {

  // open the font file for reading
  std::ifstream istr(font_file.c_str());
  if (!istr) { 
    std::cerr << "ERROR: cannot open font file " << font_file << std::endl; 
    exit(0);
  }

  // read in the width & height for every character in the file
  istr >> width >> height;
  assert (width >= 1);
  assert (height >= 1);

  // Create a vector to store all 256 ASCII characters of the
  // characters.  Each character is represented as a vector of
  // <height> strings that are each <width> wide.  Initially the
  // characters are unknown (represented with the '?' character).
  bitmap_letters = std::vector<std::vector<std::string> > 
    ( 256, std::vector<std::string> ( height, std::string(width, '?')));

  // read in all the characters
  // first is the ascii integer representation of the character
  int ascii;
  while (istr >> ascii) {
    assert (ascii >= 0 && ascii < 256);
    // next the character is printed in single quotes
    char c;
    istr >> c;
    assert (c == '\'');
    // use std::noskipws to make sure we can read the space character correctly
    istr >> std::noskipws >> c;
    // verify that the ascii code matches the character
    assert (c == (char)ascii);
    // switch back to std::skipws mode
    istr >> std::skipws >> c;
    assert (c == '\'');
    // read in the letter
    std::vector<std::string> bitmap;
    std::string tmp;
    for (int i = 0; i < height; i++) {
      istr >> tmp;
      assert ((int)tmp.size() == width);
      // make sure the letter uses only '#' and '.' characters
      for (unsigned int j = 0; j < tmp.size(); j++) { 
        assert (tmp[j] == '.' || tmp[j] == '#'); 
      }
      bitmap.push_back(tmp);
    }
    // overwrite the initially unknown letter in the vector
    bitmap_letters[ascii] = bitmap;
  }
}

// ======================================================================================


std::string GetMessageString(std::vector<std::vector<std::string> > &input, const std::string &message, int height){
    std::string ret;
    for(int row = 0; row< height; row++){
        for(int letter = 0; letter< message.length(); letter++){
            ret+=input[(int)message[letter]][row];
        }
        ret += '\n';
    }
    return ret;
}


bool checkArrEqual(const std::vector<std::string> &v1, const std::vector<std::string> &v2){
  if (v1.size() != v2.size()) return false;
    // Checking all elements of vector equal or not
    for (int i = 0; i < v1.size(); i++) {
        if (strcmp(v1[i].c_str(), v2[i].c_str())!=0) return false;
    }
    return true;
}
std::vector<std::vector<std::string> > GetLetterVecFromFile(std::string filePath, int width, int height){
  
  std::ifstream istr(filePath.c_str());
  if (!istr) { 
    std::cerr << "ERROR: cannot open font file " << filePath << std::endl; 
    exit(0);
  }
  std::string tmp;
  std::map<int, std::vector<std::string> > letterMap;
  while(std::getline(istr, tmp)){
    for(int i = 0; i< tmp.length()/(width+1); i++){
      if(letterMap.count(i) == 0){
        letterMap.emplace(i, std::vector<std::string>());
      }
      const auto letterVecItr = letterMap.find(i);
      if(letterVecItr != letterMap.end()){
        auto &letterVec = (*letterVecItr).second;
        letterVec.push_back(tmp.substr(i*width + i, width));
      }
      
    }
  }
  std::vector<std::vector<std::string> > ret;
  std::transform( letterMap.begin()
  , letterMap.end()
  , std::back_inserter( ret )
  , [](const std::pair<int, std::vector<std::string> > &entry){
    return entry.second;
  } );

  return ret;
}
void Part2(int argc, char* argv[]){
  const char* sourceFile = argv[2];
  const char* asciiMessage = argv[3];
  int w;
  int h;
  std::vector<std::vector<std::string> > bitmap_letters;
  ReadFont(sourceFile, w, h, bitmap_letters);
  auto messageLetterVec = GetLetterVecFromFile(asciiMessage, w, h);
  //do string replacement
  for(auto &letter: messageLetterVec){
    for(auto &row: letter){
      std::replace(row.begin(), row.end(), '@', '#');
      std::replace(row.begin(), row.end(), '|', '#');
      std::replace(row.begin(), row.end(), '*', '#');
      std::replace(row.begin(), row.end(), 'X', '#');
      std::replace(row.begin(), row.end(), '_', '.');
      std::replace(row.begin(), row.end(), ' ', '.');
    }
  }
  std::string ret;
  for(const auto vec: messageLetterVec){
    for(int i = 0; i< bitmap_letters.size(); i++){
      if(checkArrEqual(bitmap_letters[i], vec)){
        ret+=std::string(1, static_cast<char>(i));
      }
    }
  }
  LOG(ret);



}

void Part1(int argc, char* argv[]){
    const char* fileName = argv[2];
    const char* message = argv[3];
    const char foregroundChar = argv[4][0];
    const char backgroundChar = argv[5][0];
    

    int w;
    int h;
    std::vector<std::vector<std::string> > bitmap_letters;
    ReadFont(fileName, w, h, bitmap_letters);
    std::string asciiMessage= GetMessageString(bitmap_letters, message, h);
    std::replace(asciiMessage.begin(), asciiMessage.end(), '#', foregroundChar);
    std::replace(asciiMessage.begin(), asciiMessage.end(), '.', backgroundChar);
    
    LOG(asciiMessage);
}

int main(int argc, char* argv[])
{
    const char* commandString = argv[1];
    if(strcmp(commandString, "display") == 0){
       Part1(argc, argv);
    }else if(strcmp(commandString, "read") == 0){
      Part2(argc, argv);
    }
}

I can fit this one all one script. It was pretty easy, and a good reminder that the hardest parts of these homeworks was honestly the formatting.

Homework 2- Decathlon scoring 

Did you know the decathlon scoring is really arbitrary (jk it probably is actually very smart).

homework pdf

link to da github

This one was fun! really satisfying once I got the formatting working out.

 

 

 

homework 3 was boring and I already did it back in 2017..

Homework 4- jagged array

homework pdf

Link to da github

This one was understandibly the hardest one so far. I was taught immedietly at my job to NEVER use raw pointers because you will fuck up memory management and boy did I. jkjk I actually had a pretty good understanding of all of the bugs and did this one actually pretty quickly!! The hardest part was doing the assignment operator and the copy constructor.

void copy(const JaggedArray& other){
        size = other.size;
        num_elements = other.num_elements;
        is_packed = other.is_packed;
        unpacked_values = nullptr;
        if(other.unpacked_values!=nullptr){
            unpacked_values = new T*[size];
            for(int i = 0; i< size; i++){
                int numElems = other.numElementsInBin(i);
                unpacked_values[i] = new T[numElems];
                for(int j = 0; j< numElems; j++){
                    unpacked_values[i][j] = other.unpacked_values[i][j];
                }
            }
        }
        counts = nullptr;
        if(other.counts!=nullptr){
            counts = new int[size];
            for(int i = 0; i< size; i++){
                counts[i] = other.counts[i];
            }
        }
        
        offsets = nullptr;
        if(other.offsets!=nullptr){
            offsets = new int[size];
            for(int i = 0; i< size; i++){
                offsets[i] = other.offsets[i];
            }
        }
        packed_values = nullptr;
        if(other.packed_values!=nullptr){
            packed_values = new T[num_elements];
            for(int i = 0; i< num_elements; i++){
                packed_values[i] = other.packed_values[i];
            }
        }
    }
    public:
    JaggedArray(int _size);
    JaggedArray& operator=(JaggedArray& other){

        if (this != &other) {
            this->~JaggedArray();
            this->copy(other);
        }
        return *this;
    }
    JaggedArray (const JaggedArray& other) {
        
        this->copy(other);
    }
    ~JaggedArray() {
        if(unpacked_values!=nullptr){
            for(int i = 0; i< size; i++){
                if(unpacked_values[i] != nullptr){
                    delete[] unpacked_values[i];
                }
            }
            delete[] unpacked_values;
        }
        if(counts != nullptr){
            delete[] counts;
        }
        if(offsets != nullptr){
            delete[] offsets;
        }
        if(packed_values != nullptr){
            delete[] packed_values;
        }
    }

I ended up making a copy helper function which copies one JaggedArray to another, and then when I do assignment, I just use that copy constructor. I initially messed up by forgetting to call the destructor on itself first, which resulted in memory leaks since copying just creates new allocations without cleaning up old allocations. Anyways this was actually such a great exercise for understanding allocation/deallocation better, but it also made me very thankful for smart pointers!

Oh last thing, in order to compile and run it I used a shell script which I wish I figured out in college, for example, here is the shell script for homework 4.

#!/bin/bash

# Variables
CXX=g++
CXXFLAGS="-Wall -std=c++20"
OUTPUT="homework4"
SOURCES="main.cpp jagged_array.cpp"

# Compile
echo "Compiling..."
$CXX $CXXFLAGS -o $OUTPUT $SOURCES

# Check success
if [ $? -eq 0 ]; then
    echo "Compilation successful. Running $OUTPUT"
    echo "---------------------------------------"
    # ./$OUTPUT
    leaks --atExit -- ./$OUTPUT
else
    echo "Compilation failed."
fi

this was nice since I can build and run in one command.