85.8. Sudoku 示例决策(组合模式匹配、回调和 GUI 集成)


Sudoku 示例决定基于流行数量 puzzle Sudoku,演示了如何根据各种限制在 Red Hat Process Automation Manager 中使用规则来查找大型潜在解决方案空间的解决方案。本例还演示了如何将 Red Hat Process Automation Manager 规则集成到图形用户界面(GUI)中,在这种情况下,使用基于 Swing 的桌面应用程序,以及如何使用回调与正在运行的决策引擎交互,根据工作内存中的更改更新 GUI。

以下是 Sudoku 示例的概述:

  • 名称 :sudoku
  • 主类org.drools.examples.sudoku.SudokuExample (在 src/main/java中)
  • 模块drools-examples
  • 类型 :Java 应用程序
  • 规则文件:org.drools.examples.sudoku114.drl (在 src/main/resources中)
  • 目标 :演示复杂模式匹配、问题问题、回调和 GUI 集成

Sudoku 是一个基于逻辑的数字放置 puzzle。目标是填充 9x9 网格,以便每列、每行和每 9 个 3x3 区域都包含从 1 到 9 次的数字。puzzle setter 提供了部分完成的网格,puzzle solver 的任务是完成具有这些限制的网格。

解决问题的一般策略是确保在插入新数字时,必须在其特定的 3x3 区域、行和列中是唯一的。这个 Sudoku 示例决策使用 Red Hat Process Automation Manager 规则从各种困难级别解决 Sudoku puzzles,并尝试解决包含无效条目的缺陷。

Sudoku 示例执行和交互

与其他 Red Hat Process Automation Manager 决策示例类似,您可以通过在 IDE 中作为 Java 应用程序运行 org.drools.examples.sudoku.SudokuExample 类来执行 Sudoku 示例。

当您执行 Sudoku 示例时,会出现 droyku Example GUI 窗口。此窗口包含一个空的网格,但程序在内部存储各种您可以加载和解决。

File Samples Simple 加载其中一个示例。请注意,所有按钮都被禁用,直到网格加载为止。

图 85.19. 启动后,Sudoku 示例 GUI

sudoku1

当您加载 简单示例 时,网格会根据 puzzle 的初始状态填充。

图 85.20. 在载入简单示例后,Sudoku 示例 GUI

sudoku2

�以下选项中选择:

  • Solve 触发 Sudoku 示例中定义的规则,以填充剩余的值,并使按钮再次不活跃。

    图 85.21. 已解决简单示例

    sudoku3
  • Step 查看规则集找到的下一个数字。IDE 中的控制台窗口显示有关正在执行的规则的详细信息,以解决问题。

    IDE 控制台中的步骤执行输出

    single 8 at [0,1]
    column elimination due to [1,2]: remove 9 from [4,2]
    hidden single 9 at [1,2]
    row elimination due to [2,8]: remove 7 from [2,4]
    remove 6 from [3,8] due to naked pair at [3,2] and [3,7]
    hidden pair in row at [4,6] and [4,4]

  • 单击 Dump 以查看网格的状态,以及显示已建立的值或剩余值的单元格。

    在 IDE 控制台中转储执行输出

            Col: 0     Col: 1     Col: 2     Col: 3     Col: 4     Col: 5     Col: 6     Col: 7     Col: 8
    Row 0:  123456789  --- 5 ---  --- 6 ---  --- 8 ---  123456789  --- 1 ---  --- 9 ---  --- 4 ---  123456789
    Row 1:  --- 9 ---  123456789  123456789  --- 6 ---  123456789  --- 5 ---  123456789  123456789  --- 3 ---
    Row 2:  --- 7 ---  123456789  123456789  --- 4 ---  --- 9 ---  --- 3 ---  123456789  123456789  --- 8 ---
    Row 3:  --- 8 ---  --- 9 ---  --- 7 ---  123456789  --- 4 ---  123456789  --- 6 ---  --- 3 ---  --- 5 ---
    Row 4:  123456789  123456789  --- 3 ---  --- 9 ---  123456789  --- 6 ---  --- 8 ---  123456789  123456789
    Row 5:  --- 4 ---  --- 6 ---  --- 5 ---  123456789  --- 8 ---  123456789  --- 2 ---  --- 9 ---  --- 1 ---
    Row 6:  --- 5 ---  123456789  123456789  --- 2 ---  --- 6 ---  --- 9 ---  123456789  123456789  --- 7 ---
    Row 7:  --- 6 ---  123456789  123456789  --- 5 ---  123456789  --- 4 ---  123456789  123456789  --- 9 ---
    Row 8:  123456789  --- 4 ---  --- 9 ---  --- 7 ---  123456789  --- 8 ---  --- 3 ---  --- 5 ---  123456789

