关于你的陈述
“我正在尝试编写 Petrick 方法,这是一种用于 Quine-McCluskey 算法的技术。”
我会告诉你一个
- 准备使用 Petrick 方法的实现和
- 也是一个成熟的 Quine & McCluskey 实施和
- 我实现的算法的解释
首先,完整的解决方案可以在这里找到:
MCDC
源代码是一个优雅的解决方案,使用集合操作实现高效:
// Functor operator for applying Petricks methhod
ProductTermVector PetricksMethod::operator ()(const CNF& cnf)
{
// We select an iterative approach. Start with the first Element of the CNF (which is a DNF)
// And we store the result of each iterative operation again in this element
DNF resultingDNF{ cnf[0] };
// We will always start with the element 1 (not element 0) because in 0 is the initial value
// or respectively the intermediate result
for (CNF::size_type dnfInCnfIndex = 1; dnfInCnfIndex < cnf.size(); ++dnfInCnfIndex)
{
// Result of multipliying out the intermediate (initial) value with the current CNF Product term
DNF intermediateCalculatedDNF;
// Now go through all elements of the intermediate (initial) product term/DNF
// For (1+2)(3+4) this would be the (1+2) part
for (const ProductTerm& productTermLeftSide : resultingDNF)
{
// Next we will iterate over all Minterms in the next DNF
// For (1+2)(3+4) this would be the (3+4) part
for (const ProductTerm& productTermRightSide : cnf[dnfInCnfIndex])
{
ProductTerm productTerm{ productTermLeftSide }; // Resulting Product term is now 1
// Add all elements from the right side
productTerm.insert(productTermRightSide.begin(), productTermRightSide.end()); // Resulting Product term is now 1,2
intermediateCalculatedDNF.insert(std::move(productTerm)); // Store this one
// And continue to add more product terms. The stl::set will ensure the idempotence law and prevent memory waste
}
}
// And now add all found terms to the result and continue with the next element of the right hand side
// Please note: also here the set will prevent double terms
resultingDNF = std::move(intermediateCalculatedDNF);
}
// Now we have the result (with 10 lines of code). The result contains all product terms in DNF
// But for our prupose we are only interested in the minimum size terms
// so, lets find the element with the minimu size (can be more than one)
uint minLength{ narrow_cast<uint>(std::min_element(resultingDNF.begin(), resultingDNF.end(), [](const ProductTerm & left, const ProductTerm & right) noexcept {return left.size() < right.size(); })->size()) };
// And from the big list of the DNF with all product terms, we copy all elements having the minimu size to the result. These are our best coverage sets
ProductTermVector cheapestVector;
// Copy result and return it to caller
std::copy_if(resultingDNF.begin(), resultingDNF.end(), std::back_inserter(cheapestVector), [&minLength](const ProductTerm& pt) noexcept {return pt.size() == minLength; });
return cheapestVector;
}
净代码行数为 14。使用了一些额外的定义,以便更容易理解和使用类型:
using BooleanVariable = uint_fast8_t;
using ProductTerm = std::set<BooleanVariable>;
using ProductTermVector = std::vector<ProductTerm>;
// Disjunctive Normal Form
using DNF = std::set<ProductTerm>;
// Conjunctive Normal Form
using CNF = std::vector<DNF>;
class PetricksMethod // Functor
{
public:
ProductTermVector operator()(const CNF& cnf); // Functor operator
};
请注意。 “BooleanVariable”类型也可以是字符、字符串或任何您喜欢的类型。这对实现无关紧要,但会对消耗的内存产生影响。
该算法背后的思想是使用“STL 集”并利用集合操作的属性。例如,如果您看一下术语 (a+b)(c+d): 如果您想将其相乘,那么结果将是:ac+ad+bc+bd。如果您将布尔变量视为只有一个成员的特殊乘积项并将该乘积项实现为一个集合,那么操作就是将变量添加到现有集合中。因此,如果您有一个包含“a”的集合,并且您想将其与包含“b”的集合相乘,那么您只需将“b”添加到“a”即可。然后该集合将包含“ab”。
所以,我们将把包含“b”的集合插入到包含“a”的集合中,以此类推。我们将循环执行此操作,将获得 4 个集合(产品术语)。这 4 个集合构成了一个结果 DNF。刚刚计算的 DNF 可以与 CNF 的附加项进一步组合。
如果我们有“(a+b)(c+d)(e+f)”,我们会将其视为“((a+b)(c+d)) (e+f)”。我们首先将前 2 个 MaxTerm 相乘,得到 (ac+ad+bc+bd) 并对 (e+f) 应用相同的算法。我们将始终将新变量添加到现有集合中。我们将迭代地执行此操作,直到评估完整的表达式。
问题是产生的子项很多。但幸运的是,这里集合的属性,具有独特的排序元素会有所帮助。对于 (a+b)(b+a),我们将得到 ab+aa+bb+ba。 stl 集合将为我们提供幂等律。意思是“a”和“a”是“a”,“b”或“b”是“b”。因此,尝试将“a”添加到已经包含“a”的集合中是行不通的。结果仍然是“a”。与完整的产品术语相同,例如 2 次“ab”。它们不会被添加到 DNF 替身中。所以,上述操作的结果是:
ab+aa+bb+ba --> ab+ab+aa+bb --> ab+aa+bb --> a+b+ab
采用这种方法,不会有双重条款,而且通常会更短。
但请注意。计算时间和内存消耗会随着词条的数量呈几何级数增长。因此,该函数只能与有限数量的变量/术语一起应用。
我希望我能给出一个可以理解的解释。如果您需要更多信息,请询问。