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,因为它有助于更​​好地根据计算(撇开浮点不准确问题)。