Data Structures & Algorithms, Fall 2008

Programming Assignment 2 - An Example Solution


Table of Contents

Introduction

This page gives a solution to the second programming assignment. The solution's simplicity reflects the problem's simplicity:
<main()>= (U→)

int 
main() 

  std::string tag
  string_stack tags

  while read_tag(std::cin, tag)
    if open_tag(tag)
      tags.push(tag)
    else 
      if tags.empty()
        err(std::string("No matching open tag for ") + tag)
      const std::string t = tags.pop()
      if not paired_tags(t, tag)
        err(std::string("Tag mismatch between ") + t + " and " + tag)

  if not tags.empty()
    err(std::string("Tag ") + tags.pop() + " unmatched at end-of-input")

  exit(EXIT_SUCCESS)
Defines main (links are to index).

And the rest.
<main.cc>=

#include <string>
#include <iostream>
#include <cstdlib>
#include <cassert>
#include "string-stack.h"


// Tag elements.

   const char
     tag_open_mark = '[',
     tag_close_mark = ']',
     tag_end_mark = '/'


// Deja vu c++ style.

   static bool read_tag(std::istream &, std::string &)
   static bool open_tag(const std::string &)
   static bool paired_tags(const std::string &, const std::string &)
   static bool skip_until(std::istream &, char)
   static std::string read_until(std::istream &, char)
   static std::string trim(const std::string &)
   static void err(const std::string &)


static void
check_tag(const std::string & tag) 
  
  // Check the given tag for syntax; die with an error if anything's wrong.

  if tag.empty()
    err("Empty tag")

  size_t s = 0
  if tag[0] == tag_end_mark
    s++
  const size_t n = tag.size()

  while ((s < n) and isalnum(tag[s++])) { }

  if s < n
    err("Non-alphanumeric character in tag name")


static void
err(const std::string & emsg) 

  // Print the given error message and die.

  std::cerr << "! " << emsg << ".\n"
  exit(EXIT_FAILURE)


#ifdef MAINTESTING

  // g++ -o maintesting -g -DMAINTESTING -ansi -pedantic -Wall main.cc ∧ ./maintesting

  int 
  main() 
    assert(open_tag("joe"))
    assert(not open_tag("/joe"))
    assert(paired_tags("joe", "/joe"))
    assert(not paired_tags("joe", "/blow"))
    std::string str = "hello"
    assert(trim(std::string("\n ") + str + " \n") == str)
    assert(trim(str + " \n") == str)
    assert(trim(std::string("\n ") + str) == str)
    assert(trim(str) == str)

#else

  <main()>

#endif


static bool
open_tag(const std::string & tag) 

  // Return true iff the given tag is an open tag.

  return not tag.empty() ∧ (tag[0] ≠ tag_end_mark)


static bool 
paired_tags(const std::string & otag, const std::string & ctag) 

  // Return true iff the given tags are a matched open-close pair.

  return 
        open_tag(otag) 
    and not open_tag(ctag) 
    and not ctag.empty()
    and (ctag.compare(1, ctag.size() - 1, otag) == 0)


static bool
read_tag(std::istream & is, std::string & tag) 

  // Read from the given input stream the next tag and store it in the given
  // reference.  Return true of a tag was read; false otherwise

  if not skip_until(is, tag_open_mark)
    return false

  tag = trim(read_until(is, tag_close_mark))
  check_tag(tag)

  return true


static std::string
read_until(std::istream & is, char c) 

  // Read and store characters from the given input stream until given
  // character is read (which is not stored) or there are no more characters to
  // read.  The characters read are stored in the string returned.

  std::string tag
  const unsigned n = 16
  char t[n]
  unsigned i = 0

  while ((t[i++] = is.get()) ≠ c) ∧ (is.gcount() > 0)
    if i ≥ n
      tag.append(t, n)
      i = 0
  if is.gcount() < 1
    err(std::string("Unexpected eof while searching for '") + c + "'")

  assert((i > 0) ∧ (t[i - 1] == c))
  tag.append(t, i - 1)

  return tag


static bool
skip_until(std::istream & is, char c) 

  // Read and toss characters from the given input stream until given character
  // is read (which is also tossed) or there are no more characters to read.
  // Return true iff the given character was read.

  while ((is.get() ≠ c) ∧ (is.gcount() > 0)) { }

  return is.gcount() > 0
  

static std::string
trim(const std::string & str) 

  // Return a copy of the given string stipped of all leading and trailing
  // space characters.

  const char * const space_chars = "\n \t\v"

  size_t s = str.find_first_not_of(space_chars)
  if s == std::string::npos
    s = 0

  size_t e = str.find_last_not_of(space_chars)
  if e == std::string::npos
    e = str.size()
  else
    e++

  return str.substr(s, e - s)
Defines main.cc (links are to index).

A String Stack

A Static String Stack

<strstk public methods>= (U→)

// Return true iff this stack is empty.

   bool empty() const


// Return true iff this stack is full.

   bool full() const


// Pop and return the top string in this stack; die with an error message 
// if there's no string to pop.

   std::string pop()


// Push the given string on this stack; die with an error message if
// there's no more room.

   void push(const std::string & str)