Sudoku 示例包含一个破坏的示例文件,在示例中定义的规则可以解析。

File Samples !DELIBERATELY BROKEN! 加载有问题的示例。网格从某些问题开始,例如,第一行中的 5 值会出现两次,而这不允许使用。

图 85.22. broken Sudoku 示例初始状态

sudoku4

单击 Solve,以将过期规则应用到此无效的网格。Sudoku 示例中的关联规则会检测示例中的问题,并尝试尽可能解决 puzzle。这个过程没有完成,并保留一些单元为空。

过期规则活动显示在 IDE 控制台窗口中:

在有问题的示例中检测到问题

cell [0,8]: 5 has a duplicate in row 0
cell [0,0]: 5 has a duplicate in row 0
cell [6,0]: 8 has a duplicate in col 0
cell [4,0]: 8 has a duplicate in col 0
Validation complete.

图 85.23. 有问题的示例解决方案尝试

sudoku5

标记为 Hard 的 Sudoku 文件示例更为复杂,一般规则可能无法解决它们。IDE 控制台窗口中会显示失败的解决方案尝试:

未解析的硬链接

Validation complete.
...
Sorry - can't solve this grid.

解决损坏的样本的规则根据仍为单元候选者的值集合实施标准过期技术。例如,如果集合包含单个值,则这是单元的值。对于在 9 个单元组中出现一个值,规则会插入带有某些特定单元的解决方案值的类型 设置 事实。这将导致从单元所属的所有其他单元中分离这个值,并且该值会被重新遍历。

示例中的其他规则可减少某些单元的感知值。规则 "naked pair", "hidden pair in row", "hidden pair in column", and "hidden pair in physical" 会消除可能性,但不建立解决方案。规则 "X-wings in rows", "'X-wings in column"', "intersection removal row", 和 "intersection removal column" 会执行更复杂的背景。

Sudoku 示例类

软件包 org.drools.examples.sudoku.swing 包含以下核心类集合,该类实现了 Sudoku puzzles 的框架:

  • SudokuGridModel 类定义了一个接口,用来存储 Sudoku puzzle 作为 Cell 对象的 9x9 网格。
  • SudokuGridView 类是一个 Swing 组件,它可以视觉化 SudokuGridModel 类的任何实现。
  • SudokuGridEventSudokuGridListener 类在模型和视图之间进行通信状态更改。当单元值被解决或更改时,会触发事件。
  • SudokuGridSamples 类提供了部分填充 Sudoku puzzles 用于演示目的。
注意

这个软件包对 Red Hat Process Automation Manager 库没有任何依赖项。

软件包 org.drools.examples.sudoku 包含以下核心类集合,它们实现了元素 Cell 对象及其各种聚合:

  • CellFile 类,带有子类型 CellRowCellColCellSqr,它们都是 CellGroup 类的子类型。
  • SetOfNineCellCellGroup 子类,它提供了一个没有类型 Set & lt;Integer> 的属性。对于 Cell 类,该集合代表单个候选集。对于 CellGroup 类,该集合是其单元的所有候选集合(仍需要分配的数字集)。

    在 Sudoku 示例中,有 81 Cell 和 27 CellGroup 对象,以及 Cell 属性 cellRowcellColcellSqr 以及 CellGroup 属性 单元 提供的链接。使用这些组件,您可以编写规则来检测特定情况,允许将值分配给单元,或从某些候选集中删除值。

  • Setting 类用于触发值分配附带的操作。所有规则中都会使用 设置 事实,以检测新情况以避免重新发生中间状态。
  • "Step" 不会定期终止时,在低优先级规则中使用 Stepping 类来执行紧急停止。这个行为表示程序无法解决模糊。
  • 主类 org.drools.examples.sudoku.SudokuExample 实施了组合了所有这些组件的 Java 应用程序。

