Making card configurations in Sagemath

45 Views Asked by At

I'm following SageMath's tutorial on Combinatorics and one of the exercizes is to calculate the set of Four of a Kind hands (in card games a hand containing four cards of the same value is called a four of a kind and a Hand is a subset of 5 cards). Each card has a Value and a Suit.

I want to generate the hand in a structured way like {(1,"Hearts"), (1,"Spades"), (1,"Clubs"), (1,"Hearts"), (5,"Clubs") )}, so my idea was to create all the cards first and then join them in a set, like so:

Suits = Set(["Hearts", "Diamonds", "Spades", "Clubs"])
Values = Set([2, 3, 4, 5, 6, 7, 8, 9, 10,
              "Jack", "Queen", "King", "Ace"])
Cards = cartesian_product([Values, Suits])

pick = Values.random_element()
card1 = Subsets(cartesian_product([Set([pick]), Suits]),1)
card2 = Subsets(cartesian_product([Set([pick]), Suits]),1)
card3 = Subsets(cartesian_product([Set([pick]), Suits]),1)
card4 = Subsets(cartesian_product([Set([pick]), Suits]),1)
card5 = Subsets(Cards,1)

FourOfaKind = cartesian_product([card1,card2,card3,card4,card5])

FourOfaKind.cardinality()

FourOfaKind.random_element()

However, I realised I'm picking Suits with reposition, which is not what I want!

How can i tell Sage Math that I don't want to pick the choice of the previous card? If i instantiate a Suit with Suit.random_element() each time I fix a card, then that won't be counted as part of the construction of the Suit/{whatever suit is instantiated} for the following cards, which puts me in quite a pickle! I'm sure there's some method to do this but I've been at hours for this and the Sage documentation is huge!

Any help would be really appreciated!

1

There are 1 best solutions below

0
On

The following code generates all relevant four of a kind hands:

suits = ["\u2663", "\u2665", "\u2666", "\u2660"]
values = '2,3,4,5,6,7,8,9,10,J,Q,K,A'.split(',')

cards = [v + s for v in values for s in suits]
fours = [set(v + s for s in suits) for v in values]
 
four_of_a_kind_hands = [f.union((c,)) for c in cards for f in fours if c not in f]

We expect in the list four_of_a_kind_hands exactly $13\cdot(52-4)$ elements, the counting being as follows. There are $13$ possibilities to fix the value v, such that all four cards 'v♠', 'v♣', 'v♥', 'v♦' are in the hand. Fix such a v. Then for the fifth card we have (depending on v) the same number of chances, namely $(52-4)$ (the deck of cards with the v-cards removed).

And indeed:

sage: len(four_of_a_kind_hands)
624
sage: 13*(52 - 4)
624

This was the tiny mathematical part from the answer and from the essence of the problem. Well, from the programatical point of view, such an argument usually does not count. This should be changed in the approach of both groups that tend to exclude each other, since structural applications of mathematics in computer search and experiment become more and more important, and conversely, the programmer can always profit from the way the mathematician is organizing combinatorial data.


To get a random element...

import random
print(random.choice(four_of_a_kind_hands))

And this time i've got:

sage: 
....: import random
....: print(random.choice(four_of_a_kind_hands))
....: 
{'4♥', '4♠', '6♠', '4♦', '4♣'}
sage: 

A "place wasting" solution is also possible. It is not optimal, but it respects the "spirit of the game". We can "collect" (using a generator, instead of a list) the range of all hands, then pick from this "collection" those hands that match a given pattern, in our case the pattern being a hand of the shape $XXXXY$ with suits ignored.

We construct the cards (deck) as above, then extract sets of five. For each such "hand" we extract only the hands with the wanted property. (So we get a list, this may be expensive, if the list is too "big" for the poor laptop.)

C = Combinations(cards, 5)
def myproperty(hand):
    vals = [h[0] for h in hand]
    v, w = vals[0], vals[1]
    return 4 in (vals.count(v), vals.count(w))

XXXXY_list = list(filter(myproperty, C))

(We check that a value - either v or w appears at least four times in the hand, where v, w are two rather random values from the hand) And we get in short time the list, its cardinality being:

sage: len(XXXXY_list)
624

It is surprising how quick the list comes to us, given that C has

sage: len(C)
2598960
sage: binomial(52, 5)
2598960

elements.