如何在 C++ 中打乱数组?
How to shuffle an array in C++?
我有一个数组:
names[4]={john,david,jack,harry};
我想随机播放,例如:
names[4]={jack,david,john,harry};
我尝试使用它,但它只是打乱了数组中第一个单词的字母:
random_shuffle(names->begin(), names->end());
这是完整的代码,它从 .txt 文件中读取名称并放入数组中:
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
ifstream readName("names.txt");
string names[197];
int i = 0;
for (string line; getline(readName, line); ){
readName >> names[i];
i++;
}
readName.close();
random_shuffle(names->begin(), names->end());
for (int i = 0; i < 197; i++) {
cout << names[i] << endl;
}
return 0;
}
我尝试了不同人的一些其他东西,但我无法做到。任何有帮助的东西,谢谢!
这是您的代码,我认为改动最少。有人可能会争辩说我不需要那么多地更改您的第一个 for 循环,但我认为如果您有先见之明知道您正在阅读多少个名字,您不妨使用这些知识。
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator> // std::begin(), std::end(); required for C-arrays
#include <random> // std::mt19937; needed to feed std::shuffle()
#include <string>
// using namespace std; // BAD PRACTICE
int main() {
constexpr int size = 4; // Give your magic number a name; only need to change
// a single location
std::ifstream readName("names.txt");
if (!readName) { // Always check that you successfully opened the file.
std::cerr << "Error opening file.\n";
return 1;
}
std::string names[size];
// int i = 0;
for (int i = 0; i < size; ++i) { // Retool the loop entirely
std::getline(readName, names[i]);
}
readName.close();
// This is a fragile solution. It's only working because the array is in
// scope
std::shuffle(std::begin(names), std::end(names),
std::mt19937{std::random_device{}()});
for (int i = 0; i < size; i++) {
std::cout << names[i]
<< '\n'; // Don't use std::endl unless you actually need it
}
return 0;
}
不过,这不是理想的代码。对输入文件大小的任何更改都需要更改代码并重新编译。最大的单一变化是摆脱 std::random_shuffle
并使用 std::shuffle()
代替。 std::random_shuffle
在 C++14 中弃用并在 C++17 中移除。使用它是不好的。 std::shuffle()
确实增加了提供 PRNG 的要求,但还不错。如果你有一个 PRNG 需要在一个更大的程序中随机化许多不同的东西,它会产生更好的代码。这是因为最好有一个 PRNG 并让它在您的程序中一直存在,而不是不断构建新的。
而 C 数组只是让事情变得有点笨拙。输入 std::vector
.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <random> // std::mt19937; needed to feed std::shuffle()
#include <string>
#include <vector>
int main() {
std::ifstream readName("names.txt");
if (!readName) { // Always check that you successfully opened the file.
std::cerr << "Error opening file.\n";
return 1;
}
std::vector<std::string> names;
std::string name;
while (std::getline(readName, name)) { // Retool the loop entirely
names.push_back(name);
}
readName.close();
std::shuffle(std::begin(names), std::end(names),
std::mt19937{std::random_device{}()});
for (const auto& i : names) {
std::cout << i << '\n';
}
return 0;
}
矢量可以根据需要增长,因此您会看到读取名称的循环变得多么简单。它也更加灵活,因为您不必提前知道预期有多少条目。它会“正常工作”。在调用 std::shuffle()
时,我保留了 std::begin(names)
语法,因为许多人认为这是最佳实践, 但是 如果需要,您也可以使用 names.begin()
因为向量 class 提供了自己的迭代器。
让我们看看你的主要问题:
I tried to use this but it just shuffled the letters of the first word in the array:
random_shuffle(names->begin(), names->end());
它只打乱第一个单词的原因是因为类型和用法。
所以 names
是一个字符串数组。
string names[197];
问题出在C世界。数组是否非常容易衰减为指针(仅通过在表达式中使用)。所以这里 names->
已经衰减为指向数组第一个元素的指针。这允许您使用通常仅适用于指针的 ->
运算符。因此,您在指向数组中第一个元素的指针上调用函数 begin()
和 end()
。因此只有名字被洗牌。
使用 std::begin()
方法修复此问题。
// here std::begin / std::end find the beginning and end
// of the array. So you are shuffling the array.
random_shuffle(std::begin(names), std::end(names));
但我会注意到 random_shuffle()
已经过时了。正如@sweenish 所提到的,您应该使用 std::shuffle()
有关详细信息,请参阅他的回答。
我们可以改进的几件事:
您使用 C 数组存储名称。当然可以,但它容易受到一些问题的影响,因为它无法重新调整大小(除非您认为该文件永远不会被更改,否则这可能是一个问题)。对于遥远的维护者来说可能是一个隐藏的问题。
std::vector<std::string> names; // resizeable container.
我会注意到当前的实现忽略了第一行。然后从每个后续行中读取第一个单词。还有一个小问题,最后一行可能是空的,你将一个空名称读入数组的最后一个元素(但你不会跟踪你读了多少名字,所以除非你使用数组中的所有元素,否则你可能永远不会请注意)。
我会改变它。因为不明显。我会故意和单独地忽略第一行。然后我会简单地将所有第一个单词读入一个向量(所以你知道大小)。
std::string line
std::getline(file, line); // Ignore the first line.
std::string word
while(file >> word) {
names.push_back(word);
std::getline(file, line); // ignore the rest of the line.
}
我们可以想象一下。使用迭代器直接简单地创建数组。
class Line
{
std::string firstWord;
friend std::istream& operator>>(std::istream& stream, Line& data) {
stream >> data.firstWord;
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); retrun stream;
}
operator std::string() const {
return firstWord;
}
};
现在您可以在一行中创建和加载矢量:
std::vector<std::string> names(std::istream_iterator<Line>(file),
std::istream_iterator<Line>{});
最后,使用 foreach 循环可以更轻松地复制名称。也不要在这样的循环中使用 std::endl
。它会在每一行之后强制刷新底层错误。这是非常低效的。
for(auto const& name: names) {
std::cout << name << "\n";
}
所以结果是:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
class Line
{
std::string firstWord;
friend std::istream& operator>>(std::istream& stream, Line& data) {
stream >> data.firstWord;
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); return stream;
}
operator std::string() const {
return firstWord;
}
};
int main()
{
std::ifstream file("names.txt");
std::string line
std::getline(file, line); // Ignore the first line.
std::vector<std::string> names(std::istream_iterator<Line>(file),
std::istream_iterator<Line>{});
random_shuffle(std::begin(names), std::end(names));
for(auto const& name: names) {
std::cout << name << "\n";
}
}
我有一个数组:
names[4]={john,david,jack,harry};
我想随机播放,例如:
names[4]={jack,david,john,harry};
我尝试使用它,但它只是打乱了数组中第一个单词的字母:
random_shuffle(names->begin(), names->end());
这是完整的代码,它从 .txt 文件中读取名称并放入数组中:
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
ifstream readName("names.txt");
string names[197];
int i = 0;
for (string line; getline(readName, line); ){
readName >> names[i];
i++;
}
readName.close();
random_shuffle(names->begin(), names->end());
for (int i = 0; i < 197; i++) {
cout << names[i] << endl;
}
return 0;
}
我尝试了不同人的一些其他东西,但我无法做到。任何有帮助的东西,谢谢!
这是您的代码,我认为改动最少。有人可能会争辩说我不需要那么多地更改您的第一个 for 循环,但我认为如果您有先见之明知道您正在阅读多少个名字,您不妨使用这些知识。
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator> // std::begin(), std::end(); required for C-arrays
#include <random> // std::mt19937; needed to feed std::shuffle()
#include <string>
// using namespace std; // BAD PRACTICE
int main() {
constexpr int size = 4; // Give your magic number a name; only need to change
// a single location
std::ifstream readName("names.txt");
if (!readName) { // Always check that you successfully opened the file.
std::cerr << "Error opening file.\n";
return 1;
}
std::string names[size];
// int i = 0;
for (int i = 0; i < size; ++i) { // Retool the loop entirely
std::getline(readName, names[i]);
}
readName.close();
// This is a fragile solution. It's only working because the array is in
// scope
std::shuffle(std::begin(names), std::end(names),
std::mt19937{std::random_device{}()});
for (int i = 0; i < size; i++) {
std::cout << names[i]
<< '\n'; // Don't use std::endl unless you actually need it
}
return 0;
}
不过,这不是理想的代码。对输入文件大小的任何更改都需要更改代码并重新编译。最大的单一变化是摆脱 std::random_shuffle
并使用 std::shuffle()
代替。 std::random_shuffle
在 C++14 中弃用并在 C++17 中移除。使用它是不好的。 std::shuffle()
确实增加了提供 PRNG 的要求,但还不错。如果你有一个 PRNG 需要在一个更大的程序中随机化许多不同的东西,它会产生更好的代码。这是因为最好有一个 PRNG 并让它在您的程序中一直存在,而不是不断构建新的。
而 C 数组只是让事情变得有点笨拙。输入 std::vector
.
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
#include <random> // std::mt19937; needed to feed std::shuffle()
#include <string>
#include <vector>
int main() {
std::ifstream readName("names.txt");
if (!readName) { // Always check that you successfully opened the file.
std::cerr << "Error opening file.\n";
return 1;
}
std::vector<std::string> names;
std::string name;
while (std::getline(readName, name)) { // Retool the loop entirely
names.push_back(name);
}
readName.close();
std::shuffle(std::begin(names), std::end(names),
std::mt19937{std::random_device{}()});
for (const auto& i : names) {
std::cout << i << '\n';
}
return 0;
}
矢量可以根据需要增长,因此您会看到读取名称的循环变得多么简单。它也更加灵活,因为您不必提前知道预期有多少条目。它会“正常工作”。在调用 std::shuffle()
时,我保留了 std::begin(names)
语法,因为许多人认为这是最佳实践, 但是 如果需要,您也可以使用 names.begin()
因为向量 class 提供了自己的迭代器。
让我们看看你的主要问题:
I tried to use this but it just shuffled the letters of the first word in the array:
random_shuffle(names->begin(), names->end());
它只打乱第一个单词的原因是因为类型和用法。
所以 names
是一个字符串数组。
string names[197];
问题出在C世界。数组是否非常容易衰减为指针(仅通过在表达式中使用)。所以这里 names->
已经衰减为指向数组第一个元素的指针。这允许您使用通常仅适用于指针的 ->
运算符。因此,您在指向数组中第一个元素的指针上调用函数 begin()
和 end()
。因此只有名字被洗牌。
使用 std::begin()
方法修复此问题。
// here std::begin / std::end find the beginning and end
// of the array. So you are shuffling the array.
random_shuffle(std::begin(names), std::end(names));
但我会注意到 random_shuffle()
已经过时了。正如@sweenish 所提到的,您应该使用 std::shuffle()
有关详细信息,请参阅他的回答。
我们可以改进的几件事:
您使用 C 数组存储名称。当然可以,但它容易受到一些问题的影响,因为它无法重新调整大小(除非您认为该文件永远不会被更改,否则这可能是一个问题)。对于遥远的维护者来说可能是一个隐藏的问题。
std::vector<std::string> names; // resizeable container.
我会注意到当前的实现忽略了第一行。然后从每个后续行中读取第一个单词。还有一个小问题,最后一行可能是空的,你将一个空名称读入数组的最后一个元素(但你不会跟踪你读了多少名字,所以除非你使用数组中的所有元素,否则你可能永远不会请注意)。
我会改变它。因为不明显。我会故意和单独地忽略第一行。然后我会简单地将所有第一个单词读入一个向量(所以你知道大小)。
std::string line
std::getline(file, line); // Ignore the first line.
std::string word
while(file >> word) {
names.push_back(word);
std::getline(file, line); // ignore the rest of the line.
}
我们可以想象一下。使用迭代器直接简单地创建数组。
class Line
{
std::string firstWord;
friend std::istream& operator>>(std::istream& stream, Line& data) {
stream >> data.firstWord;
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); retrun stream;
}
operator std::string() const {
return firstWord;
}
};
现在您可以在一行中创建和加载矢量:
std::vector<std::string> names(std::istream_iterator<Line>(file),
std::istream_iterator<Line>{});
最后,使用 foreach 循环可以更轻松地复制名称。也不要在这样的循环中使用 std::endl
。它会在每一行之后强制刷新底层错误。这是非常低效的。
for(auto const& name: names) {
std::cout << name << "\n";
}
所以结果是:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
class Line
{
std::string firstWord;
friend std::istream& operator>>(std::istream& stream, Line& data) {
stream >> data.firstWord;
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); return stream;
}
operator std::string() const {
return firstWord;
}
};
int main()
{
std::ifstream file("names.txt");
std::string line
std::getline(file, line); // Ignore the first line.
std::vector<std::string> names(std::istream_iterator<Line>(file),
std::istream_iterator<Line>{});
random_shuffle(std::begin(names), std::end(names));
for(auto const& name: names) {
std::cout << name << "\n";
}
}