Sudoku 验证规则(validate.drl)

Sudoku 示例中的 validate.drl 文件包含验证规则,用于检测单元组中重复数字。它们合并到一个 "validate" 站组中,允许用户在用户加载 puzzle 后明确激活该规则。

三种规则 "duplicate in cell …​" all 功能 的时间 条件如下:

  • 规则中的第一个条件会找到带有分配值的单元。
  • 规则中的第二个条件会拉取到该单元所属的三个单元组。
  • 根据规则,最终条件找到一个单元(除前一个除外),其值为第一个单元和同一行、列或方括号。

规则 "duplicate in cell …​"

rule "duplicate in cell row"
  when
    $c: Cell( $v: value != null )
    $cr: CellRow( cells contains $c )
    exists Cell( this != $c, value == $v, cellRow == $cr )
  then
    System.out.println( "cell " + $c.toString() + " has a duplicate in row " + $cr.getNumber() );
end

rule "duplicate in cell col"
  when
    $c: Cell( $v: value != null )
    $cc: CellCol( cells contains $c )
    exists Cell( this != $c, value == $v, cellCol == $cc )
  then
    System.out.println( "cell " + $c.toString() + " has a duplicate in col " + $cc.getNumber() );
end

rule "duplicate in cell sqr"
  when
    $c: Cell( $v: value != null )
    $cs: CellSqr( cells contains $c )
    exists Cell( this != $c, value == $v, cellSqr == $cs )
  then
    System.out.println( "cell " + $c.toString() + " has duplicate in its square of nine." );
end

规则 "terminate group" 是最后一个要触发的规则。此规则打印消息并停止序列。

规则 "terminate group"

rule "terminate group"
    salience -100
  when
  then
    System.out.println( "Validation complete." );
    drools.halt();
end

Sudokucertification rules (sudoku.drl)

Sudoku 示例中的 sudoku.drl 文件包含三种类型的规则:一个组处理到单元的数字分配,另一个组检测到可行的分配,第三个组从 candidate 集合中删除值。

规则 "set a value", "eliminate a value from Cell", 和 "retract setting" 取决于 Setting 对象的存在。第一个规则处理到单元的分配,以及从单元格的三个组 的空闲 集合中删除值的操作。这个组也会减少计数器,当零时,将控制返回到名为 fireUntilHalt () 的 Java 应用程序。

规则 "eliminate a value from Cell" 的目的是减少与新分配的单元相关的所有单元的候选列表。最后,当完成所有提升后,规则 " Retract 设置" 会重新遍历触发器 设置 事实。

规则 "set a value", "eliminate a value from a Cell", and "retract setting"

// A Setting object is inserted to define the value of a Cell.
// Rule for updating the cell and all cell groups that contain it
rule "set a value"
  when
    // A Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // A matching Cell, with no value set
    $c: Cell( rowNo == $rn, colNo == $cn, value == null,
              $cr: cellRow, $cc: cellCol, $cs: cellSqr )

    // Count down
    $ctr: Counter( $count: count )
  then
    // Modify the Cell by setting its value.
    modify( $c ){ setValue( $v ) }
    // System.out.println( "set cell " + $c.toString() );
    modify( $cr ){ blockValue( $v ) }
    modify( $cc ){ blockValue( $v ) }
    modify( $cs ){ blockValue( $v ) }
    modify( $ctr ){ setCount( $count - 1 ) }
end

