【问题标题】:How to use streams to get pairs of chess Kings that threaten each other?如何使用流来获得相互威胁的国际象棋国王对?
【发布时间】:2023-03-15 10:42:01
【问题描述】:

虽然国际象棋规则不允许国王互相威胁,但这对于真正的问题来说是一个很好的几何类比。

给定以下几何形状

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

所需算法的结果应该是具有以下条目的数据结构:[K1, K2], [K1, K3], [K2, K3], [K2, K4], [K3, K4]

  • 条目中的顺序无关紧要,因此[Kx, Ky][Ky, Kx] 相同。
  • 数据结构中的条目不需要排序。
  • 国王有 0 到 8 个相邻的国王。

存在以下代码:

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; }
}

还有实用方法

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

返回与给定坐标相邻的 8 个Squares。以上都不是必须使用的,它只是可用的。

输入是Collection&lt;King&gt; kingsColl,包含所有棋盘上没有相关顺序的K(它是LinkedHashMap#values())。换句话说,我当前的非流算法遍历所有国王,对于每一个国王,它都会查看所有尚未查看的国王,并检查匹配条件。

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);
                }
            }
        }
    }
}

这个算法很糟糕,因为它依赖于索引并且需要创建一个索引访问集合,它不能被并行化,并且它进行的检查超出了它的需要。请注意,这里的结果存储在 Map 中,但任何对的集合都可以(包括 Guava)。

我尝试了一些使用流的想法,但我都不想发布,因为它们一团糟(如果有人不相信我会发布常见的“你试过什么?”)。流倾向于解决上述问题,因为它们是独立于实现的。如何使用流实现此算法?

【问题讨论】:

  • 你有什么问题?
  • @Code-Apprentice 标题中的那个。我也把它复制到底部。
  • @maraca 已修复,但是该方法确实指定了 8。

标签: java algorithm java-stream


【解决方案1】:

这是一个重新思考数据结构的邀请。

您的数据结构不仅效率低下,而且存在冗余,这是潜在的错误来源。您有一个包含位置的 Square 对象的集合。如果该集合中的两个 Square 对象具有相同的位置,会发生什么情况?然后,Square 实例可能具有对 King 实例的引用,该实例本身具有 Point 引用。如果该位置与Square 中的Point 不匹配,或者甚至两个Square 实例引用同一个King 实例,该怎么办?

通常,您会使用其中任何一个,一个带有国王的位置列表,例如Collection&lt;Point&gt;,或包含表示国王存在或不存在的对象的数组或列表,例如ChessPiece[][] 其中ChessPiece 是无状态的enum,尽管单个King 实例足以完成实际任务。

在最简单的情况下,您可以使用单个位来表示该字段是否为 King 或为空。这允许使用单个 long 值来表示固定结构,例如 8x8 棋盘。 long 值允许从本质上检查星座,例如

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)

这符合你所说的“不好,因为它依赖于索引”,但实际上,它是如此便宜,以至于考虑并行处理(例如通过 Stream API)是荒谬的。相反,如果您的起点是Point 实例的集合,则将其转换为long 表示、执行分析并将威胁星座转换回Points 将是有益的。

【讨论】:

  • 真的很可爱,但如果他想添加更多的棋子而不是国王怎么办
  • @Nick Ziebert:如果任务还是一样,就像我已经说过的,将源数据转换为long 是有益的,丢弃所有不相关的东西,获取对并转换它们进入目标数据表格。如果您想实施其他检查,您可以从这个答案的模式组成大多数常规国际象棋移动。当然,这需要对片段类型进行额外的(外部)迭代。
  • 你从哪里得到 0x80C0500000000000L?我正在努力理解这一点。
  • @Nick Ziebert:也许它有助于理解0x80_C0_50_00_00_00_00_00L0b10000000_11000000_01010000_00000000_00000000_00000000_00000000_00000000L 相同,并且当您将下划线替换为二进制格式的换行符时,您会得到图片。我没有这样写,因为二进制文字很长,而且 Stackoverflow 的语法高亮不能正确处理下划线。
  • 这非常聪明(+1),但我给出的问题与现实世界的问题类似,而且这个解决方案不会工作,因为它过于简单化了。数据结构是固定的:Kings 列表驻留在 map 的值中,或者您可以遍历所有方格并使用 == null 检查是否有 Kings,由于未命中,效率较低。所有对坐标相同的格子或一个格子中有 2 个国王等的担忧都可以解除,内部证明并非如此。
