0
\$\begingroup\$

For the problem statement shown below:

Part II(b):

Your run-length encodings will only be useful if other classes have the ability to read them after you create them. Therefore, implement the nextRun() and restartRuns() methods. These two methods work together to return all the runs in a run-length encoding to an outside application, one by one. Each time nextRun() is invoked, it returns a different run--represented as a TypeAndSize object--until every run has been returned. The first time nextRun() is invoked, it returns the first run in the encoding, which contains cell (0, 0). After every run has been returned, nextRun() returns null, which lets the calling program know that there are no more runs in the encoding.

The restartRuns() method resets the enumeration, so that nextRun() will once again return the first run as if it were being called for the first time. Warning: the test code will not necessarily call restartRuns() before the first time nextRun() is called.

IMPORTANT NOTE on nextRun() and restartRuns(): your methods in the RunLengthEncoding class should never call these methods. The nextRun() and restartRuns() methods are provided so that other classes (specifically, the Test program that autogrades your project) can read the contents of a run-length encoding. If your RunLengthEncoding methods call them, they will mess up the position of the internal pointer for the other classes.

IMPORTANT NOTE on TypeAndSize: The Java "return" keyword only allows you to return one value from a method call. But the nextRun() method needs to return two values--the length of a run, and the type of object it contains. How can it do this? Answer: by returning a "TypeAndSize" object. A TypeAndSize object is nothing more than a way to return two integers at once. That's it. Each time nextRun() is called, it creates a TypeAndSize object (or it will once you've coded it to do so), fills in the values, and returns it to the calling routine, which then throws it away. The TypeAndSize object is part of the predefined interface of your RunLengthEncoding class, so you CANNOT change it, because the calling programs (including the autograder) are relying on you to return TypeAndSize objects according to spec. TypeAndSize objects are NOT suitable as a way to represent a run in your run-length encoding, because they do not encode a shark's hunger.

Part II(c):

Implement a toOcean() method in the RunLengthEncoding class, which converts a run-length encoding to an Ocean object. To accomplish this, you will need to implement a new addShark() method in the Ocean class, so that you can specify the hunger of each shark you add to the ocean. This way, you can convert an Ocean to a run-length encoding and back again without forgetting how hungry each shark was.

Read RunLengthEncoding.java carefully for an explanation of what methods you must write. The fields of the Ocean class MUST be private, and the RunLengthEncoding class cannot manipulate these fields directly. Hence, the toOcean() method will rely upon the Ocean constructor and the addFish() and addShark() methods.

You cannot change any of the prototypes in RunLengthEncoding.java, and you cannot change the file TypeAndSize.java. Again, we will test your code by calling your methods directly.

Part III: Converting an Ocean to a Run-Length Encoding

write a RunLengthEncoding constructor that takes an Ocean object as its sole parameter and converts it into a run-length encoding of the Ocean. To accomplish this, you will need to implement a sharkFeeding() method in the Ocean class, which tells you how hungry a given shark is. Read Ocean.java and RunLengthEncoding.java carefully for an explanation of what methods you must write.

The fields of the Ocean class MUST be private, so the RunLengthEncoding constructor will rely upon the width(), height(), starveTime(), cellContents(), and sharkFeeding() methods.

Here is my solution:

RunLengthEncoding:

/* RunLengthEncoding.java */

package Project1;

/**
 *  The RunLengthEncoding class defines an object that run-length encodes an
 *  Ocean object.  Descriptions of the methods you must implement appear below.
 *  They include constructors of the form
 *
 *      public RunLengthEncoding(int i, int j, int starveTime);
 *      public RunLengthEncoding(int i, int j, int starveTime,
 *                               int[] runTypes, int[] runLengths) {
 *      public RunLengthEncoding(Ocean ocean) {
 *
 *  that create a run-length encoding of an Ocean having width i and height j,
 *  in which sharks starve after starveTime timesteps.
 *
 *  The first constructor creates a run-length encoding of an Ocean in which
 *  every cell is empty.  The second constructor creates a run-length encoding
 *  for which the runs are provided as parameters.  The third constructor
 *  converts an Ocean object into a run-length encoding of that object.
 *
 *  See the README file accompanying this project for additional details.
 */