// Rule for removing a value from all cells that are siblings
// in one of the three cell groups
rule "eliminate a value from Cell"
  when
    // A Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // The matching Cell, with the value already set
    Cell( rowNo == $rn, colNo == $cn, value == $v, $exCells: exCells )

    // For all Cells that are associated with the updated cell
    $c: Cell( free contains $v ) from $exCells
  then
    // System.out.println( "clear " + $v + " from cell " + $c.posAsString()  );
    // Modify a related Cell by blocking the assigned value.
    modify( $c ){ blockValue( $v ) }
end

// Rule for eliminating the Setting fact
rule "retract setting"
  when
    // A Setting with row and column number, and a value
    $s: Setting( $rn: rowNo, $cn: colNo, $v: value )

    // The matching Cell, with the value already set
    $c: Cell( rowNo == $rn, colNo == $cn, value == $v )

    // This is the negation of the last pattern in the previous rule.
    // Now the Setting fact can be safely retracted.
    not( $x: Cell( free contains $v )
         and
         Cell( this == $c, exCells contains $x ) )
  then
    // System.out.println( "done setting cell " + $c.toString() );
    // Discard the Setter fact.
    delete( $s );
    // Sudoku.sudoku.consistencyCheck();
end

双向规则会检测一个可能为单元分配数字的情况。规则 "single" 会为 Cell 触发一个包含单个数字的候选集。当单个候选不存在单元格时,规则 "hidden single" 会触发,但当单元存在包含候选者的单元格时,这个候选都不包括在单元格所属的三个组中。规则创建和插入 设置 事实。

规则"单一"和"隐藏单一"

// Detect a set of candidate values with cardinality 1 for some Cell.
// This is the value to be set.
rule "single"
  when
    // Currently no setting underway
    not Setting()

    // One element in the "free" set
    $c: Cell( $rn: rowNo, $cn: colNo, freeCount == 1 )
  then
    Integer i = $c.getFreeValue();
    if (explain) System.out.println( "single " + i + " at " + $c.posAsString() );
    // Insert another Setter fact.
    insert( new Setting( $rn, $cn, i ) );
end

// Detect a set of candidate values with a value that is the only one
// in one of its groups. This is the value to be set.
rule "hidden single"
  when
    // Currently no setting underway
    not Setting()
    not Cell( freeCount == 1 )

    // Some integer
    $i: Integer()

    // The "free" set contains this number
    $c: Cell( $rn: rowNo, $cn: colNo, freeCount > 1, free contains $i )

    // A cell group contains this cell $c.
    $cg: CellGroup( cells contains $c )
    // No other cell from that group contains $i.
    not ( Cell( this != $c, free contains $i ) from $cg.getCells() )
  then
    if (explain) System.out.println( "hidden single " + $i + " at " + $c.posAsString() );
    // Insert another Setter fact.
    insert( new Setting( $rn, $cn, $i ) );
end

来自最多组的规则,可以是独立或三组,可以手动实施用于手动的 Sudoku puzzles 的各种技术。

规则 "naked pair" 在组的两个单元中检测相同的大小为 2 的候选集合。这两个值可以从该组的所有其他候选集合中删除。

规则"naked pair"

// A "naked pair" is two cells in some cell group with their sets of
// permissible values being equal with cardinality 2. These two values
// can be removed from all other candidate lists in the group.
rule "naked pair"
  when
    // Currently no setting underway
    not Setting()
    not Cell( freeCount == 1 )

    // One cell with two candidates
    $c1: Cell( freeCount == 2, $f1: free, $r1: cellRow, $rn1: rowNo, $cn1: colNo, $b1: cellSqr )

    // The containing cell group
    $cg: CellGroup( freeCount > 2, cells contains $c1 )

    // Another cell with two candidates, not the one we already have
    $c2: Cell( this != $c1, free == $f1 /*** , rowNo >= $rn1, colNo >= $cn1 ***/ ) from $cg.cells

    // Get one of the "naked pair".
    Integer( $v: intValue ) from $c1.getFree()

    // Get some other cell with a candidate equal to one from the pair.
    $c3: Cell( this != $c1 && != $c2, freeCount > 1, free contains $v ) from $cg.cells
  then
    if (explain) System.out.println( "remove " + $v + " from " + $c3.posAsString() + " due to naked pair at " + $c1.posAsString() + " and " + $c2.posAsString() );
    // Remove the value.
    modify( $c3 ){ blockValue( $v ) }
