1

Although the rules of chess don't allow Kings to threaten each other, this is a good geometric analogy for the real problem.

Given the following geometry

K1 [] [] [] []
K2 K3 [] [] []
[] K4 [] K5 []

the result of the required algorithm should be a data structure with the following entries: [K1, K2], [K1, K3], [K2, K3], [K2, K4], [K3, K4].

  • The order inside an entry does not matter, so [Kx, Ky] is the same as [Ky, Kx].
  • The entries in the data structure don't need to be ordered.
  • Kings have 0 to 8 other Kings adjacent.

The following code exists:

class King {
    Point location; // Point contains x and y fields corresponding to the grid
    Point getLocation() { return location; }
}

class Square {
    Point location;
    Point getLocation() { return location; }

    King king; // null if there is no King in the square
    King getKing() { return king; }
}

and also the utility method

static List<Square> getThreatenedSquares(Point location) { ... }

which returns the 8 Squares adjacent to the given coordinates. None of the above has to be used, it's just available.

The input is Collection<King> kingsColl, containing all Kings on the board in no relevant order (it's LinkedHashMap#values()). My current non-stream algorithm, in words, iterates over all Kings, for each one it looks at all Kings which weren't already looked at, and checks for a matching condition.

List<King> kings = new ArrayList<>(kingsColl);
if (kings.size() > 1) {
    Map<King, King> conflicts = new HashMap<>();
    for (int i = 0; i < kings.size() - 1; i++) { // if the last king has a conflict, it would show up in the other king
        King k1 = kings.get(i);
        Point loc1 = k1.getLocation();
        List<Square> adj = getThreatenedSquares(loc1);
        for (int j = i + 1; j < kings.size(); j++) { // avoids duplicates Kx <-> ky
            King k2 = kings.get(j);
            Point loc2 = k2.getLocation();
            for (Square sqr : adj) {                   // check for adjacency
                if (sqr.getLocation().equals(loc2)) {
                    conflicts.put(k1, k2);
                }
            }
        }
    }
}

This algorithm is bad because it relies on indexes and requires the creation of an index-access collection, it can't be parallelized, and it makes more checks than it needs. Note that the result here is stored in a Map, but any collection of pairs is fine (Guava included).

Iv'e tried a few ideas using streams, none of which I cared to post because they were a mess (if someone isn't convinced I will post the common "what have you tried?"). Streams tend to solve the above problem because they are implementation independent. How do I achieve this algorithm using streams?

user1803551
  • 12,965
  • 5
  • 47
  • 74

3 Answers3

3

This is an invitation to rethink the data structures.

Your data structure is not only inefficient, it has redundancy which is a potential source of errors. You have a collection of Square object containing a position. What happens, if two Square objects in that collection have the same position? Then, a Square instance may have a reference to a King instance, which has a Point reference on its own. What, if that position mismatches the Point of the Square or even two Square instances refer to the same King instance?

Normally, you would use either, a list of locations bearing a King, e.g. Collection<Point>, or an array or list containing objects representing the presence or absence of a King, e.g. ChessPiece[][] where ChessPiece is a stateless enum though a single King instance is sufficient for the actual task.

In its simplest, you can use a single bit to denote whether the field bears a King or is empty. This allows to represent a fixed structure like an 8x8 chess board using a single long value. And the long value allows to check the constellations intrinsically, e.g.

public static void printSituation(long board) {
    System.out.println("  ABCDEFGH");
    for(int row=1, p=63; row<=8; row++) {
        System.out.print(row+" ");
        for(int col='A'; col<='H'; col++, p--) {
            System.out.print((board&(1L<<p))!=0? "K": "-");
        }
        System.out.println();
    }
}
public static void printConstellations(long board) {
    final long horizontal=board & (board<<1) & ~0x0101010101010101L,
               vertical  =board & (board<<8),
               diagonal1 =board & (board<<9) & ~0x0101010101010101L,
               diagonal2 =board & (board<<7) & ~0x8080808080808080L;
    long bit=1L<<63;
    for(int row=1; row<=8; row++) {
        for(int col='A'; col<='H'; col++, bit>>>=1) {
            if((horizontal&bit)!=0)
                System.out.printf("(%c,%d)-(%c,%d)%n", col, row, col+1, row);
            if((vertical&bit)!=0)
                System.out.printf("(%c,%d)-(%c,%d)%n", col, row, col, row+1);
            if((diagonal1&bit)!=0)
                System.out.printf("(%c,%d)-(%c,%d)%n", col, row, col+1, row+1);
            if((diagonal2&bit)!=0)
                System.out.printf("(%c,%d)-(%c,%d)%n", col, row, col-1, row+1);
        }
    }
}
public static void main(String[] args) {
    long scenario=0x80C0500000000000L;//your actual board situation
    printSituation(scenario);
    printConstellations(scenario);
}
  ABCDEFGH
