C ++ std::map 带有自定义比较器以保持比赛锦标赛
C ++ std::map with custom comparer for keeping tournament of matches
我想将比赛结果保存在某个容器中。对于每场比赛,我都需要存储球员姓名和分数。例如:
map["player1:player2"] = {2,4};
我不仅想通过键 "player1:player2"
从这个容器中检索,甚至还想通过反键 "player2:player1"
检索,我想得到相反的结果。
我即将使用 std::map
并围绕它制作一些智能包装器。也许有一些使用自定义比较器、自定义检索和保存功能的技巧。
std::map
是一个不错的选择还是其他更好的选择?
编辑:
我将这些评论总结成如下所示的解决方案:
struct Match
{
std::string player1;
std::string player2;
int pointsPlayer1;
int pointsPlayer2;
std::string getKey()
{
return player1 + ":" + player2;
}
Match reverse()
{
Match reversed;
reversed.player1 = player2;
reversed.player2 = player1;
reversed.pointsPlayer1 = pointsPlayer2;
reversed.pointsPlayer2 = pointsPlayer1;
return reversed;
}
};
class Tournament
{
std::map<std::string, Match> _games;
public:
void insert(Match match);
};
void Tournament::insert(Match match)
{
_games.insert({ match.getKey(), match });
Match reversed = match.reverse();
_games.insert({ reversed.getKey(), reversed });
}
我选择更简单的方法,我不介意每个结果都出现两次,因为插入函数每次都会替换匹配的两个并且封装可以保证这一点(它不暴露指针,只暴露结构)。
你可以为这项工作编写一个简单的函数:
string getResult(map<string, string> scores, string key) {
vecor<string> splited = split(key, ":");
if (scores.containsKey(key))
return scores.get(key);
else
return scored.get(splited[1] + ":" + splited[0]);
}
也许这会有所帮助:
#include <map>
#include <array>
#include <string>
class Player {
private:
std::string m_strName;
unsigned m_uScore;
public:
Player() : m_uScore( 0 ) {}
explicit Player( const std::string& strName, unsigned score = 0 ) :
m_strName( strName ),
m_uScore( score )
{}
const std::string& getName() const {
return m_strName;
}
const unsigned getScore() const {
return m_uScore;
}
void setName( const std::string& strName ) {
m_strName = strName;
}
void setScore( const unsigned& uScore ) {
m_uScore = uScore;
}
bool operator==( const Player& p ) const {
if ( m_uScorescore == p.m_uScore && m_strName == p.strName ) {
return true;
}
return false;
}
bool operator!=( const Player& p ) const {
return !operator==( p );
}
bool operator<( const Player& p ) const {
return m_uScore < p.m_uScore;
}
bool operator> ( const Player& p ) const {
return m_uScore > p.m_uScore;
}
const Player& lessThan( const Player& p ) const {
return m_uScore < p.m_uScore ? *this : p;
}
const Player& greaterThan( const Player& p ) const {
return m_uScore > p.m_uScore ? *this : p;
}
}; // Player
int main() {
unsigned uMatch = 1;
// Since you need to compare 2 for each match I did this but isn't necessary
// You can choose which ever method is necessary. But since this
// Has only 2 of the same type of object then this made sense to me.
// I Initialized the std::array with the first results it isn't needed
// but used to show how 2 players are grouped to one entity but yet
// remaining individual objects.
std::array<Player,2> aPlayerResults = { Player( "Player1", 20 ),
Player( "Player2", 10 ) };
// This map then would hold every game-round( match results )
std::map<unsigned, std::array<Player,2>> mMatches;
mMatches[uMatch] = aPlayerResults;
// Now Just Update The Values
uMatch = 2;
aPlayerResults.at(0).setName( std::string( "Player3" ) );
aPlayerResults.at(0).setScore( 19 );
aPlayerResults.at(1).setName( std::string( "Player4" ) );
aPlayerResults.at(1).setScore( 17 );
// Add To Map
mMatches[uMatch] = aPlayerResults;
return 0;
}
正如您所见,玩家是一个 class 对象,将玩家名称与分数相关联并将数据保密,您可以使用辅助函数来设置和获取值,有一个默认构造函数来创建一个稍后填充的空播放器对象,加上一个显式构造函数,该构造函数至少要有一个 std 字符串作为名称,并且分数是可选的。如果 score 留空,那么你有一个在任何比赛之前都存在的球员,一旦比赛结束,你就可以使用 setScore 方法。有一些运算符可以测试 Player1 == Player2 或 !=,以及 < & > 运算符 return if 语句的 true 或 false。如果你想比较两个玩家和 return 一个玩家或另一个基于分数是否 > 或 < 有两个功能可以为你做这件事。所有工具都在这个漂亮的小 class 中,您甚至可以扩展它。
我使用了 std::array 因为我们知道每场比赛只有 2 名玩家互相对抗,例如在国际象棋比赛中。现在,如果您需要更多玩家,您可以增加数组元素的数量,只要该值很小,比如最多小于 10 或 20,如果有更多玩家,那么您将需要切换 std::array 到 std::vector 个 Player 对象或 std::vector 个 Player 对象指针,具体取决于您的需要。该地图只是将 unsigned 作为键或索引与玩家的 std::array 或 std::vector 相关联。地图中的索引让它变得很好,因为每场比赛或比赛都是独一无二的,法线地图不允许重复键。此外,使用 unsigned 使地图的 [] 赋值符号更易于阅读。
当然,您始终可以创建一个函数,通过常量引用接收 2 个玩家或 2 个玩家指针,它们会为您将这些玩家设置到地图中。
你也可以用一堆玩家对象预填充你的地图,这些玩家对象都有唯一的名字,没有分数,然后在每场比赛结束后,你可以使用迭代器通过 for 循环来更新所有分数值,然后创建另一个函数来遍历整个地图,以首先或第二等方式对每个匹配项进行排序。这只是如何实现您所说的内容的指南。从来没有确切的 "Correct Way" 因为在编写程序时有很多方法可以达到相同的预期结果。
首先,使用 std::map
是行不通的。原因很简单,你想往里面插入map["player1:player2"] = {2, 4};
,但是以后你求map["player2:player1"]
的时候就需要return{4, 2}
。因此,您不仅需要不同的键来引用相同的数据(std::map
可以为您提供自定义比较器),而且您还需要不同格式的相同数据,具体取决于键中的顺序,std::map
做不到。
现在,如何解决这个问题?首先,考虑您需要的界面。目前,您具有插入和查询比赛结果的功能。我的 crystal ball 还告诉我你会想要遍历锦标赛中的所有结果,查询比赛是否已经发生并且可能重置 table 的内容。因此,首先去写下这些函数的接口并记录它们的行为,尤其是对于 cornercases。
然后,想想如何实现。最直接的方法可能是使用 map<pair<string,string>, pair<int,int>>
来存储分数。现在,在插入时,您要么冗余地存储结果(即存储 "player1:player2" 和 "player2:player1" 的分数),然后在使用任一变体检索时都会给出正确的结果。或者,规范化顺序(按字典顺序对玩家进行排序)并在检索时,可选地反转查找前键的顺序和查找后结果的顺序以获得正确的顺序。
备注:
- 还有另一种方法:如果映射玩家 X 对玩家 Y 得分多少,您将获得相同的信息。相应的数据结构是
map<string, map<string, int>>
。要插入匹配结果,只需执行 res["player1"]["player2"] = 2;
和 res["player2"]["player1"] = 4;
。我不会那样做,除非可能作为上述接口背后的实现。
- 我更喜欢一对而不是字符串 "player1:player2" 即使我通常不得不像字符串一样显示它。原因很简单,它不会将表示与数据混淆,从而为您提供更清晰的代码。出于同样的原因,我不会例如存储3% 作为字符串或整数值 3,而不是作为浮点值 0.03,因为它有助于更好地根据计算(撇开浮点不准确问题)。
我想将比赛结果保存在某个容器中。对于每场比赛,我都需要存储球员姓名和分数。例如:
map["player1:player2"] = {2,4};
我不仅想通过键 "player1:player2"
从这个容器中检索,甚至还想通过反键 "player2:player1"
检索,我想得到相反的结果。
我即将使用 std::map
并围绕它制作一些智能包装器。也许有一些使用自定义比较器、自定义检索和保存功能的技巧。
std::map
是一个不错的选择还是其他更好的选择?
编辑:
我将这些评论总结成如下所示的解决方案:
struct Match
{
std::string player1;
std::string player2;
int pointsPlayer1;
int pointsPlayer2;
std::string getKey()
{
return player1 + ":" + player2;
}
Match reverse()
{
Match reversed;
reversed.player1 = player2;
reversed.player2 = player1;
reversed.pointsPlayer1 = pointsPlayer2;
reversed.pointsPlayer2 = pointsPlayer1;
return reversed;
}
};
class Tournament
{
std::map<std::string, Match> _games;
public:
void insert(Match match);
};
void Tournament::insert(Match match)
{
_games.insert({ match.getKey(), match });
Match reversed = match.reverse();
_games.insert({ reversed.getKey(), reversed });
}
我选择更简单的方法,我不介意每个结果都出现两次,因为插入函数每次都会替换匹配的两个并且封装可以保证这一点(它不暴露指针,只暴露结构)。
你可以为这项工作编写一个简单的函数:
string getResult(map<string, string> scores, string key) {
vecor<string> splited = split(key, ":");
if (scores.containsKey(key))
return scores.get(key);
else
return scored.get(splited[1] + ":" + splited[0]);
}
也许这会有所帮助:
#include <map>
#include <array>
#include <string>
class Player {
private:
std::string m_strName;
unsigned m_uScore;
public:
Player() : m_uScore( 0 ) {}
explicit Player( const std::string& strName, unsigned score = 0 ) :
m_strName( strName ),
m_uScore( score )
{}
const std::string& getName() const {
return m_strName;
}
const unsigned getScore() const {
return m_uScore;
}
void setName( const std::string& strName ) {
m_strName = strName;
}
void setScore( const unsigned& uScore ) {
m_uScore = uScore;
}
bool operator==( const Player& p ) const {
if ( m_uScorescore == p.m_uScore && m_strName == p.strName ) {
return true;
}
return false;
}
bool operator!=( const Player& p ) const {
return !operator==( p );
}
bool operator<( const Player& p ) const {
return m_uScore < p.m_uScore;
}
bool operator> ( const Player& p ) const {
return m_uScore > p.m_uScore;
}
const Player& lessThan( const Player& p ) const {
return m_uScore < p.m_uScore ? *this : p;
}
const Player& greaterThan( const Player& p ) const {
return m_uScore > p.m_uScore ? *this : p;
}
}; // Player
int main() {
unsigned uMatch = 1;
// Since you need to compare 2 for each match I did this but isn't necessary
// You can choose which ever method is necessary. But since this
// Has only 2 of the same type of object then this made sense to me.
// I Initialized the std::array with the first results it isn't needed
// but used to show how 2 players are grouped to one entity but yet
// remaining individual objects.
std::array<Player,2> aPlayerResults = { Player( "Player1", 20 ),
Player( "Player2", 10 ) };
// This map then would hold every game-round( match results )
std::map<unsigned, std::array<Player,2>> mMatches;
mMatches[uMatch] = aPlayerResults;
// Now Just Update The Values
uMatch = 2;
aPlayerResults.at(0).setName( std::string( "Player3" ) );
aPlayerResults.at(0).setScore( 19 );
aPlayerResults.at(1).setName( std::string( "Player4" ) );
aPlayerResults.at(1).setScore( 17 );
// Add To Map
mMatches[uMatch] = aPlayerResults;
return 0;
}
正如您所见,玩家是一个 class 对象,将玩家名称与分数相关联并将数据保密,您可以使用辅助函数来设置和获取值,有一个默认构造函数来创建一个稍后填充的空播放器对象,加上一个显式构造函数,该构造函数至少要有一个 std 字符串作为名称,并且分数是可选的。如果 score 留空,那么你有一个在任何比赛之前都存在的球员,一旦比赛结束,你就可以使用 setScore 方法。有一些运算符可以测试 Player1 == Player2 或 !=,以及 < & > 运算符 return if 语句的 true 或 false。如果你想比较两个玩家和 return 一个玩家或另一个基于分数是否 > 或 < 有两个功能可以为你做这件事。所有工具都在这个漂亮的小 class 中,您甚至可以扩展它。
我使用了 std::array 因为我们知道每场比赛只有 2 名玩家互相对抗,例如在国际象棋比赛中。现在,如果您需要更多玩家,您可以增加数组元素的数量,只要该值很小,比如最多小于 10 或 20,如果有更多玩家,那么您将需要切换 std::array 到 std::vector 个 Player 对象或 std::vector 个 Player 对象指针,具体取决于您的需要。该地图只是将 unsigned 作为键或索引与玩家的 std::array 或 std::vector 相关联。地图中的索引让它变得很好,因为每场比赛或比赛都是独一无二的,法线地图不允许重复键。此外,使用 unsigned 使地图的 [] 赋值符号更易于阅读。
当然,您始终可以创建一个函数,通过常量引用接收 2 个玩家或 2 个玩家指针,它们会为您将这些玩家设置到地图中。
你也可以用一堆玩家对象预填充你的地图,这些玩家对象都有唯一的名字,没有分数,然后在每场比赛结束后,你可以使用迭代器通过 for 循环来更新所有分数值,然后创建另一个函数来遍历整个地图,以首先或第二等方式对每个匹配项进行排序。这只是如何实现您所说的内容的指南。从来没有确切的 "Correct Way" 因为在编写程序时有很多方法可以达到相同的预期结果。
首先,使用 std::map
是行不通的。原因很简单,你想往里面插入map["player1:player2"] = {2, 4};
,但是以后你求map["player2:player1"]
的时候就需要return{4, 2}
。因此,您不仅需要不同的键来引用相同的数据(std::map
可以为您提供自定义比较器),而且您还需要不同格式的相同数据,具体取决于键中的顺序,std::map
做不到。
现在,如何解决这个问题?首先,考虑您需要的界面。目前,您具有插入和查询比赛结果的功能。我的 crystal ball 还告诉我你会想要遍历锦标赛中的所有结果,查询比赛是否已经发生并且可能重置 table 的内容。因此,首先去写下这些函数的接口并记录它们的行为,尤其是对于 cornercases。
然后,想想如何实现。最直接的方法可能是使用 map<pair<string,string>, pair<int,int>>
来存储分数。现在,在插入时,您要么冗余地存储结果(即存储 "player1:player2" 和 "player2:player1" 的分数),然后在使用任一变体检索时都会给出正确的结果。或者,规范化顺序(按字典顺序对玩家进行排序)并在检索时,可选地反转查找前键的顺序和查找后结果的顺序以获得正确的顺序。
备注:
- 还有另一种方法:如果映射玩家 X 对玩家 Y 得分多少,您将获得相同的信息。相应的数据结构是
map<string, map<string, int>>
。要插入匹配结果,只需执行res["player1"]["player2"] = 2;
和res["player2"]["player1"] = 4;
。我不会那样做,除非可能作为上述接口背后的实现。 - 我更喜欢一对而不是字符串 "player1:player2" 即使我通常不得不像字符串一样显示它。原因很简单,它不会将表示与数据混淆,从而为您提供更清晰的代码。出于同样的原因,我不会例如存储3% 作为字符串或整数值 3,而不是作为浮点值 0.03,因为它有助于更好地根据计算(撇开浮点不准确问题)。