end

三个规则 "hidden pair in …​" 函数类似于规则 "naked pair "。这些规则检测一个组的两个单元中有两个数字的子集,且在组的任何其他单元中不会发生值。这意味着,所有其他候选者可以从两个单元格中删除。

规则"hidden pair in …​"

// If two cells within the same cell group contain candidate sets with more than
// two values, with two values being in both of them but in none of the other
// cells, then we have a "hidden pair". We can remove all other candidates from
// these two cells.
rule "hidden pair in row"
  when
    // Currently no setting underway
    not Setting()
    not Cell( freeCount == 1 )

    // Establish a pair of Integer facts.
    $i1: Integer()
    $i2: Integer( this > $i1 )

    // Look for a Cell with these two among its candidates. (The upper bound on
    // the number of candidates avoids a lot of useless work during startup.)
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2, $cellRow: cellRow )

    // Get another one from the same row, with the same pair among its candidates.
    $c2: Cell( this != $c1, cellRow == $cellRow, freeCount > 2, free contains $i1 && contains $i2 )

    // Ascertain that no other cell in the group has one of these two values.
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellRow.getCells() )
  then
    if( explain) System.out.println( "hidden pair in row at " + $c1.posAsString() + " and " + $c2.posAsString() );
    // Set the candidate lists of these two Cells to the "hidden pair".
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

rule "hidden pair in column"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i1: Integer()
    $i2: Integer( this > $i1 )
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2, $cellCol: cellCol )
    $c2: Cell( this != $c1, cellCol == $cellCol, freeCount > 2, free contains $i1 && contains $i2 )
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellCol.getCells() )
  then
    if (explain) System.out.println( "hidden pair in column at " + $c1.posAsString() + " and " + $c2.posAsString() );
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

rule "hidden pair in square"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i1: Integer()
    $i2: Integer( this > $i1 )
    $c1: Cell( $rn1: rowNo, $cn1: colNo, freeCount > 2 && < 9, free contains $i1 && contains $i2,
               $cellSqr: cellSqr )
    $c2: Cell( this != $c1, cellSqr == $cellSqr, freeCount > 2, free contains $i1 && contains $i2 )
    not( Cell( this != $c1 && != $c2, free contains $i1 || contains $i2 ) from $cellSqr.getCells() )
  then
    if (explain) System.out.println( "hidden pair in square " + $c1.posAsString() + " and " + $c2.posAsString() );
    modify( $c1 ){ blockExcept( $i1, $i2 ) }
    modify( $c2 ){ blockExcept( $i1, $i2 ) }
end

两个规则处理行和 列中的"X-wings "。当两个不同的行(或列)中只有两个可能的值的单元,且这些候选者也位于同一列(或行),那么可以消除列(或行)中这个值的所有其他候选者。当您遵循其中一个规则中的模式序列时,请注意,如何按照单词(如 相同或 会导致带有适当约束或前缀的模式)表示的条件。

规则 "X-wings in …​"

