Search code examples
c++stringvectorsetelement

How to access an element of a std::vector<std::vector<std::set<std::string>>> in C++?


I am currently trying to rewrite a Github Repo which calculates the equity of a range vs. range. I am also trying to connect my python code to this C++ code, because to calculate equity the author used a Montecarlo simulation. That is just the background though. To do that I need to acces each and every element of the ranges. So what I have is a vector containing vectors which represent every opponents range and in each range there are sets of strings. Each set contains two strings representig the two cards of the player. And since I am working with a range each player doesn't only play with one card combination but with several.

And now I need to access each of those sets containing the two strings to then perform the Montecarlo simulation. But it always throws an error.

So my goal is to retrieve a card combination (if my understanding is correct, then I just need the set containing the two strings and pass that retrieved set to the Hand constructer) from that range vector and pass it to the Hand constructor, which intern requires a std::setstd::string. So I just need a method to do that and mine did't work (I guess :) ).

Here is the code:

Scoring.cpp (the important function is "double montecarlo()")

//cppimport

/*
<%
setup_pybind11(cfg)
%>
*/
#include "Scoring.h"

#include <array>
#include <vector>
#include <iostream>
#include <tuple>
#include <algorithm>

#include <iostream>
#include <algorithm>
#include <random>
#include <iterator>

#include <pybind11/pybind11.h>

#include <pybind11/stl.h>
#include <pybind11/complex.h>,
#include <pybind11/functional.h>
#include <pybind11/chrono.h>

namespace py = pybind11;
using namespace pybind11::literals;


PYBIND11_MODULE(Scoring, m) {
    m.def("montecarlo", &montecarlo);
}


namespace
{
    constexpr auto x = std::array<int, 4>{2, 2, 2, 1};
}

bool eval_best_hand(const std::vector<std::set<std::string>>& all_cards_with_table_combined)
// returns true if first player has best hand
{
    std::vector<std::tuple< std::vector<int>, std::vector<int>, std::string>> all_players_score;
    std::vector<std::tuple< std::vector<int>, std::vector<int>, std::string>>  all_players_score_original;
    bool best_hand;

    for (const auto& cards_with_table : all_cards_with_table_combined)
    {
        auto result = calc_score(cards_with_table);
        all_players_score.emplace_back(result);
    }
    all_players_score_original = all_players_score;

    std::sort(all_players_score.begin(), all_players_score.end(), std::greater<>());
    if (all_players_score[0] == all_players_score_original[0])
        best_hand = true; // first player is best hand
    else
        best_hand = false; // different player is best hand

    return best_hand;
}

Score get_rcounts(const std::set<std::string>& all_cards_with_table_combined,
    std::vector<std::size_t> available_ranks,
    const std::string original_ranks) {
    Score rcounts;
    for (const auto& card : all_cards_with_table_combined) {
        available_ranks.emplace_back(original_ranks.find(card.substr(0, 1)));
    }
    for (int i = 0; i <= 12; i++) {
        int count = std::count(available_ranks.begin(), available_ranks.end(), i);
        if (count > 0) {
            rcounts.emplace_back(std::make_pair(count, i));
        }
    }
    return rcounts;
}

