0

I am working on a cube solver at the moment and it uses breadth first search to find the shortest solution to the 2x2x2 rubiks cube solver. The thing is that in the search there are duplicate positions that get hit and my goal is to know how many of them I have and "prune" them out later.(I know how to avoid the duplicates but that is irrelevant to this post). Here is what it should look like Old version working

That is from an old version of the code which works with the hashCode and equals but I am trying to get the new version to work with it. I think the reason is because in the old version I override equals and hashCode, but in the new one I can't seem to do that because I am no longer comparing objects, rather arrays (that is guess). The current version isn't picking up duplicates due to this. It says there are no duplicates but that is incorrect.

current non working version.

here is what the hashCode and equals is like for the old version which detects duplicates.

private Cube() {
    cube = new int[][] {
        { 0, 0, 0, 0 },
        { 1, 1, 1, 1 },
        { 2, 2, 2, 2 },
        { 3, 3, 3, 3 },
        { 4, 4, 4, 4 },
        { 5, 5, 5, 5 }
    };

    cube = scanCube(cube);
    cube = print_cube(cube);
}

private Cube(Cube other) {
    cube = new int[other.cube.length][];
    for (int i = 0; i < other.cube.length; i++) {
        cube[i] = Arrays.copyOf(other.cube[i], other.cube[i].length);
    }
}

public boolean isSolved() {
    for (int i = 0; i < cube.length; i++) {
        for (int k = 1; k < cube[i].length; k++) {
            if (cube[i][0] != cube[i][k]) {
                return false;
            }
        }
    }
    return true;
}

@Override
public boolean equals(Object other) {
    return other instanceof Cube && Arrays.deepEquals(((Cube) other).cube, cube);
}