class RunLengthEncoding {

  /**
   *  Define any variables associated with a RunLengthEncoding object here.
   *  These variables MUST be private.
   */

  private DList2 list;
  private long sizeOfRun;
  private int width;
  private int height;
  private static int starveTime;

  /**
   *  restartRuns() and nextRun() are two methods that work together to return
   *  all the runs in the run-length encoding, one by one.  Each time
   *  nextRun() is invoked, it returns a different run (represented as a
   *  TypeAndSize object), until every run has been returned.  The first time
   *  nextRun() is invoked, it returns the first run in the encoding, which
   *  contains cell (0, 0).  After every run has been returned, nextRun()
   *  returns null, which lets the calling program know that there are no more
   *  runs in the encoding.
   *
   *  The restartRuns() method resets the enumeration, so that nextRun() will
   *  once again enumerate all the runs as if nextRun() were being invoked for
   *  the first time.
   *
   *  (Note:  Don't worry about what might happen if nextRun() is interleaved
   *  with addFish() or addShark(); it won't happen.)
   */

  /**
   *  restartRuns() resets the enumeration as described above, so that
   *  nextRun() will enumerate all the runs from the beginning.
   */

  private void restartRuns() {
    this.sizeOfRun = this.list.size;
  }

  /**
   *  nextRun() returns the next run in the enumeration, as described above.
   *  If the runs have been exhausted, it returns null.  The return value is
   *  a TypeAndSize object, which is nothing more than a way to return two
   *  integers at once.
   *  @return the next run in the enumeration, represented by a TypeAndSize
   *          object.
   */

  private String nextRun() {
      String obj = null;
      if(this.sizeOfRun > 0){
          obj = this.list.nTh(this.sizeOfRun);
          this.sizeOfRun--;
      }
      return obj;
  }

  /**
   *  toOcean() converts a run-length encoding of an ocean into an Ocean
   *  object.  You will need to implement the three-parameter addShark method
   *  in the Ocean class for this method's use.
   *  @return the Ocean represented by a run-length encoding.
   */

  public Ocean toOcean() {
      Ocean sea = new Ocean(this.width, this.height, this.starveTime);
      String stringObject = null;
      String singleArray[] = new String[this.width*this.height];
      int savedIndex = 0;
      int runLength = 0;
      int tokenNumber = 0;
      /*Convert Doubly linked List of Strings to 1d array */

      //Critter critterObject = critterFactory.convertString(obj);

      while((stringObject = nextRun()) != null){
        tokenNumber = 1;  
        runLength = Utility.regexChecker("\\d{1,}", stringObject, tokenNumber);  
        for(int index = savedIndex; index < (savedIndex + runLength); index++){
            singleArray[index] = stringObject;

        }
        savedIndex += runLength;
      }

      AbstractFactory factoryObject =  FactoryProducer.getFactory();
      /* Convert 1d array to 2d array Ocean using abstract factory pattern*/
      for(int index =0; index < singleArray.length; index++){
          factoryObject.toCritterObject(sea, index, singleArray[index], this.width);
      }

      this.restartRuns();
      return sea;
  }


  /**
   *  The following method is required for Part III.
   */

  /**
   *  RunLengthEncoding() (with one parameter) is a constructor that creates
   *  a run-length encoding of an input Ocean.  You will need to implement
   *  the sharkFeeding method in the Ocean class for this constructor's use.
   *  @param sea is the ocean to encode.
   */

