-1

I am working on a school assignment. The objective is to practice GUI's, clone() methods, and using/ modifying existing code. I am trying to write an equals method in the way the instructor desires-- by using a clone of the object, removing items from the bag (returns boolean based on success or failure to remove).

The bag is represented in an array, and should return true in cases such as {1,2,3} and {3,2,1}, ie order does not matter, only the number of each number present in the arrays.

Here is the issue

It works in most cases, however there is a bug in cases where the bags contain numbers as such: {1,1,2} and {1,2,2} and other similar iterations. It is returning true instead of false.

I believe it has something to do with the remove() method we are supposed to use. If i understand it correctly, it is supposed to put the value at the 'end' of the array and decrease the manyItems counter (this is a variable for number of items in the array, because array.length is by default in the constructor 10.)

The code is largely written by another person. We had to import the existing files and write new methods to complete the task we were given. I have all the GUI part done so i will not include that class, only the used methods in the IntArrayBag class.

A second pair of eyes would be helpful. Thanks.

public class IntArrayBag implements Cloneable
{
// Invariant of the IntArrayBag class:
//   1. The number of elements in the bag is in the instance variable 
//      manyItems, which is no more than data.length.
//   2. For an empty bag, we do not care what is stored in any of data;
//      for a non-empty bag, the elements in the bag are stored in data[0]
//      through data[manyItems-1], and we don�t care what�s in the
//      rest of data.
private int[ ] data;
private int manyItems;

public IntArrayBag( )
{
  final int INITIAL_CAPACITY = 10;
  manyItems = 0;
  data = new int[INITIAL_CAPACITY];
}

public IntArrayBag clone( )
{  // Clone an IntArrayBag object.
  IntArrayBag answer;

  try
  {
     answer = (IntArrayBag) super.clone( );
  }
  catch (CloneNotSupportedException e)
  {  // This exception should not occur. But if it does, it would probably
     // indicate a programming error that made super.clone unavailable.
     // The most common error would be forgetting the "Implements Cloneable"
     // clause at the start of this class.
     throw new RuntimeException
     ("This class does not implement Cloneable");
  }

  answer.data = data.clone( );

  return answer;
}

public int size( )
{
  return manyItems;
}

public boolean remove(int target)
{
  int index; // The location of target in the data array.

  // First, set index to the location of target in the data array,
  // which could be as small as 0 or as large as manyItems-1; If target
  // is not in the array, then index will be set equal to manyItems;
  for (index = 0; (index < manyItems) && (target != data[index]); index++)
     // No work is needed in the body of this for-loop.
     ;

  if (index == manyItems)
     // The target was not found, so nothing is removed.
     return false;
  else
  {  // The target was found at data[index].
     // So reduce manyItems by 1 and copy the last element onto data[index].
     manyItems--;
     data[index] = data[manyItems];
     return true;
  }
}
//I added extra variables that are not needed to try to increase readability,
//as well as when i was trying to debug the code originally
public boolean equals(Object obj){

   if (obj instanceof IntArrayBag){

       IntArrayBag canidate = (IntArrayBag) obj; // i know this can be changed, this was required
       IntArrayBag canidateTest = (IntArrayBag) canidate.clone(); //this was created
       //as a clone because it was otherwise referring to the same memory address
       //this caused items to be removed from bags when testing for equality
       IntArrayBag test = (IntArrayBag) this.clone();

       //fast check to see if the two objects have the same number of items, 
       //if they dont will return false and skip the item by item checking
       if (test.size() != canidateTest.size())
           return false;

       //the loop will go through every element in the test bag it will 
       //then remove the value that is present at the first index of the test bag
       for (int i = 0; (i < (test.size()) || i < (canidateTest.size())); i++){
           int check = test.data[i];
           //remove() returns a boolean so if the value is not present in each bag
           //then the conditional will be met and the method will return false
           boolean test1 = test.remove(check);
           boolean test2 = canidateTest.remove(check);
           if (test1 != test2)
               return false;

       }//end for loop

       // if the loop goes through every element
       //and finds every value was true it will return true
       return true;  
   }//end if 
   else
       return false;
}//end equals
}
Brian O.
  • 1
  • 1
  • 6
  • 2
    Maybe I'm missing something, but I don't see a specific question in your post. Please clearly state just what it is that you're asking. – Hovercraft Full Of Eels Feb 14 '16 at 05:19
  • 1
    Why do you need to clone the objects in equals? – OneCricketeer Feb 14 '16 at 05:21
  • Edits should reflect your comments. – Brian O. Feb 14 '16 at 05:27
  • I appreciate everyone trying to come up with other ways to solve the problem. It is greatly appreciated. Unfortunately, I asked the instructor if I could do the problem another way when we first got the assignment. She insisted we clone the objects. The way it is written it works in most cases. I was debugging and the issue occurs with the remove() method , sometimes it does not operate correctly(in one case it turned the 1 into a 2). I did not write this method. I tried modifying the method, but to no avail i was unable to make it work as desired. – Brian O. Feb 14 '16 at 05:47
  • Has the instructor given any explanation for insisting on the use of `clone()`? It still has some defenders, but `clone()` and `Cloneable` are [generally considered broken](http://stackoverflow.com/a/2427946/1953590) and you will rarely if ever encounter a real-world use case for them. – Kevin Krumwiede Feb 14 '16 at 06:17
  • No the instructor did not specify why we should use it. We were just told to use it. – Brian O. Feb 14 '16 at 06:21

4 Answers4

1

Is it explicitly stated to use clone? You can achieve it easily by overriding the hashCode() for this Object.

You can override the hashCode() for this object as follows:

@Override
public int hashCode() {
    final int prime = 5;
    int result = 1;
    /* Sort Array */
    Arrays.sort(this.data);
    /* Calculate Hash */
    for(int d : this.data) {
        result = prime * result + d;
    }
    /* Return Result */
    return result;
}

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

If you want to continue using your implementation for equals to compare test and CandidateTest then also you can compute unique hashes and make decision based on the results.

Here is the code snippet:

/* Assuming that you have put size comparison logic on top 
   and the two objects are of same size */

final int prime = 31;
int testResult = 1;
int candidateTestResult = 1;
for(int i = 0; i < test.size(); i++) {
    testResult = prime * testResult + test.data[i];
    candidateTestResult = prime * candidateTestResult + candidateTest.data[i];
}
/* Return Result */
return testResult == candidateTestResult;
user2004685
  • 9,548
  • 5
  • 37
  • 54
  • Unfortunately, We are supposed to use clone. – Brian O. Feb 14 '16 at 05:28
  • @Brain O. Instead of comparing the individual elements, compute the hashes for the two objects in your clone method. Please see the updated solution. – user2004685 Feb 14 '16 at 05:49
  • You cannot test for equality by testing hash codes. Since a hash code is shorter than the array, that means there will be cases where the arrays have different elements but map to the same hash code. You will get `true` for cases that should be `false. This answer is just plain wrong. – ajb Feb 14 '16 at 05:53
  • @ajb Thanks for your response. I understand that the limit for calculated `hashCode()` will be `INT_MAX`. But I do not understand how different elements can map to same hash code. I'm doing it in many of my POJO classes and never encountered an issue. Could you please provide an example? – user2004685 Feb 14 '16 at 06:01
  • I would use your solution if I could. Things can be screwy with some instructors. They want things their way. I think hashes are a couple chapters out, so they are off limits. I appreciate the time you took to help me out though. – Brian O. Feb 14 '16 at 06:03
  • I can't provide an example, because it would be too hard to compute. But suppose you have a class with 3 32-bit integer fields, and a hash code that's one 32-bit integer that's computed from those 3 fields.. There are 2^32 possible hash codes, but 2^96 possible combinations of values. Thus, on average, for each hash code, there will be 2^64 possible values for those three fields that map to the same hash code. If you've never encountered a case where hash codes are equal when the objects aren't equal, consider yourself lucky. Mathematically, **it cannot work**. – ajb Feb 14 '16 at 06:05
1

I cannot see the big picture, as I havent coded GUIs in Java before, however, as far as comparing 2 int[] arrays, I would sort the arrays before the comparison. This will allow you to eliminate problem cases like the one you stated ( if sorting is possible), then apply something like:

while(array_1[index]==array_2[index] && index<array_1.length)
{index++;}

and find where did the loop break by checking the final value of index

a.atlam
  • 742
  • 5
  • 17
  • I suppose this could work if the cloned arrays were sorted (we are supposed to keep the original unchanged so when it prints the order can be seen). A possible work around. – Brian O. Feb 14 '16 at 05:53
0

I believe the problem is in this line:

for (int i = 0; (i < (test.size()) || i < (canidateTest.size())); i++){

The problem here is that test and canidateTest are the clones that you made, and you are removing elements from those bags. And any time you remove an element from the bag, the size will decrease (because you decrease manyItems, and size() returns manyItems). This means you're only going to go through half the array. Suppose the original size is 4. Then, the first time through the loop, i==0 and test.size()==4; the second time, i==0 and test.size()==3; the third time, i==2 and test.size()==2, and you exit the loop. So you don't look at all 4 elements--you only look at 2.

You'll need to decide: do you want to go through the elements of the original array, or the elements of the clone? If you go through the elements of the clone, you actually never need to increment i. You can always look at test.data[0], since once you look at it, you remove it, so you know test.data[0] will be replaced with something else. In fact, you don't need i at all. Just loop until the bag size is 0, or until you determine that the bags aren't equal. On the other hand, if you go through the elements of this.data (i.e. look at this.data[i] or just data[i]), then make sure i goes all the way up to this.size().

(One more small point: the correct spelling is "candidate".)

ajb
  • 31,309
  • 3
  • 58
  • 84
  • Thank you very much. I changed the loop control so that it was not tied to the removing as you stated. I did some testing and it was successful. I am embarrassed by the the simplicity of my error though. Again, many thanks. – Brian O. Feb 14 '16 at 06:09
  • It's a common mistake. You have to be very careful when you loop through an array or list at the same time you're removing elements from the array or list (or inserting/appending elements). – ajb Feb 14 '16 at 06:10
  • I also tried the way of omitting the increment, and that also worked. Thanks for the help. – Brian O. Feb 14 '16 at 06:18
-1

Maybe you should try SET interface view this in detail :http://www.tutorialspoint.com/java/java_set_interface.htm A set object cannot contains duplicate elements, so it's suitable for your assignment than build your own class.

For example:[1,1,2] and [1,2,2] you can use this to test whether they are equal

arr1 = {1,1,2}
arr2 = {1,2,2}
Set<Integer> set = new HashSet<Integer>();
for(int i : arr1){//build set of arr1
    if(set.contains(i)==false){
        set.add(i)
    }
}
for(int i:arr2){
    if(set.contains(i)==false){
        System.out.println('not equal');
        break;
    }
}

Hope this is helpful.

張泰瑋
  • 138
  • 2
  • 9
  • While you are right that sets cannot contain duplicates, this doesn't answer this question as clone is required to be used – OneCricketeer Feb 14 '16 at 05:41
  • It does seem that this answer could be used, unfortunately we are required to do it by cloning. Thanks for taking the time to answer though – Brian O. Feb 14 '16 at 05:49