std::tuple< std::vector<int>, std::vector<int>, std::string> calc_score(const std::set<std::string>& all_cards_with_table_combined) {
    const std::string original_ranks = "23456789TJQKA";
    const std::vector<std::string> original_suits{ "C","D","H","S" };
    std::vector<std::size_t> available_ranks;
    std::vector<std::string> available_suits;
    std::vector<int> score;
    std::vector<int> card_ranks;
    std::vector<int> sorted_card_ranks;
    std::string hand_type;
    bool flush = false;
    bool straight = false;

    Score rcounts;
    std::vector<std::tuple<int, std::string>> rsuits;

    rcounts = get_rcounts(all_cards_with_table_combined, available_ranks, original_ranks);

    // sort tuple and split into score and card ranks
    std::sort(rcounts.begin(), rcounts.end(), std::greater<std::tuple<int, int>>());
    for (auto it = std::make_move_iterator(rcounts.begin()),
        end = std::make_move_iterator(rcounts.end()); it != end; ++it)
    {
        score.push_back(std::get<0>(*it));  // amount of occurrences
        card_ranks.push_back(std::get<1>(*it));  // ranks of individual cards
    }

    bool potential_threeofakind = score[0] == 3;
    bool potential_twopair = score == std::vector<int> {2, 2, 1, 1, 1};
    bool potential_pair = score == std::vector<int> {2, 1, 1, 1, 1, 1};

    auto sub_score2 = slice(score, 0, 2);
    auto sub_score4 = slice(score, 0, 5);
    auto sub_score0 = slice(score, 0, 0);
    // # fullhouse(three of a kind and pair, or two three of a kind)
    if (sub_score2 == std::vector<int> {3, 2} || sub_score2 == std::vector<int> {3, 3}) {
        // make adjustment
        card_ranks = slice(card_ranks, 0, 2);
        score = { 3,2 };
    }
    // edge case: convert three pair to two pair
    //const auto x = &score[3];
    else if (sub_score4 == std::vector<int>{2, 2, 2, 1}) {
        score = { 2,2,1 };
        int kicker = std::max(card_ranks[2], card_ranks[3]);
        card_ranks = { card_ranks[0], card_ranks[1], kicker };
    }
    else if (score[0] == 4) {  // four of a kind
        score = { 4, };
        // avoid for example 11, 8, 9
        sorted_card_ranks = card_ranks;
        std::sort(sorted_card_ranks.begin(), sorted_card_ranks.end(), std::greater <>());
        card_ranks = { sorted_card_ranks[0], sorted_card_ranks[1] };
    }
    else if (score.size() >= 5) {  // high card, flush, straight and straight flush
        // straight
        // adjust for 5 high straight
        if (std::find(card_ranks.begin(), card_ranks.end(), 12) != card_ranks.end())
            card_ranks.push_back(-1);
        sorted_card_ranks = card_ranks;
        std::sort(sorted_card_ranks.begin(), sorted_card_ranks.end(), std::greater <>());  // sort again

        for (int i = 0; i < sorted_card_ranks.size() - 4; ++i) {
            straight = sorted_card_ranks[i] - sorted_card_ranks[i + 4] == 4;
            if (straight == true) {
                card_ranks = {
                    sorted_card_ranks[i], sorted_card_ranks[i + 1], sorted_card_ranks[i + 2], sorted_card_ranks[i + 3],
                    sorted_card_ranks[i + 4] };
                break;
            }
        }

        //flush
        for (std::string card : all_cards_with_table_combined) {
            available_suits.emplace_back(card.substr(1, 1));
        }

        std::vector<int> suit_counts;
        std::vector<std::string> suit_cards;
        for (const auto& suit : original_suits) {  // why can original_suits not be a string and suit a char?
            int count = std::count(available_suits.begin(), available_suits.end(), suit);
            if (count > 0) {
                rsuits.emplace_back(std::make_pair(count, suit));
            }
        }
        std::sort(rsuits.begin(), rsuits.end(), std::greater<std::tuple<int, std::string>>());
        flush = std::get<0>(rsuits[0]) >= 5; // the most occurred suit appear at least 5 times

        if (flush == true)
        {
            auto flush_suit = std::get<1>(rsuits[0]);
            std::set<std::string> flush_hand;
            for (auto card : all_cards_with_table_combined) {
                if (card[1] == flush_suit[0]) {
                    flush_hand.insert(card);
                }
            }

            Score rcounts_flush = get_rcounts(flush_hand, available_ranks, original_ranks);
            // sort tuple and split into score and card ranks
            std::sort(rcounts_flush.begin(), rcounts_flush.end(), std::greater<std::tuple<int, int>>());
            card_ranks.clear();
            score.clear();
            for (auto it = std::make_move_iterator(rcounts_flush.begin()),
                end = std::make_move_iterator(rcounts_flush.end()); it != end; ++it)
            {
                score.push_back(std::get<0>(*it));  // ranks of individual cards
                card_ranks.push_back(std::get<1>(*it));  // amount of occurrences
            }

            //  # check for straight in flush
            // if 12 in card_ranks and -1 not in card_ranks : # adjust if 5 high straight
            if (std::find(card_ranks.begin(), card_ranks.end(), 12) != card_ranks.end() &&
                !(std::find(card_ranks.begin(), card_ranks.end(), -1) != card_ranks.end())) {
                card_ranks.push_back(-1);
            }

            for (int i = 0; i < card_ranks.size() - 4; i++) {
                straight = card_ranks[i] - card_ranks[i + 4] == 4;
                if (straight == true)
                {
                    break;
                }
            }
        }

        // no pair, straight, flush, or straight flush
        if (flush == false && straight == false)
            score = { 1 };
        else if (flush == false && straight == true)
            score = { 3, 1, 2 };
        else if (flush == true && straight == false)
            score = { 3, 1, 3 };
        else if (flush == true && straight == true)
            score = { 5 };
    }
    if (score[0] == 1 && potential_threeofakind == true)
        score = { 3,1 };
    else if (score[0] == 1 && potential_twopair == true)
        score = { 2, 2, 1 };
    else if (score[0] == 1 && potential_pair == true)
        score = { 2, 1, 1 };

    if (score[0] == 5)
        // # crdRanks=crdRanks[:5] # five card rule makes no difference {:5] would be incorrect
        hand_type = "StraightFlush";
    else if (score[0] == 4)
        hand_type = "FoufOfAKind"; // crdRanks = crdRanks[:2] # already implemented above
    else if (slice(score, 0, 2) == std::vector<int> {3, 2})
        hand_type = "FullHouse"; // # crdRanks = crdRanks[:2] # already implmeneted above
    else if (slice(score, 0, 3) == std::vector<int> {3, 1, 3}) {
        hand_type = "Flush";
        card_ranks = slice(card_ranks, 0, 5);
    }

    else if (slice(score, 0, 3) == std::vector<int> {3, 1, 2}) {
        hand_type = "Straight";
        card_ranks = slice(card_ranks, 0, 5);
    }

    else if (slice(score, 0, 2) == std::vector<int> {3, 1}) {
        hand_type = "ThreeOfAKind";
        card_ranks = slice(card_ranks, 0, 3);
    }
    else if (slice(score, 0, 2) == std::vector<int> {3, 1}) {
        hand_type = "ThreeOfAKind";
        card_ranks = slice(card_ranks, 0, 3);
    }
    else if (slice(score, 0, 2) == std::vector<int> {2, 2}) {
        hand_type = "TwoPair";
        card_ranks = slice(card_ranks, 0, 3);
    }
    else if (score[0] == 2) {
        hand_type = "Pair";
        card_ranks = slice(card_ranks, 0, 4);
    }
    else if (score[0] == 1) {
        hand_type = "HighCard";
        card_ranks = slice(card_ranks, 0, 5);
    }
    else
        throw std::runtime_error("Card Type error!");

    auto res = std::make_tuple(score, card_ranks, hand_type);
    return res;
}