1 K-------
2 KK------
3 -K-K----
4 --------
5 --------
6 --------
7 --------
8 --------
(A,1)-(A,2)
(A,1)-(B,2)
(A,2)-(B,2)
(A,2)-(B,3)
(B,2)-(B,3)

This fits into what you called “bad because it relies on indexes”, but actually, its so cheap that it would be ridiculous to ever consider parallel processing (e.g. via Stream API). Instead, if your starting point is a collection of Point instances, it would be beneficial to convert it to the long representation, perform the analysis and convert the threat constellations back to Points.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • That's really cute but what if he wants to add more pieces other than a King – Nick Ziebert Apr 19 '17 at 15:30
  • @Nick Ziebert: if the task still is the same, it’s like I already said, it’s beneficial to transform the source data into a `long`, dropping all irrelevant stuff, get the pairs and transform them into the target data form. If you want to implement other checks, you can compose most regular chess moves from the pattern of this answer. Of course, this would require an additional (outer) iteration over the piece types. – Holger Apr 19 '17 at 15:40
  • Where did you get 0x80C0500000000000L? I'm trying to understand this. – Nick Ziebert Apr 19 '17 at 16:48
  • @Nick Ziebert: maybe it helps understanding that [`0x80_C0_50_00_00_00_00_00L`](https://en.wikipedia.org/wiki/Hexadecimal) is the same as `0b10000000_11000000_01010000_00000000_00000000_00000000_00000000_00000000L` and when you replace the underscores with linebreaks in the binary format, you’ll get the picture. I didn’t write it that way as the binary literal is quite long and Stackoverflow’s syntax highlighter can’t cope with the underscores correctly. – Holger Apr 19 '17 at 17:07
  • This is very clever (+1), but the problem I gave is just analogous to the real world problem and this solution won't work because it oversimplifies. The data structures are fixed: a list of Kings resides in values of a map, or you can iterate over all squares and check if a King is there with `== null`, which is less efficient because of the misses. All the worries about squares having the same coordinates or 2 Kings in a square etc. can be relieved, it is proven internally that this cannot be the case. – user1803551 Apr 25 '17 at 07:36
  • There is an array of `Square`s, `Square[][]`, which can be used as I alluded to above, but bit representation of the board and existence of a King doesn't exist and creating one in addition to holding other data structures is synchronization overhead. – user1803551 Apr 25 '17 at 07:43
  • Eventually I realized that this problem is not a best fit for streams as I'd wanted it to be. Sometimes I see some stream wizardry and clever (but still readable) ways to accomplish something in a way I did not think of. This was my hope for this one, but it turned out that without modifications my own solutions are just as good as anything else Iv'e seen. – user1803551 Apr 25 '17 at 07:45
1

Here's a way to do it using streams. I think this isolates the components to create better readability. However, If you're looking for speed, it probably has to do with a better algorithm rather than using Streams.

    List<List<King>> conflicts = kings.stream()
                                      .flatMap(k1 -> subStream(kings, k1) 
                                               .map(k2 -> Arrays.asList(k1, k2)))
                                      .filter(kingPair -> threatensEachother(kingPair))
                                      .collect(Collectors.toList());
    }


private static Stream<King> subStream(List<King> kings, King k1) {
    return kings.subList(kings.indexOf(k1) + 1, kings.size()).stream();
}

private static boolean threatensEachother(List<King> kingPair) {
      List<Square> k1Squares = getThreatenedSquares(kingPair.get(0).getLocation());
      List<Square> k2Squares = getThreatenedSquares(kingPair.get(1).getLocation());
    return sqauresOverlap(k1Squares, k2Squares);
}

