通常情况下,一个好的解决方案是
- 首先分析需求并
- 然后了解数学背景,
- 做适当的设计,
- 使用适当的 C++ 容器和元素
- 最后,编写代码
分析需求后发现,数学背景是构建2个集合的笛卡尔积。
集合中的元素是std::strings。我们从只有一个字符的字符串开始,然后构建笛卡尔积。结果是一个包含 2 个字符的字符串,包含单个字符的所有可能组合。
对于这项任务,构建笛卡尔积非常简单。它只是 2 个元素的字符串连接。通过std::string,我们可以简单地使用“+”运算符进行字符串连接。
所以,在 C++ 中,我们可以像这样使用 2 个嵌套的 for 循环:
for (const Element a : elements)
for (const Element b : B.elements)
cartesianProduct.elements.insert( a + b );
作为我们元素的容器,我们可以使用 STL 中现有的“集合”之一。
-
std::set 具有独特的元素并且是有序的
-
std::mulitset 可以有重复的元素并且是有序的
-
std::unordered_set 具有独特的元素并且没有排序
-
std::unordered_mulitset 可以有重复的元素并且没有顺序
因此,我们可以根据自己的意愿或要求选择任何类型的套装。
由于我们使用 C++ 并进行面向对象编程,因此我们会将所有内容包装在一个类(结构)中并覆盖 2 个运算符:
-
operator * 用于创建笛卡尔积
-
operator << 用于简单输出
所以,如果我们在班级内改变某些东西,外部世界不会受到影响。有了它,我们还可以使用std::vector 来存储元素。没什么大不了的。
而且,为了实现,我们将添加一个特殊功能来构建笛卡尔积。通常 2 个集合的乘积,至少有一个集合是空的,将导致一个空集合。但是,因为我们想为整个集合做笛卡尔积,所以我们想从一个空集合开始,然后在一个循环中构建所有后续集合。
因为在我们的例子中,两个元素的笛卡尔积是一个连接,所以基本上是一个加法,我们可以忍受一个集合是空的(和一个中性元素)。亲爱的所有数学家:请怜悯并原谅我这样做!
通过所有这些准备,我们将构建一个非常简单的包装类和一个更简单直观的“main”函数。请查看以下众多可能的解决方案之一:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
#include <set>
#include <unordered_set>
using Element = std::string;
using SetImplementation = std::unordered_set<Element>;
// Small wrapper to add additional functionality to our set
struct CSet {
SetImplementation elements{};
// Calculate the cartesian product
CSet operator * (const CSet& B) {
// Special handling. If one of the sets is empty, we return the other
if (elements.empty()) return B; if (B.elements.empty()) return *this;
// The resulting set containing the cartesian product
CSet cartesianProduct{};
// For all elements in set A and set B
for (const Element a : elements) for (const Element b : B.elements)
// For strings, the cartesian product is concatenation
cartesianProduct.elements.insert( a + b );
return cartesianProduct;
}
// Create the desired output for the set
friend std::ostream& operator << (std::ostream& os, const CSet& s) {
if (not s.elements.empty()) {
os << "Len: " << s.elements.begin()->size() << '\n';
std::copy(s.elements.begin(), s.elements.end(), std::ostream_iterator<Element>(os, "\n"));
}
return os;
}
};
// Test data
const std::vector<CSet> testSets{
{{"l", "m", "n"}},
{{"v", "g", "h", "k", "L", "Z", "a", "b", "d"}},
{{"M", "q", "r", "u", "v", "g", "h", "k", "l"}},
{{"M", "Q", "R", "Z", "a", "b", "d"}},
{{"M", "Q", "R", "d"}},
{{"d"}} };
// Driver Code
int main() {
CSet cartesianProduct{};
CSet& A{ cartesianProduct };
for (const CSet& B : testSets) {
cartesianProduct = A * B;
std::cout << cartesianProduct;
}
}
如前所述。你可以使用各种不同的套装。只需更改顶部的使用状态,它将继续工作。
如果您更喜欢使用std::vector,那么这也是可能的。我们只会修改类内部。外部代码保持原样。请看以下代码:
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>
using Element = std::string;
using SetImpl = std::vector<Element>;
struct Set {
std::vector<Element> elements{};
Set operator * (const Set& B) {
if (elements.empty()) return B;
if (B.elements.empty()) return *this;
Set cartesianProduct{};
cartesianProduct.elements.resize(elements.size() * B.elements.size());
auto product{ cartesianProduct.elements.begin() };
for (const Element a : elements) for (const Element b : B.elements)
*product++ = a + b;
return cartesianProduct;
}
friend std::ostream& operator << (std::ostream& os, const Set& s) {
if (not s.elements.empty()) {
os << "Len: " << s.elements[0].size() << '\n';
std::copy(s.elements.begin(), s.elements.end(), std::ostream_iterator<Element>(os,"\n"));
}
return os;
}
};
std::vector<Set> testSets{ {{"a","b"}}, {{"c"}}, {{"e", "f", "g"}} };
int main() {
Set cartesianProduct{}, &A{ cartesianProduct };
for (const Set& B : testSets) {
cartesianProduct = A * B;
std::cout << cartesianProduct;
}
}
#endif
#if 0
#include <iostream>
#include <cctype>
#include <climits>
#include <string>
#include <algorithm>
#include <iterator>
int main() {
// Loop. unitl the input number is valid
for (bool numberIsValid{}; not numberIsValid;) {
// Instruct user on what to do
std::cout << "\n\nEnter a number (digits only, no leading 0es) that you would like to reverse:\n";
// Read a number as string and check, if it is valid
if (std::string numberAsString{}; std::getline(std::cin, numberAsString) and
not numberAsString.empty() and
std::all_of(numberAsString.begin(), numberAsString.end(), std::isdigit) and
not (numberAsString[0] == '0') and
numberAsString.size() < std::numeric_limits<unsigned int>::digits10)
{
// Number is valid
numberIsValid = true;
std::cout << "\n\nReverse number is:\t ";
std::copy(numberAsString.rbegin(), numberAsString.rend(), std::ostream_iterator<char>(std::cout));
}
else {
std::cerr << "\nError: Invalid input\n\n";
}
}
}