@Override
public int hashCode() {
    return Arrays.deepHashCode(cube);
}`

Here is the current version.

public static void main(String[] args) {

    int[][] cube = new int[][] {
        { 0, 0, 0, 0 },
        { 1, 1, 1, 1 },
        { 2, 2, 2, 2 },
        { 3, 3, 3, 3 },
        { 4, 4, 4, 4 },
        { 5, 5, 5, 5 }
    };
    cube = scanCube(cube);
    cube = print_cube(cube);
    solve(cube);
}     
private static boolean isSolved(int [][] cube) {
    for (int i = 0; i < cube.length; i++) {
        for (int k = 1; k < cube[i].length; k++) {
            if (cube[i][0] != cube[i][k]) {
                return false;
            }
        }
    }
    return true;
}

public static int[][] copyCube(int [][] cube){
   int [][] copy = new int [6][4];

   for(int i = 0; i < 6; i++ ){
       copy[i] = cube[i].clone();
   }
   return copy;
}

public static boolean equals(int[][] other, int[][] cube) {
    return Arrays.deepEquals(other, cube);
}

public int hashCode(int [][] cube) {
    return Arrays.deepHashCode(cube);
}

In the search method is where duplicates are determined. Here is the code for the old one.

static public void solve(Cube c) {
    Set<Cube> cubesFound = new HashSet<Cube>();
    cubesFound.add(c);

    Stack<Cube> s = new Stack<Cube>();
    s.push(c);

    Set<Stack<Cube>> initialPaths = new HashSet<Stack<Cube>>();
    initialPaths.add(s);

    solve(initialPaths, cubesFound);
}

static public void solve(Set<Stack<Cube>> livePaths, Set<Cube> cubesFoundSoFar) {
    System.out.println("livePaths size:" + livePaths.size());
    int numDupes = 0;

    Set<Stack<Cube>> newLivePaths = new HashSet<Stack<Cube>>();

    for (Stack<Cube> currentPath : livePaths) {

        Set<Cube> nextStates = currentPath.peek().getNextStates();

        for (Cube next : nextStates) {
            if (currentPath.size() > 1 && next.isSolved()) {
                currentPath.push(next);
                System.out.println("Path length:" + currentPath.size());
                System.out.println("Path:" + currentPath);
                System.exit(0);

            } else if (!cubesFoundSoFar.contains(next)) {
                Stack<Cube> newCurrentPath = new Stack<Cube>();
                newCurrentPath.addAll(currentPath);
                newCurrentPath.push(next);
                newLivePaths.add(newCurrentPath);
                cubesFoundSoFar.add(next);
            } else {
                numDupes += 1;
            }
        }
    }

    System.out.println("Duplicates found " + numDupes + ".");
    solve(newLivePaths, cubesFoundSoFar);
}

And the new one.

static private void solve(int[][] cube) {

    int[][][] s = new int[12][6][4];
    s[0] = cube;

    Set<int[][][]> initialPaths = new HashSet<int[][][]>();
    initialPaths.add(s);
    Set<int[][]> cubesFound = new HashSet<int[][]>();
    cubesFound.add(cube);

    solve(initialPaths, cubesFound, 1);
}

static private void solve(Set<int[][][]> livePaths,Set<int[][]> cubesFoundSoFar, int iterationCount) {
    System.out.println("livePaths size:" + livePaths.size());

    Set<int[][][]> newLivePaths = new HashSet<int[][][]>();
    int counter = 0;
    int recordDepth = 0;
    int duplicates = 0;

    for(int[][][] currentPath : livePaths) {

        Set<int [][]> nextStates = getNextStates(currentPath[iterationCount-1]);

        for (int[][] next : nextStates) {
            if (isSolved(next)) {
                currentPath[iterationCount] = next;
                int maxSteps = -1;
                System.out.println("Path:" );
                for(int i = 0; i < currentPath.length; i++) {
                    if(currentPath[i] != null) {
                        maxSteps = i;
                        System.out.println(toString(currentPath[i]));
                    }else {
                        break;
                    }
                }
                System.out.println("Path length:" + maxSteps);
                System.exit(0);

            } else if(!cubesFoundSoFar.contains(next)){
                int[][][] newCurrentPath = new int[12][6][4];
                newCurrentPath = currentPath.clone();
                newCurrentPath[iterationCount] = next;
                newLivePaths.add(newCurrentPath);
                counter ++;
                cubesFoundSoFar.add(next);
            }  else {
                duplicates += 1;
            }
        }
    }
    //System.out.println(" Set.size(): "+newLivePaths.size());

    String storeStates = "positions.txt";
    try {
        PrintWriter outputStream = new PrintWriter(storeStates);
        outputStream.println(storeStates);
        for(int[][][] s:newLivePaths) {
            outputStream.println(toString(s[iterationCount]));
        }
        outputStream.close();

    } catch (FileNotFoundException e) {
        System.err.println("Fatal: could not open cache file for cube positions. exiting.");
        e.printStackTrace();
        System.exit(1);
    }
    System.out.println("Duplicates found "+ duplicates + ".");
    solve(newLivePaths, cubesFoundSoFar, iterationCount+1);
}
cuber
  • 371
  • 5
  • 20
  • So, if I understand, you're asking why your duplication detection method doesn't work, right? Where is that method? How can we help if you don't post it? – JB Nizet Nov 25 '16 at 11:27
  • yes it isn't working in the new version, I will post the code to that of the old one and the new one. @JBNizet – cuber Nov 25 '16 at 11:34
  • @JBNizet updated it – cuber Nov 25 '16 at 11:38
  • 4
    An array will only ever be equal to itself. So storing arrays in a Set to detect duplicate arrays will never work. You need a class that wraps the array and overrides equals() and hashCode(). – JB Nizet Nov 25 '16 at 11:41
  • Ok what is wrapping an array? I haven't done so before. – cuber Nov 25 '16 at 11:46
  • 1
    Yes, you have. Your original Cube class does just that. – JB Nizet Nov 25 '16 at 11:46
  • I did have the cube in a constructor and does that mean i have to use objects again and can't use arrays? – cuber Nov 25 '16 at 11:51
  • Yes, Java is an OO language. You should use objects and classes. BTW, a List, or even a Cube[], is way easier to understand and reason about than an int[][][]. You use STrings everyday, and List everyday, not char[] or char[][]. – JB Nizet Nov 25 '16 at 11:53
  • Oh I see the reason why I got rid of it was because when I was using a stack in the solving method I would run out of heap space when I gave the solver a hard position to solve and it couldn't complete it. So I moved to a less memory consumption search by using arrays because it is lighter on the memory. @JBNizet – cuber Nov 25 '16 at 12:09
  • That won't save you much. You should increase the memory, or think about a better algorithm. – JB Nizet Nov 25 '16 at 12:10
  • I did try increasing the memory giving it 3 gigs to run but I still got an out of heap space error. @JBNizet – cuber Nov 25 '16 at 12:12
  • Possible duplicate of [Why is it important to override GetHashCode when Equals method is overridden?](http://stackoverflow.com/questions/371328/why-is-it-important-to-override-gethashcode-when-equals-method-is-overridden) – cuber Jan 09 '17 at 02:48

1 Answers1

1

You have not overridden the equals(Object) method in your second code, but Set.contains(Object) use equals to compare the elements. Since there is none in Cube, the one of Object is used. This does not compare content, it just test if the objects are the same instance (same memory location).

Here the relevant part of contains:

... More formally, returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e)). ...

You could add something like to the second code:

@Override
public boolean equals(Object other) {
    if (other instanceof Cube)
        return equals(cube, ((Cube) other).cube);
    else
        return false;
}


@Override
public int hashCode() {
    return hashCode(cube);
}
user85421
  • 28,957
  • 10
  • 64
  • 87
  • I see what you mean so I would have to revert to using a wrapper class in the second code and then get edit parts of my search methods to work with objects instead of int[][][], like I had done before. Right? – cuber Nov 25 '16 at 13:42
  • maybe you could represent the state as an int or long... but that would be tricky and hard to understand/maintain. – user85421 Nov 25 '16 at 13:50
  • Also I don't think I would be able to return the `hashCode(cube)` because it either would have to have a parameter in the method thus making it not possible to override or remove the `cube` parameter and that would give me an overflow error because of recursion. I think it would happen with the equals method also because I would have to remove the `return equals` extra argument. – cuber Nov 25 '16 at 13:52