private static boolean squaresOverlap(List<Squares> k1Squares, List<Squares> k2Squares) {
    return k1Squares.stream()
                    .anyMatch(k1Square -> k2Squares.contains(k1Square).
}
Nick Ziebert
  • 1,258
  • 1
  • 9
  • 17
  • What is `sqauresOverlap`? – user1803551 Apr 18 '17 at 15:26
  • This returns a List> whereas the OP requested a Map to be returned. squaresOverlap I think is the "better algorithm" he is referring to. Meaning it checks if the Kings threaten each other. – Alex Papageorgiou Apr 18 '17 at 15:28
  • @AlexPapageorgiou I specifically said "*Note that the result here is stored in a `Map`, but any collection of pairs is fine (Guava included)*". I didn't request a `Map` since one of its problems is that it allows reverse mappings while I treat them as the same. – user1803551 Apr 18 '17 at 15:34
  • @user1803551 I understand but still, returning a single List> is not ideal since it doesn't show to which King each List is mapped to and when using parallelStream you cannot match the List to a single King since the parallelStream doesn't guarantee sequential execution of comparisons etc. – Alex Papageorgiou Apr 18 '17 at 15:41
  • @AlexPapageorgiou I didn't test this answer yet, but it looks like it returns a list of pairs, where each pair is represented by a list of 2 elements. This is fine. My problem is the amount of scaffolding needed for this solution. Still might be legitimate. – user1803551 Apr 18 '17 at 15:50
  • @user1803551 Yeah, it returns a List of Pairs of Kings that looks like [[K1, K2], [K1, K3], [K2, K3], [K2, K4], [K3, K4]]. It's not a map, just copying what OP asked for as a return. What do you mean by scaffolding? The idea of isIntersecting and squaresOverlap() is to break down the problem into smaller components so you can implement these methods with different behaviors given different pieces. – Nick Ziebert Apr 18 '17 at 17:15
0

UPDATE

I've created some code that does what you want without comparing indexes:

List<King> kings = new ArrayList<>(kingsColl);
Map<King,List<King>> conflicts = kings.parallelStream() //Initiating Stream API (Stream<King>)
                                .collect(Collectors.toMap( //To Map
                                         Function.identity(), //Return the king we are going through.
                                         king -> getThreatenedSquares(king.getLocation())));// Use the getThreatenedSquares function.

Then all that is needed is a small change to getThreatenedSquares which instead of returning the Squares, it returns the Kings that are threatened utilizing the getKing() method:

static List<King> getThreatenedSquares(Point location) {
    //Same Code until the end...
    List<King> toReturn = yourSquareList.parallelStream() //Initiating Stream API
                                 .map(square -> square.getKing())//Transforming Square to King.
                                 .filter(king -> king!=null)//Removing squares with no King.
                                 .collect(Collectors.toList());
    return toReturn;
}

This solution can be used for any type of "piece" since all pieces will theoretically extend a common parent, the type of which the returned list can be.

ORIGINAL ANSWER:

Your algorithm makes a lot of extra checks simply because it checks all 9 squares adjacent to the King. For the following co-ordinates:

[0,0] [0,1] [0,2]
[1,0] [1,1] [1,2]
[2,0] [2,1] [2,2]

Where [1,1] notes the point where the King is "seated", the only squares that need to be checked are [1,2], [2,0], [2,1] and [2,2] marked by X in the following snippet.

[O] [O] [O]
[O] [O] [X]
[X] [X] [X]

The reason is that you only need to "move" forward when checking since if a king was in point [1,0], [0,0], [0,1] and [0,2], your king in position [1,1] would have been found by them since they also followed the above searching pattern.

In order for the searching pattern to function correctly, the Kings must be sorted within the List based on their position on the board. Specifically, the first entry in "kings" must be the left-most, top-most seated King with top-most taking priority (For a Cartesian Plane of XY coordinates, a King seated in point [0,2] would be first in the list compared to a King seated in point [1,0]).

You can further limit the iterations your loops do by using a simple "if" before the Square loop which checks 2 conditions, if the x1-x2 and y1-y2 distances are bigger than 2, and skips the current iteration with continue; Since Kings can only move one block, there is no need to iterate over the Squares if it is impossible for the Kings to attack each other.

  • The input is given as is from `LinkedHashMap#values()`, which is insertion ordered and not geometrically ordered. Sorting the copied list is just an additional overhead for the benefit of removing some checks. It doesn't solve the problem I posted. – user1803551 Apr 18 '17 at 01:44
  • 1
    Sorry I misinterpreted your question, I thought you were searching for a faster solution worked on the one you provider. – Alex Papageorgiou Apr 18 '17 at 01:47
  • @user1803551 Can you alter any of the original code in King or any of the provider materials? – Alex Papageorgiou Apr 18 '17 at 02:05
  • Depends on what it is. I can't remove any of the current code, what do you want changed? – user1803551 Apr 18 '17 at 02:08
  • EDIT: I think utilizing streams in general for this particular task is counter-intuitive since streams are used for the processing of data to a final result. Creating a Map of all the possible combinations and then filtering that would actually achieve what you want to do but I think it is more taxing than an optimized algorithm... – Alex Papageorgiou Apr 18 '17 at 03:15
  • ...since if you can access the X and Y coordinates of the Kings, a pair can be found by the simple condition (|x1-x2| < 2) && (|y1-y2| < 2) without using the getThreatenedSquares function. – Alex Papageorgiou Apr 18 '17 at 03:20
  • Solved your question using a stream one liner as requested. Works fine with parallel streams too. – Alex Papageorgiou Apr 18 '17 at 04:02
  • `equals` and `hashCode` are already overridden in the class and what you wrote is a very bad `equals` since (1) it always returns `true`, (2) it changes the state of the object, (3) it doesn't tell you if a King is in fact equal to another, (4) it has an incorrect method signature so it doesn't really override the method even. Sorry, this is a wrong answer. – user1803551 Apr 18 '17 at 15:22
  • The equals method provided is not supposed to compare the objects, it is supposed to utilize the stream's distinct() function, which by factor uses the equals() as the double loop does you provided. This still achieves what you asked, the above code in a stream() format which is exactly what this does. – Alex Papageorgiou Apr 18 '17 at 15:26
  • *The* `equals` method is supposed to do one thing: check if it is equal to another (see the [docs](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object-)). There are no different types of these. Of course, you can create one with the same name and different arguments, but it wouldn't override anything and it wouldn't be used by `distinct`: "*Returns a stream consisting of the distinct elements (according to `Object.equals(Object))` of this stream.)*" – user1803551 Apr 18 '17 at 15:31
  • I know what it is supposed to do. I am simply providing a solution. As far as I remember it doesn't use the Object.equals(Object a) necessarily, you can use a different [function](http://stackoverflow.com/questions/21333646/stream-and-the-distinct-operation). – Alex Papageorgiou Apr 18 '17 at 15:37
  • I've just now quoted from the javadoc that shows it uses `Object.equals(Object)` exactly and nothing else. The link you gave only shows that you have to override `hashCode` whenever overriding `equales`, it's irrelevant. – user1803551 Apr 18 '17 at 15:44
  • Please read what the poster wanted and read the detailed answer of the one who responded. "Apparently, Stream.distinct() indeed uses the hash code of the objects, because when you implement it like I showed above, you get the expected result: 2." I could do a test run and write the whole code structure if you'd like to show you that the above solution indeed achieves what you want to. – Alex Papageorgiou Apr 18 '17 at 16:11
  • Go ahead and try to get correct results with your equals method, it won't work. – user1803551 Apr 18 '17 at 16:37
  • When I get home I will and I will also update the post. – Alex Papageorgiou Apr 18 '17 at 16:47
  • That stream in the if statement is atrocious. – Nick Ziebert Apr 18 '17 at 17:18
  • @NickZiebert how so? The solution with the plain "if" clause is obviously a better one but could you please clarify? – Alex Papageorgiou Apr 18 '17 at 17:27
  • 2
    @AlexPapageorgiou I mean the readability is atrocious. I would break it out into a separate method. – Nick Ziebert Apr 18 '17 at 17:30
  • Oh I will fix that up when I also test the above code works, thanks for pointing it out. – Alex Papageorgiou Apr 18 '17 at 17:31
  • 1
    The other thing I don't understand is why King has a matched boolean. Your turning the King class into having multiple responsibilities. It's all confusing to me. The class also has a List pairs? Kings shouldn't know about each other. What if we wanted another class, Queen? Does this class also need all this junk? – Nick Ziebert Apr 18 '17 at 17:32
  • @NickZiebert & OP, the solution I provider actually works as-is with slight modifications (Nothing major that diverts from the logic of overriding the .equals(Object obj) function). I will post the complete code in the original post soon after I solved the issue of iterating over a nested list. – Alex Papageorgiou Apr 19 '17 at 00:27
  • Update: Even though the solution I provided _does_ work, as in, it returns a Map> with the type of info you want, extraction from the List is impossible due to the list inherently using the .equals() method and thus altering itself. .distinct() **does** call the overriden method as I stated earlier if the hash-codes are the same. The code successfully finds single pairs though but I will stop here. – Alex Papageorgiou Apr 19 '17 at 01:58
  • Only you modified you code since then to change the `equals` method to take `Object` instead of your original `King`, so now `distinct` would work because you actually override the method, but it didn't before. And you're not even checking the cast to `King`, or if the input is `null`, or if they are identical at all, thus breaking the `Object#equals` contract. This is bad. – user1803551 Apr 19 '17 at 17:40
  • I updated my answer based on your requests. This solution should achieve what you are looking for without breaking anything that you wouldn't like. – Alex Papageorgiou Apr 21 '17 at 15:59