NivaarExam PrepOfficial exam papers ↗

25-Comp-A4 Program Design and Data Structures · May 2016

Question 3 of 8: Object-Oriented Design — A C++ Set Class

Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)

Notes on this paper

Paper format. 98-Comp-A4 Program Design and Data Structures, May 2016 — 3 hours, closed book, no calculator permitted. Eight questions of equal weight (20 marks each: some split as (a) 10 + (b) 10); candidates answer any five, so a complete paper is 100 marks. Pseudocode or any high-level language is accepted, and the examiner's note states explicitly that marking emphasises the operation of the program, not syntactic details. All eight questions are answered below, because the whole set is the more useful revision resource. Answers are given in C or C++ as the question dictates; each is compilable as written, but a clear, correctly reasoned pseudocode answer would earn the same marks.

Reference texts for this subject.

  • Cormen, Leiserson, Rivest & Stein, Introduction to Algorithms, 4th ed. — tree traversals (ch. 12), sorting and Quicksort (ch. 7), asymptotic analysis (ch. 3).
  • Weiss, Data Structures and Algorithm Analysis in C, 2nd ed. — linked lists (ch. 3), binary search trees (ch. 4).
  • Deitel & Deitel, C++ How to Program, 10th ed. — class design and templates (ch. 9–12), file streams (ch. 14).
  • Kernighan & Ritchie, The C Programming Language, 2nd ed. — character/array I/O idioms (ch. 1, 7), pointers and structures (ch. 5–6).
  • Stroustrup, The C++ Programming Language, 4th ed. — class templates and value semantics (ch. 3, 25–27).

The Computer Engineering citation list is built around architecture and networking texts (Patterson & Hennessy, Tanenbaum, Mano); this subject is programming and data structures, so the works above are cited instead.

Question 3: Object-Oriented Design — A C++ Set Class (20 marks)

Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.

Given. No numeric data; a design requirement for a templated class supporting empty and pre-populated construction, member addition, deletion and membership test, over any numeric element type.

Find. A Set<T> class, split into Set.h (declaration) and Set.cc (implementation), whose member functions never allow a duplicate element to be stored.

Approach. A class template Set<T> wraps a dynamically resized array holding the current elements and a count of how many are in use. add is the only place duplicates are guarded against — every other operation (delete, membership test) is a plain linear scan over that array. Assumption: mathematical set operations beyond the three named (union, intersection, etc.) are out of scope, since the question lists exactly addition, deletion and membership; elements are compared with ==, which is why the class is restricted to number types (as stated) rather than arbitrary objects.

  1. Choose the internal representation. A hash table would give faster membership tests, but the question is explicitly about supplying what C++ lacks natively with the simplest correct mechanism, and a class syllabus at this level expects a dynamic array: it demonstrates manual memory management (the point of the exercise) without extra machinery. Capacity doubles when full, exactly like a minimal std::vector.
  2. Write Set.h. The Big Three (copy constructor, copy assignment, destructor) are declared because the class owns a raw T*; omitting them would let the compiler-generated shallow copy double-free the same buffer when a Set is copied.
    // Set.h
    #ifndef SET_H
    #define SET_H
    
    template <class T>
    class Set {
    public:
        Set();                                  // empty set
        Set(const T *elems, int n);             // initialized with n elements
        Set(const Set &other);                  // copy constructor
        Set &operator=(const Set &other);       // copy assignment
        ~Set();
    
        void add(const T &value);               // no-op if value already present
        void remove(const T &value);            // no-op if value not present
        bool contains(const T &value) const;
        int  size() const { return count; }
    
    private:
        T   *items;
        int  count;
        int  capacity;
        void grow();                            // doubles capacity
    };
    
    #include "Set.cc"   // template definitions must be visible at the point of use
    #endif
  3. Write Set.cc. Every mutator funnels through contains, so the "no duplicates" invariant is enforced in exactly one place.
    // Set.cc
    #include "Set.h"
    
    template <class T>
    Set<T>::Set() : items(nullptr), count(0), capacity(0) {}
    
    template <class T>
    Set<T>::Set(const T *elems, int n) : items(nullptr), count(0), capacity(0)
    {
        for (int i = 0; i < n; i++)
            add(elems[i]);                      // add() rejects duplicates in the input too
    }
    
    template <class T>
    Set<T>::Set(const Set &other)
        : items(new T[other.capacity]), count(other.count), capacity(other.capacity)
    {
        for (int i = 0; i < count; i++) items[i] = other.items[i];
    }
    
    template <class T>
    Set<T> &Set<T>::operator=(const Set &other)
    {
        if (this != &other) {
            delete[] items;
            capacity = other.capacity;
            count    = other.count;
            items    = new T[capacity];
            for (int i = 0; i < count; i++) items[i] = other.items[i];
        }
        return *this;
    }
    
    template <class T>
    Set<T>::~Set() { delete[] items; }
    
    template <class T>
    void Set<T>::grow()
    {
        int newCap = (capacity == 0) ? 4 : capacity * 2;
        T *bigger = new T[newCap];
        for (int i = 0; i < count; i++) bigger[i] = items[i];
        delete[] items;
        items = bigger;
        capacity = newCap;
    }
    
    template <class T>
    void Set<T>::add(const T &value)
    {
        if (contains(value)) return;            // the ONE place duplicates are blocked
        if (count == capacity) grow();
        items[count++] = value;
    }
    
    template <class T>
    void Set<T>::remove(const T &value)
    {
        for (int i = 0; i < count; i++) {
            if (items[i] == value) {
                items[i] = items[count - 1];    // swap-with-last: O(1), order not guaranteed
                count--;
                return;
            }
        }
    }
    
    template <class T>
    bool Set<T>::contains(const T &value) const
    {
        for (int i = 0; i < count; i++)
            if (items[i] == value) return true;
        return false;
    }
    
    // Explicit instantiations for the number types this Set is required to support.
    template class Set<int>;
    template class Set<float>;
    template class Set<double>;
  4. Confirm the contract with example usage.
    Set<int> s;                    // empty set
    s.add(3); s.add(1); s.add(3);  // second add(3) is a no-op: size() stays 2
    s.contains(1);                 // true
    s.remove(1);
    s.contains(1);                 // false
    
    int init[] = {2, 4, 2, 6};
    Set<int> t(init, 4);           // constructs {2, 4, 6}: duplicate 2 collapsed
    $$\boxed{\text{size after } s.add(3);\,s.add(1);\,s.add(3); \;=\; 2}$$
Question 3 — results
Operation sequenceResulting set / value
add(3); add(1); add(3){3, 1} — size 2 (duplicate rejected)
contains(1) then remove(1); contains(1)true, then false
Set<int> t({2,4,2,6}, 4){2, 4, 6} — size 3
Instantiated element typesint, float, double