【解决方案2】:

这是一种使用流的方法。我认为这隔离了组件以创建更好的可读性。但是,如果您正在寻找速度,它可能与更好的算法有关,而不是使用 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).
}

【讨论】:

  • 什么是sqauresOverlap
  • 这将返回一个 List> 而 OP 要求返回一个 Map。 squaresOverlap 我认为是他所指的“更好的算法”。这意味着它会检查国王是否相互威胁。
  • @AlexPapageorgiou 我特别说“注意这里的结果存储在Map,但是任何对的集合都可以(包括番石榴)”。我没有请求Map&lt;King, King&gt;,因为它的一个问题是它允许反向映射,而我将它们视为相同。
  • @user1803551 我理解但仍然返回单个 List> 并不理想,因为它没有显示每个 List 映射到哪个 King 以及使用 parallelStream 时你无法将 List 与单个 King 匹配,因为 parallelStream 不保证按顺序执行比较等。
  • @AlexPapageorgiou 我还没有测试这个答案,但它看起来像返回一个对列表,其中每对由 2 个元素的列表表示。这可以。我的问题是此解决方案所需的脚手架数量。仍然可能是合法的。
【解决方案3】:

更新

我创建了一些代码,无需比较索引即可满足您的需求:

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.

那么所需要做的就是对 getThreatenedSquares 进行一个小改动,它不是返回 Squares,而是使用 getKing() 方法返回受到威胁的 Kings:

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;
}

此解决方案可用于任何类型的“片段”,因为理论上所有片段都将扩展一个共同的父级,返回的列表可以是其类型。

原始答案:

您的算法进行了大量额外检查,仅仅是因为它检查了与国王相邻的所有 9 个方格。对于以下坐标:

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

其中 [1,1] 表示国王“就座”的点,唯一需要检查的方格是 [1,2]、[2,0]、[2,1] 和 [2, 2]在下面的sn-p中用X标记。

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

原因是你只需要在检查时向前“移动”,因为如果国王在点 [1,0]、[0,0]、[0,1] 和 [0,2],你的国王他们会在位置 [1,1] 中找到,因为他们也遵循上述搜索模式。

为了使搜索模式正确运行,K 必须根据其在棋盘上的位置在列表中进行排序。具体来说,“国王”中的第一个条目必须是最左边、最上面的国王,并具有最高优先权(对于 XY 坐标的笛卡尔平面,坐在点 [0,2] 中的国王将是第一个该列表与坐在 [1,0] 点的国王相比)。

如果 x1-x2 和 y1-y2 距离大于 2,您可以通过在 Square 循环之前使用简单的“if”来进一步限制循环执行的迭代,该循环检查 2 个条件,并使用 continue 跳过当前迭代;由于国王只能移动一格,因此如果国王不可能互相攻击,则无需遍历方格。

【讨论】:

  • 输入来自LinkedHashMap#values(),它是插入顺序而不是几何顺序。对复制的列表进行排序只是为了删除一些检查而带来的额外开销。它没有解决我发布的问题。
  • 抱歉,我误解了您的问题,我以为您正在寻找一种更快的解决方案,适用于您提供的解决方案。
  • @user1803551 您可以更改 King 中的任何原始代码或任何提供商材料吗?
  • @AlexPapageorgiou 我的意思是可读性很差。我会把它分解成一个单独的方法。
  • 我不明白的另一件事是为什么 King 有一个匹配的布尔值。你把国王班变成了多重责任。这一切都让我感到困惑。该类也有一个 List 对?国王不应该互相了解。如果我们想要另一个班级怎么办,女王?这个类也需要这些垃圾吗?
猜你喜欢
  • 2013-05-24
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 2022-06-17
  • 2015-05-02
  • 1970-01-01
  • 2014-03-02
  • 1970-01-01
相关资源
最近更新 更多