rule "X-wings in rows"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    $ca1: Cell( freeCount > 1, free contains $i,
                $ra: cellRow, $rano: rowNo,         $c1: cellCol,        $c1no: colNo )
    $cb1: Cell( freeCount > 1, free contains $i,
                $rb: cellRow, $rbno: rowNo > $rano,      cellCol == $c1 )
    not( Cell( this != $ca1 && != $cb1, free contains $i ) from $c1.getCells() )

    $ca2: Cell( freeCount > 1, free contains $i,
                cellRow == $ra, $c2: cellCol,       $c2no: colNo > $c1no )
    $cb2: Cell( freeCount > 1, free contains $i,
                cellRow == $rb,      cellCol == $c2 )
    not( Cell( this != $ca2 && != $cb2, free contains $i ) from $c2.getCells() )

    $cx: Cell( rowNo == $rano || == $rbno, colNo != $c1no && != $c2no,
               freeCount > 1, free contains $i )
  then
    if (explain) {
        System.out.println( "X-wing with " + $i + " in rows " +
            $ca1.posAsString() + " - " + $cb1.posAsString() +
            $ca2.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

rule "X-wings in columns"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    $ca1: Cell( freeCount > 1, free contains $i,
                $c1: cellCol, $c1no: colNo,         $ra: cellRow,        $rano: rowNo )
    $ca2: Cell( freeCount > 1, free contains $i,
                $c2: cellCol, $c2no: colNo > $c1no,      cellRow == $ra )
    not( Cell( this != $ca1 && != $ca2, free contains $i ) from $ra.getCells() )

    $cb1: Cell( freeCount > 1, free contains $i,
                cellCol == $c1, $rb: cellRow,  $rbno: rowNo > $rano )
    $cb2: Cell( freeCount > 1, free contains $i,
                cellCol == $c2,      cellRow == $rb )
    not( Cell( this != $cb1 && != $cb2, free contains $i ) from $rb.getCells() )

    $cx: Cell( colNo == $c1no || == $c2no, rowNo != $rano && != $rbno,
               freeCount > 1, free contains $i )
  then
    if (explain) {
        System.out.println( "X-wing with " + $i + " in columns " +
            $ca1.posAsString() + " - " + $ca2.posAsString() +
            $cb1.posAsString() + " - " + $cb2.posAsString() + ", remove from " + $cx.posAsString()  );
    }
    modify( $cx ){ blockValue( $i ) }
end

这两个规则 "intersection removal …​" 基于一个方括号中的某个数字的限制,可以在一行或单个列中出现。这意味着这个数字必须在行或列的两或三个单元之一,并且可以从组所有其他单元的候选集合中删除。模式建立受限发生,然后针对方括号和同一单元文件之外的每个单元触发。

规则 "intersection removal …​"

rule "intersection removal column"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    // Occurs in a Cell
    $c: Cell( free contains $i, $cs: cellSqr, $cc: cellCol )
    // Does not occur in another cell of the same square and a different column
    not Cell( this != $c, free contains $i, cellSqr == $cs, cellCol != $cc )

    // A cell exists in the same column and another square containing this value.
    $cx: Cell( freeCount > 1, free contains $i, cellCol == $cc, cellSqr != $cs )
  then
    // Remove the value from that other cell.
    if (explain) {
        System.out.println( "column elimination due to " + $c.posAsString() +
                            ": remove " + $i + " from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

rule "intersection removal row"
  when
    not Setting()
    not Cell( freeCount == 1 )

    $i: Integer()
    // Occurs in a Cell
    $c: Cell( free contains $i, $cs: cellSqr, $cr: cellRow )
    // Does not occur in another cell of the same square and a different row.
    not Cell( this != $c, free contains $i, cellSqr == $cs, cellRow != $cr )

    // A cell exists in the same row and another square containing this value.
    $cx: Cell( freeCount > 1, free contains $i, cellRow == $cr, cellSqr != $cs )
  then
    // Remove the value from that other cell.
    if (explain) {
        System.out.println( "row elimination due to " + $c.posAsString() +
                            ": remove " + $i + " from " + $cx.posAsString() );
    }
    modify( $cx ){ blockValue( $i ) }
end

这些规则对于许多但不是 Sudoku puzzles 而言已经足够了。为了解决非常困难,规则集需要更复杂的规则。(类似地,只有试用和错误才能解决一些模糊。)

Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

通过我们的产品和服务,以及可以信赖的内容,帮助红帽用户创新并实现他们的目标。 了解我们当前的更新.

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

Theme

© 2026 Red Hat
返回顶部