double montecarlo(const std::set<std::string>& my_cards, const std::set<std::string>& cards_on_table, const std::vector<std::vector<std::set<std::string>>> ranges, const int number_of_players, const int iterations) { 

    int wins = 0;

    for (int i = 0; i < iterations; i++)
    {
        Deck deck;

        std::vector<int> idx_list;
        for (int i=0; i < ranges.size(); i++){
            idx_list.push_back(rand() % ranges[i].size());

        }      

        std::set<std::string> opponents_hole;
        for (int i=0; i < ranges.size(); i++){
            /*
            auto it = next(ranges[i].begin(), idx_list[i]);
            auto card_string = *it;
                opponents_hole.push_back(card_string);
            */

            std::string choosen_opponent_pair = ranges[i][idx_list[i]];
            opponents_hole.insert(std::string(1, choosen_opponent_pair));
        }
        
                deck.remove_visible_cards(my_cards, cards_on_table);
                deck.distribute_cards(number_of_players, opponents_hole);
            std::vector<std::set<std::string>> cards_with_table_combined = deck.get_cards_combined();
        bool first_player_has_best_hand = eval_best_hand(cards_with_table_combined);
        if (first_player_has_best_hand == true)
            wins += 1;
    }
    double equity = (wins / (double)iterations) * 100.0;
    std::cout << "Equity: " << equity << "%" << std::endl;
    return equity;
}



