搜索

21.8. Sudoku 示例决策(complex pattern matching、回调和 GUI 集成)

download PDF

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.sudoku.*.drl (在 src/main/resources
  • 目标 :演示复杂的模式匹配、问题解决、回调和 GUI 集成

Sudoku 是基于逻辑的数字放置 puzzle。目标是填充 9x9 网格,以便每个列、每个列、每个行以及九个 3x3 区域均包含来自 1 到 9 次的数字。puzzle 设定者提供部分完成的网格,而 puzzle solver 的任务是利用这些限制完成网格。

解决问题的一般策略是,当您插入新数字时,它必须在其特定 3x3 区域、行和列内唯一。这个 Sudoku 示例决策设置使用红帽流程自动化管理器规则来解决 Sudoku puzzles 的问题,并试图解决包含无效条目的缺陷的 puzzles。

Sudoku 示例执行和交互

与其他红帽流程自动化管理器决策示例类似,您可以通过运行 org.drools.examples.sudoku.SudokuExample 类作为 IDE 中的 Java 应用程序来执行 SudokuExample 类。

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

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

图 21.19. 启动时的 Sudoku 示例 GUI

sudoku1

加载 简单示例 时,网格会根据点的初始状态进行填写。

图 21.20. 加载简单示例后的 Sudoku 示例 GUI

sudoku2

从以下选项中选择:

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

    图 21.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 显示在第一行中不允许的两次。

图 21.22. broken Sudoku 示例初始状态

sudoku4

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

IDE 控制台窗口中会显示 solve 规则活动:

检测到无法正常工作的问题

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.

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

sudoku5

名为 Hard 的 Sudoku 示例文件更为复杂,解决规则可能无法解决它们。IDE 控制台窗口中会显示不成功的解决方案:

未解析的硬示例

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

根据仍是单元的候选值集,用于解决中断示例实施标准解析技术的规则。例如,如果一个集合包含单个值,则这是单元的值。对于九个单元里有一个值出现的一个值,规则插入了某些特定单元的"使用解决方案值"类型 设置 的事实。这一事实导致从该单元所属的任何组中所有其他单元格中消除这个值,且值将被重新处理。

示例中的其他规则会减少某些单元的可分值。规则 "naked pair", "hidden pair in row", "hidden pair in column", 和 "hidden pair in square" 消除了可能性,但没有建立解决方案。规则 "位于 行中的 X-wing in rows", "'X-wing in columns"', "interection removal line", and "intersection removal 列" 来执行更复杂的清除。

Sudoku 示例类

org.drools.examples.sudoku.swing 包含了为 Sudoku puzzles 实施框架的以下核心组件:

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

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

package org.drools.examples.sudoku 包含以下用于实施元素 Cell 对象及其各种聚合的类集:

  • CellFile 类,其子类型 CellRowCellColCellSqrCellGroup 类。
  • CellCellGroup 子类的 SetOfNine 中的一个属性 自由 提供带有 type Set<Integer> 的属性。对于 Cell 类,该集合代表个人候选集。对于 CellGroup 类,该集合就是其所有单元集的 union(仍然需要分配的数字集)。

    在 Sudoku 示例中,81 Cell 和 27 CellGroup 对象和由 Cell 属性 cellRowcellColcellSqr 提供的链接,以及 CellGroup 属性 单元单元Cell ll 对象列表)。使用这些组件,您可以编写用于检测特定情况的规则,允许为某个候选项集中的单元或消除值分配值。

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

Sudoku 验证规则(validate.drl)

Sudoku 示例中的 validate.drl 文件包含在单元组中检测重复数字的验证规则。它们组合成 "验证" 认证组,用户可在用户加载点后明确激活规则。

三个规则 "重复为 cell …​" 所有功能的 when 条件如下:

  • 规则中的第一个条件会找到一个带有分配值的单元。
  • 规则中的第二个条件会拉取单元所属的三个单元组。
  • 最终条件找到一个单元(除前一个)的单元(除前一个),其值与第一单元相同,以及同一行、列或方括号,具体取决于规则。

规则 "duicate 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" 是最后触发的最后一个。这个规则会显示信息并停止序列。

规则"确定组"

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

Sudoku 解决规则(sudoku.drl)

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

规则 "set a value", "iminate a value from Cell " 和 "retract settings" 取决于 Setting 对象的存在。第一个规则处理分配给单元的分配,操作会从单元 的空闲 组中删除值。这个组也会降低一个计数器,在零时将控制权返回到名为 fireUntilHalt() 的 Java 应用程序。

规则 "imininate a value from Cell" 的目的是减少与新分配的单元相关的所有单元的列表。最后,当进行所有的精简时,规则 "retract 设置" 会回收触发 设置 事实。

rules "set a value", "iminate a value from a Cell" and "retract set"

// 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" 触发。单个候选者没有单元格时,规则"隐藏 单"触发,但存在包含候选的单元时,这不包括在单元所属的三个组里的其它单元格中。这两个规则都创建并插入 设置 事实。

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

// 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 议会。

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

规则"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

…​"中三个规则" 函数与规则 "naked pair" 类似。这些规则检测一个组只两个单元格中两个数字的子集,且在组的任何其他单元格中都没有发生任何值。这意味着,其他所有候选者都可以从两个单元格中去除以隐藏的隐藏对。

规则"hidden 对在 …​"

// 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

两条规则处理行和列中的 "Xwing "。当值的每两个可能单元(或列)中都只包括两个可能的单元格时,这些候选人也存在于同一列(或行),那么可以删除列内(或行)的所有其他候选者。当遵循这些规则中的模式序列时,请注意如何以方便的词语表达的条件,如 相同的仅导致 具有合适的限制的模式,或以 not 为前缀。

规则 "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 …​" 基于一个方方或单一列内出现某种数量的受限位置。这意味着这个数字必须位于一行或列的两个或者三单元格之一,并可从组所有其他单元的候选单元中删除。这个模式决定了限制发生的情况,然后针对方括号外的每个单元格以及同一单元文件内触发。

rules "interection 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 模糊。为了解决非常困难的网格,规则集需要更复杂的规则。(通常,一些不清点可以通过试用和错误解决。)

Red Hat logoGithubRedditYoutubeTwitter

学习

尝试、购买和销售

社区

关于红帽文档

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

让开源更具包容性

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

關於紅帽

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

© 2024 Red Hat, Inc.