  public RunLengthEncoding(Ocean sea) {
      this.list = new DList2();
      this.width = sea.getWidth();
      this.height = sea.getHeight();
      RunLengthEncoding.starveTime = sea.getStarvationTime();

      int indexOf2DOcean =0;
      int sizeOfTheOcean = sea.getWidth() * sea.getHeight();
      int sameNeighborCount =1;
      String runLengthEncodeObject = null;

      while(indexOf2DOcean < sizeOfTheOcean){
          if(isSameNeighbor(sea,indexOf2DOcean)){
              sameNeighborCount++;
          }else{
              runLengthEncodeObject = sea.cellContents((indexOf2DOcean/sea.getWidth()), Utility.mod(indexOf2DOcean, sea.getWidth())).toRunLengthEncodeSegment(sameNeighborCount);
              this.list.insertFront(runLengthEncodeObject);
              this.sizeOfRun++;
              sameNeighborCount = 1;
          }
          indexOf2DOcean++;
      }

      check();
  }


  /**
   * This method checks the type of any two adjacent cells
   * "Critter" class equals() method is overridden in "Shark"
   * class to compare additional property "hungerLevel" of  
   * two adjacent sharks.
   * @param sea is a 2d array of Ocean
   * @param index of 2d Ocean
   * @return boolean
   */
  private boolean isSameNeighbor(Ocean sea, int index){
      Critter creature1 = sea.cellContents((index/sea.getWidth()), Utility.mod(index, sea.getWidth()));
      Critter creature2 = sea.cellContents(((index+1)/sea.getWidth()), Utility.mod(index+1, sea.getWidth())); 
      if( creature1.equals(creature2) ){
          return true;
      }
      return false;
  }




  /**
   *  check() walks through the run-length encoding and prints an error message
   *  if two consecutive runs have the same contents, or if the sum of all run
   *  lengths does not equal the number of cells in the ocean.
   */

  public void check() {
      DListNode2 node = this.list.sentinel.next;
      int sumOfAllRunLengths = 0;

      while(node != this.list.sentinel){
          int nodetype = Utility.regexChecker("\\D{1,}", node.runObject, 1);
          int nodeNextType = Utility.regexChecker("\\D{1,}", node.next.runObject, 1);
          if(nodetype == nodeNextType){
              System.out.println("Error message - Two consecutive runs have the same contents\n");
              return;
          }else{
              node = node.next;
          }
      }

      node = this.list.sentinel.next;
      while(node != this.list.sentinel){
          sumOfAllRunLengths += Utility.regexChecker("\\d{1,}", node.runObject, 1); 
          node = node.next;
      }

      if(sumOfAllRunLengths != this.width*this.height){
          System.out.println("Error Message");
      }
  }

}

Utility:

/* Utility.java */

package Project1;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The Utility class provides some utility functions which are used
 * by multiple classes like Fish, Shark etc...
 * @author mohet01
 *
 */
final class Utility {

/**
     * This method checks for the match of particular pattern frmo the 
     * given string
     * @param theRegex
     * @param str2Check
     */
    public static int regexChecker(String theRegex, String str2Check, int tokenNumber){
        Pattern checkRegex = Pattern.compile(theRegex);
        Matcher regexMatcher = checkRegex.matcher(str2Check);
        int count = 1;
        int result = 0;
        while(regexMatcher.find()){
            if(regexMatcher.group().length() != 0){
                result = Integer.parseInt(regexMatcher.group().trim());
                if(count == tokenNumber){
                    break;
                }
            }
            count++;
        }
        return result;
    }
}

AbstractFactory:

/* AbstractFactory.java */

package Project1;

abstract class AbstractFactory {
   abstract void toCritterObject(Ocean sea, int index, String str, int width);

}

class CritterFactory extends AbstractFactory{
    void toCritterObject(Ocean sea, int index, String str, int width){
        if(str.startsWith("S")){
            int hungerLevel = 0;
            int tokenNumber = 2;
            hungerLevel = Utility.regexChecker("\\d{1,}", str, tokenNumber);
            sea.addShark(index/width, Utility.mod(index, width), hungerLevel);
        }else if(str.startsWith("F")){
            sea.addFish(index/width, Utility.mod(index, width));
        }

    }
}


class FactoryProducer{
    static AbstractFactory getFactory(){
          return new CritterFactory();
       }
}

Critter:

/* Critter.java */

package Project1;
/**
 * The abstract class Critter defines a base class for any creature 
 * that can exist at a specific location in the ocean.
 * @author mohet01
 *
 */
abstract class Critter  {

    /**
     * Below data member defines a location of a Critter in an Ocean
     */

    Point location;


    public Critter(int x, int y){
        location = new Point(x,y);
    }

    public Point getLocation(){
        return location;
    }


    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (this.getClass() != obj.getClass())
            return false;
        return true;
    }



    public abstract String toRunLengthEncodeSegment(int sameNeighborCount);


    /**
     * This method computes the behavior of the Critter in the Ocean. 
     * Computes new value of location property of Critter.
     * No operation is performed as this is a base class.
     */
    public abstract Critter update(Ocean currentTimeStepSea);


}

Ocean

/* Ocean.java */

package Project1;

/**
 * The Ocean class defines an object that models an ocean full of sharks and
 * fish.
 * @author mohet01
 *
 */
class Ocean {

    /**
     * Define any variables associated with an Ocean object here. These
     * variables MUST be private.
     * 
     */
    //width of an Ocean
    private final int width;
    //height of an Ocean
    private final int height;

    /**
     * Below data member is the number of simulation time steps that a Shark
     *  can live through without eating.
     */
    private int starveTime;

    /**
     *  Do not rename these constants.  WARNING:  if you change the numbers, you
     *  will need to recompile Test4.java.  Failure to do so will give you a very
     *  hard-to-find bug.
     */

    /*public final static int EMPTY = 0;
    public final static int SHARK = 1;
    public final static int FISH = 2;*/



    /*
     * This method provides the starvation time in the Ocean for sharks
     */
    public int getStarvationTime(){
        return starveTime;
    }


    /*
     * This method provides the starvation time in the Ocean for sharks
     */
    public void setStarvationTime(int starveTime){
        this.starveTime = starveTime; 
    }


    /*
     * I preferred, 2d array of references to Critter objects
     * rather than List. Reasons(correct me),
     * 1) To display an array of ocean, it adds more logic in paint() method.
     * 2) Checking 8 nearest neighbors of each Critter looks inefficient,
     * For example: for an ocean of SEEFE  
     *                              FEEFE a 2x2 ocean, If i maintain
     * a list of Critter for this 2x2 ocean, i need to traverse 
     * S->E->E->F->E->F to get my first nearest neighbor of Shark,
     * In contrast, With 2d array, I would just use modulo operation as
     * mentioned in update() method.  Let us see what happens!!!
     *  
     */
    private Critter[][] oceanMatrix;