Deck::Deck() {
    std::string combined;
    //std::cout << "Constructing deck..." << std::endl;
    for (char& r : ranks) {
        for (char& s : suits) {
            combined = std::string() + r + s;
            full_deck.insert(combined);
        };
    };
    //std::cout << "Cards in deck: " << full_deck.size() << std::endl;
}

void Deck::remove_visible_cards(const Hand& my_cards_, const std::set<std::string>& cards_on_table_) {
    // remove my_cards and cards_on_table from full_deck
    set_difference(full_deck.begin(), full_deck.end(), my_cards_.begin(), my_cards_.end(),
        std::inserter(remaining_cards_tmp, remaining_cards_tmp.end()));

    // remove visible table cards from deck
    set_difference(remaining_cards_tmp.begin(), remaining_cards_tmp.end(), cards_on_table_.begin(),
        cards_on_table_.end(),
        std::inserter(remaining_cards, remaining_cards.end()));

    //std::cout << "Remaining cards: " << remaining_cards.size() << std::endl;

    this->my_cards = my_cards_.cards;
    this->cards_on_table = cards_on_table_;

    //std::cout << "Removed my cards from deck...\n";
}

void Deck::distribute_cards(int number_players, const std::set<std::string>& opponents_hole) {  //number_players INLCUDING OURSELF
    constexpr size_t cards_in_hand = 2;

    std::vector<std::string> shuffled_deck(remaining_cards.begin(), remaining_cards.end());
    std::shuffle(shuffled_deck.begin(), shuffled_deck.end(), std::mt19937_64(std::random_device()()));
    std::vector<Hand> player_hands(number_players);  // empty container

    auto hand_it = player_hands.begin();
    *hand_it = Hand(my_cards);  // set my own cards
    hand_it++;

    auto card_it = shuffled_deck.begin();
    int i = 0;
    while (hand_it != player_hands.end() && i<opponents_hole.size()) {
        //*hand_it = Hand(std::set<std::string>{*++card_it, * ++card_it});
        //auto it = next(opponents_hole.begin(), i);
        std::set<std::string>::iterator it = opponents_hole.begin();
        std::advance(it, i);
        *hand_it = Hand(*it);
        ++hand_it;
        i++;
    }

    while (cards_on_table.size() < 5) {
        cards_on_table.emplace(*++card_it);
    }

    // print out the hands
    //for (auto const& player_hand : player_hands) {
    //  std::cout << "Cards: ";
    //  for (const auto& card : player_hand.cards)
    //      std::cout << card << " ";
    //  std::cout << std::endl;
    //}
    this->player_hands = player_hands;

    //std::cout << "Cards on table: ";
    //print_set(cards_on_table);
}

std::vector<std::set<std::string>> Deck::get_cards_combined() {
    std::set<std::string> cards_with_table_combined;
    std::vector<std::set<std::string>> all_cards_with_table_combined;

    for (const auto& player_hand : player_hands) {
        cards_with_table_combined = player_hand.cards;
        cards_with_table_combined.insert(cards_on_table.begin(), cards_on_table.end());
        all_cards_with_table_combined.push_back(cards_with_table_combined);
    }

    return all_cards_with_table_combined;
}

void Deck::print_set(const std::set<std::string>& set) {
    for (const auto& card : set)
        std::cout << card << " ";
    std::cout << std::endl;
}

Scoring.h

//cppimport
#pragma once

#include <set>
#include <string>
#include <vector>


//using CardsWithTableCombined = std::set<std::string>;
using Score = std::vector<std::tuple<int, int>>;

bool eval_best_hand(const std::vector<std::set<std::string>>&);
std::tuple< std::vector<int>, std::vector<int>, std::string> calc_score(const std::set<std::string>&);