// Create a new empty stack capabile of holding the given number of 
// strings; die with an error message if the given number of strings isn't
// positive.

   strstk(unsigned size)


// Get rid of this stack.

  ~strstk()

The data used to implement a static string stack is unsurprising: an array of strings and a pointer to the next unused array location.
<strstk.h>=

#ifndef _strstk_h_defined_
#define _strstk_h_defined_

#include <cassert>
#include <string>

class strstk 

  public:

    <strstk public methods>

  private:

    // The stacked strings.
    // Invariant:  strings ≠ 0.

       std::string * strings


    // Where the next string pushed goes.
    // Invariant:  0 ≤ next_top ≤ max.

       unsigned next_top


    // How many strings can be pushed.
    // Invariant:  0 < max.

       unsigned max


    // Fill out the rule of three.

       strstk(const strstk &) { }
       strstk operator = (const strstk &) { return *this; }


#endif
Defines strstk.h (links are to index).

The implementation is equally unsurprising, which is the point:
<strstk.cc>=

#include "strstk.h"


bool strstk::
empty() const 
  return next_top == 0


bool strstk::
full() const 
  return next_top == max


std::string strstk::
pop() 
  assert(not empty())
  return strings[--next_top]


void strstk::
push(const std::string & str) 
  assert(not full())
  strings[next_top++] = str


strstk::
strstk(unsigned size) 
  : strings(new std::string [size]), next_top(0), max(size) 
  assert(size > 0)


strstk::
~strstk() 
  delete [] strings


#ifdef STRSTKTESTING

// g++ -o strstktesting -g -DSTRSTKTESTING -ansi -pedantic -Wall strstk.cc ∧ ./strstktesting

int 
main() 
  strstk sstk(16)
  assert(not sstk.full())
  assert(sstk.empty())
  std::string str = "hello"
  sstk.push(str)
  assert(not sstk.full())
  assert(not sstk.empty())
  assert(sstk.pop() == str)
  assert(not sstk.full())
  assert(sstk.empty())

  std::string str2 = "bye"

  sstk.push(str)
  sstk.push(str2)
  assert(sstk.pop() == str2)
  assert(sstk.pop() == str)
  assert(sstk.empty())

#endif
Defines strstk.cc (links are to index).

A Dynamic String Stack

The dynamic string stack presents just the methods needed to solve the problem other methods can be added as needed by other problems.
<string-stack public methods>= (U→)

// Return true iff this string stack is empty.

   bool empty() const


// Pop and return the top of this string stack; die with an error if the
// stack's empty.

   std::string pop()


// Push the given string on the top of this string stack.

   void push(const std::string &)


// Create a new, empty string stack.

   string_stack()


// Get rid of this string stack.

  ~string_stack()

Being, in reality, a linked list, the string stack needs a definition of a link-list element and a pointer to the first element, if it exists, of the list.
<string-stack.h>=

#ifndef _string_stack_h_defined_
#define _string_stack_h_defined_

#include <string>
#include "strstk.h"

class string_stack 

  public:
  
    <string-stack public methods>

  private:

    // A link in the string-stack list.

       struct strstk_link 

         strstk_link * next
         strstk sstk

         strstk_link(unsigned size, strstk_link * next)
           : next(next), sstk(size) 


    // The head of the string-block list.
    // Invariant: strstk_head ≠ 0 → !strstk_head→sstk.empty().

       strstk_link * strstk_head


    // Fill out the rule of three.

       string_stack(const string_stack &) { }
       string_stack operator = (const string_stack &) { return *this; }

#endif
Defines string-stack.h (links are to index).

The implementation
<string-stack.cc>=

#include "string-stack.h"


bool string_stack::
empty() const 
  return (strstk_head == 0)


std::string string_stack::
pop() 

  assert(not empty() and not strstk_head→sstk.empty())
  std::string str = strstk_head→sstk.pop()

  if strstk_head→sstk.empty()
    strstk_link * h = strstk_head→next
    delete strstk_head
    strstk_head = h

  return str


void string_stack::
push(const std::string & str) 
  if (strstk_head == 0) ∨ (strstk_head→sstk.full())
    strstk_head = new strstk_link(16, strstk_head)
  strstk_head→sstk.push(str)


string_stack::
string_stack()
  : strstk_head(0) 


string_stack::
~string_stack() 
  strstk_link * s = strstk_head
  while s
    strstk_link * n = s→next
    delete s
    s = n


#ifdef STRINGSTACKTESTING

// g++ -o stringstacktesting -g -DSTRINGSTACKTESTING -ansi -pedantic -Wall string-stack.cc strstk.cc ∧ ./stringstacktesting

int 
main() 
  string_stack sstk
  assert(sstk.empty())
  std::string str = "hello"
  sstk.push(str)
  assert(not sstk.empty())
  assert(sstk.pop() == str)
  assert(sstk.empty())

  std::string str2 = "bye"

  sstk.push(str)
  sstk.push(str2)
  assert(sstk.pop() == str2)
  assert(sstk.pop() == str)
  assert(sstk.empty())

#endif

// $Log: string-stack.cc,v $
// Revision 1.1  2008/12/17 06:14:47  rclayton
// Initial revision
//
Defines string-stack.cc (links are to index).

Index


This page last modified on 6 November 2008.

Creative
    Commons License