    /**
     * Constructor that creates an empty ocean with below dimension
     *  
     * @param width
     *            is the width of the ocean.
     * @param height
     *            is the height of the ocean.
     * 
     */
    public Ocean(int width, int height, int starveTime){
        this.oceanMatrix = new Critter[height][width];
        this.width = width;
        this.height = height;
        this.starveTime = starveTime;
        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                oceanMatrix[row][col] = new Empty(row,col);
            }
        }
    }


    /**
     * This method adds Critter in an ocean.
     * @param object
     *              is the Critter object to be added in Ocean.
     */
    public void addCritter(Critter object){
        Point p = object.getLocation();
        int x = p.getX();
        int y = p.getY();
        /*
         * I understand that, location property make sense to be be moved 
         * to corresponding Critter<type> class as it's property, which i did, But 
         * also captured location property of a Critter Object in Ocean class(with
         * above 3 lines of code) which is redundant and not relevant, But 2d array
         * is more efficient than list, for checking neighbor in update() method.
         * Are we Breaking SRS????
         * So, Instead of List am using 2d array. Let us see what happens!!!
         */
        oceanMatrix[x][y] = object;
    }


    /**
     * This method returns either Critter Object reference
     * 
     * @param x
     *            is the x-coordinate of the cell whose contents are queried.
     * @param y
     *            is the y-coordinate of the cell whose contents are queried.
     */
    public Critter cellContents(int x, int y) {
        return oceanMatrix[x][y];
    }


    /**
     * getWidth() returns the width of an ocean Object.
     * 
     * @return 
     *          the width of the ocean.
     * 
     */
    public int getWidth() {
        return this.width;
    }

    /**
     * getHeight() returns the height of an Ocean object.
     * 
     * @return
     *          the height of the Ocean.
     */

    public int getHeight() {
        return this.height;
    }





    /**
     * timeStep() performs a simulation time step as described in README.
     * 
     * @return
     *          an ocean representing the elapse of one time Step.
     */

    public Ocean timeStep() {

        Ocean nextTimeStepSea = new Ocean(width, height, starveTime);

        for (int row = 0; row < this.height; row++) {
            for (int col = 0; col < this.width; col++) {
                Critter creature = this.cellContents(row, col);
                nextTimeStepSea.addCritter(creature.update(this));
            }
        }
        return nextTimeStepSea;
    }

    /**
     *  The following method is required for Part II.
     */

    /**
     *  addShark() (with three parameters) places a shark in cell (x, y) if the
     *  cell is empty.  The shark's hunger is represented by the third parameter.
     *  If the cell is already occupied, leave the cell as it is.  You will need
     *  this method to help convert run-length encodings to Oceans.
     *  @param x is the x-coordinate of the cell to place a shark in.
     *  @param y is the y-coordinate of the cell to place a shark in.
     *  @param feeding is an integer that indicates the shark's hunger.  You may
     *         encode it any way you want; for instance, "feeding" may be the
     *         last timestep the shark was fed, or the amount of time that has
     *         passed since the shark was last fed, or the amount of time left
     *         before the shark will starve.  It's up to you, but be consistent.
     */

    public void addShark(int x, int y, int feeding) {
      if (this.cellContents(x, y).getClass().getName().equals("Empty")){
          this.addCritter(new Shark(x, y, feeding));
      }
    }

    /**
     *  addFish() places a fish in cell (x, y) if the cell is empty.  If the
     *  cell is already occupied, leave the cell as it is.
     *  @param x is the x-coordinate of the cell to place a fish in.
     *  @param y is the y-coordinate of the cell to place a fish in.
     */

    public void addFish(int x, int y) {
        if (this.cellContents(x, y).getClass().getName().equals("Empty")){
          this.addCritter(new Fish(x, y));
        }
    }

}

The complete code is available on this link.

TypeAndSize class is completely ignored as a whole.

My questions:

  1. Does OOP paradigm introducing Utility class?

  2. Please provide Comments on Abstract factory approach for issues (if any)

  3. Is this design accommodating more types of creatures in smoother way?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ @overexchange We won't explicitly review design, as you've been trying. I've engaged in this because I find it interesting, but pure design questions are not on topic. We review implementations of designs: Code. Strip all comments on class and function level from the classes that haven't changed so they're really tiny, OR, post only the new parts and link to the rest. You will only get review on that which you post, however. \$\endgroup\$
    – Pimgd
    Commented Nov 10, 2014 at 8:36
  • \$\begingroup\$ The code does not match the problem statement (creating objects when the task is Ocean -> RunLengthEncoding), the problem statement is incomplete (It should be Ocean -> RunLengthEncoding -> Ocean instead), and code is missing (Ocean class). \$\endgroup\$
    – Pimgd
    Commented Nov 10, 2014 at 12:04

1 Answer 1

1
\$\begingroup\$

I made a mistake when I told you to use an abstract factory (ehehehe~). What I really meant was a factory.

The difference is that a normal factory doesn't have an abstract superclass and that it just creates objects based on the provided parameters.

Basically, FactoryProducer and AbstractFactory disappear. CritterFactory stays.

Additionally...

When looking at new critter types, I realize I'm hopelessly uneducated about marine life. You'll have to add the new class and the corresponding mapping to CritterFactory, though. I wish there was a way to fix this... (There are, but they're evil, IMHO. I envision stuff like making a map of String, Class...)

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.