__declspec(dllexport) double montecarlo(const std::set<std::string>&, const std::set<std::string>&, const int, const int);


template<typename T>
std::vector<T> slice(std::vector<T> const& v, int m, int n)
{
    if (m > v.size())
        m = v.size();
    if (n > v.size())
        n = v.size();
    auto first = v.cbegin() + m;
    auto last = v.cbegin() + n;  // ending index needs to be n=x+1

    std::vector<T> vec(first, last);
    return vec;
}




//typedef std::set<std::string> CardsWithTableCombined;

struct Hand {
    Hand() = default;

    Hand(std::set<std::string> cards) : cards(std::move(cards)) {}

    std::set<std::string> cards;

    void insert(const std::string& card) {
        cards.insert(card);
    }

    void insert_hand(const Hand& hand) {
        for (const std::string& card : hand.cards)
            cards.insert(card);
    }

    [[nodiscard]] auto begin() const { return cards.begin(); }

    [[nodiscard]] auto end() const { return cards.end(); }
};

class Deck {
private:
    std::string ranks = "23456789TJQKA";
    std::string suits = "CDHS";
    Hand my_cards;
    std::set<std::string> remaining_cards_tmp;
    std::set<std::string> remaining_cards;
    std::set<std::string> cards_on_table;
    std::set<std::string> opponents_hole;
    void print_set(const std::set<std::string>& set);
public:
    std::set<std::string> full_deck;

    std::vector<Hand> player_hands;

    Deck();

    void distribute_cards(int number_of_players, const std::set<std::string>& opponents_hole);

    void remove_visible_cards(const Hand& my_cards_, const std::set<std::string>& cards_on_table);
    
    //std::vector<CardsWithTableCombined> get_cards_combined();
    std::vector<std::set<std::string>> get_cards_combined();
};

I have tried a few things, but I left them commented since they didn't work.

I tried just replicating this piece of code in an online C++ shell and it threw the following error:

error: conversion from '__gnu_cxx::__alloc_traits<std::allocator<std::set<std::__cxx11::basic_string<char> > >, std::set<std::__cxx11::basic_string<char> > >::value_type' {aka 'std::set<std::__cxx11::basic_string<char> >'} to non-scalar type 'std::string' {aka 'std::__cxx11::basic_string<char>'} requested
   18 |             std::string k = ranges[i][idx_list[i]];

Here is the code for that try:

// Online C++ compiler to run C++ program online
#include <iostream>
#include <set>
#include <vector>
#include <random>

int main() {
    // Write C++ code here
    std::vector<std::vector<std::set<std::string>>> ranges{{{"1", "2"}, {"1", "2", "3"}}};
    
    std::vector<int> idx_list;
    for (int i=0; i < ranges.size(); i++){
        idx_list.push_back(rand() % ranges[i].size());
    }
    
    std::set<std::string> opponents_hole;
    for (int i=0; i < ranges.size(); i++){
        std::string k = ranges[i][idx_list[i]];
        //std::string choosen_opponent_pair = ranges[i][idx_list[i]];
        //opponents_hole.insert(std::string(1, choosen_opponent_pair));
    }
    

    return 0;
}

Solution

  • The example code was throwing an error because of a failed cast. In your loop, the std:set is being assigned to std::string. Sets must be walked with an iterator. As an example, I changed your loop to use an iterator for the set:

    // Online C++ compiler to run C++ program online
    #include <iostream>
    #include <set>
    #include <vector>
    #include <random>
    
    int main() {
        // Write C++ code here
        std::vector<std::vector<std::set<std::string>>>
            ranges{{{"1", "2"}, {"1", "2", "3"}}};
    
        std::vector<int> idx_list;
        for (int i=0; i < ranges.size(); i++){
            idx_list.push_back(rand() % ranges[i].size());
        }
    
        std::set<std::string> opponents_hole;
        for (auto it = ranges[0][0].begin(); it != ranges[0][0].end(); ++it){
            std::string k = *it;
            //std::string choosen_opponent_pair = ranges[i][idx_list[i]];
            //opponents_hole.insert(std::string(1, choosen_opponent_pair));
        }
    
        return 0;
    }