Hand patterns in a card game

20

3

A deck of cards is the Cartesian product of S suits and R ranks. Many, though not all, card games use S=4 and R∊{6,8,13}. A hand of H cards is dealt from the deck. Its distribution, a.k.a. "hand pattern", is an array that describes how many cards you got from each suit, ignoring suit order (so, it's like a multi-set). Given a distribution D satisfying len(D)=S, 1≤sum(D)=H≤S×R, 0≤D[i]≤R, D[i]≥D[i+1], find the probability of it occurring.

Input: an integer R and an array D.

Output: the probability with at least 5 digits after the decimal mark; trailing zeroes may be skipped; scientific notation is ok.

Loopholes forbidden. Shortest wins.

Tests:

R    D               probability
13   4 4 3 2     ->  0.2155117564516334148528314355068773
13   5 3 3 2     ->  0.1551684646451760586940386335649517
13   9 3 1 0     ->  0.0001004716813294328274372174524508
13   13 0 0 0    ->  0.0000000000062990780897964308603403
8    3 2 2 1     ->  0.4007096203759162602321667950144035
8    4 2 1 1     ->  0.1431105787056843786543452839337155
8    2 2 1 0     ->  0.3737486095661846496106785317018910
8    3 1 1 0     ->  0.2135706340378197997775305895439377
15   4 4 3 2 1   ->  0.1428926269185580521441708109954798
10   3 0 0       ->  0.0886699507389162561576354679802956
10   2 1 0       ->  0.6650246305418719211822660098522167
10   1 1 1       ->  0.2463054187192118226600985221674877

See also Bridge hand patterns in Wikipedia.

EDIT: dropped unnecessary restriction H≤R

EDIT: added constraint H≥1

ngn

Posted 2017-11-19T23:55:50.680

Reputation: 11 449

Can we assume that D is sorted? – orlp – 2017-11-20T00:18:44.053

1@orip yes, that's what I meant by D[i]≥D[i+1] – ngn – 2017-11-20T00:26:16.843

Cards I know start from 1 not from 0... – RosLuP – 2017-11-22T15:10:32.197

@RosLuP what do you mean? – ngn – 2017-11-22T15:30:33.997

I'm sure i didn't understand something... If Cards are represented from number 1,2, ...,13 all * 4; so what does it mean "13 0 0 0" in example? 0 means card 0? – RosLuP – 2017-11-22T16:11:02.287

@RosLuP 13 0 0 0 means you have 13 cards of one suit and none of the others. Nowhere in the challenge do I mention card indices, though if I did, of course I would choose 0...51 so that ranks/suits can be determined easily through modulo and bitshift ops. – ngn – 2017-11-22T16:34:28.180

Can we assume S>1 ie there are at least two suits in the deck? – JayCe – 2018-06-14T13:42:11.830

Answers

9

APL (Dyalog Unicode), 30 chars

×/!⍨,z,1÷((z←!∘≢⊢)⌸⊢),×∘≢!⍨1⊥⊢

Try it online!

Using @orlp’s formula.

FrownyFrog

Posted 2017-11-19T23:55:50.680

Reputation: 3 112

Excellent, well done! The "+100" button says I must wait 10 more hours before I can award the bounty. After that I'll set up another one for +200. – ngn – 2018-06-14T21:34:58.920

Yay I win! thanks @jayprich – FrownyFrog – 2018-06-14T21:41:09.427

@FrownyFrog How do you like Dyalog APL compared with J? – Jonah – 2018-06-15T04:12:49.407

8

Python 3, 134 bytes

b=lambda n,k:k<1or n*b(n-1,k-1)/k
f=lambda R,D,i=1,s=1,t=0:D and b(R,D[0])*i/s*f(R,D[1:],i+1,(D[0]in D[1:])*s+1,t+D[0])or 1/b(~-i*R,t)

Formula is the product of binom(R, d) for each element d in D, times factorial(len(D)), divided by the product of factorial(len(S)) for each S in the groupings of D (e.g. [4, 4, 3, 2] has groupings [[4, 4], [3], [2]]), finally divided by binom(len(D) * R, sum(D)).

Or in math notation, assuming m contains the multiplicities of the n unique elements in D:

$$ \frac{|D|!}{m_1!\cdot m_2!\cdots m_n!} \binom{|D|\cdot R}{\sum D}^{-1} \prod_{d\in D}\binom{R}{d} $$

orlp

Posted 2017-11-19T23:55:50.680

Reputation: 37 067

2for a brief moment you made me believe PPCG now supports LaTeX :) – ngn – 2017-11-20T01:18:22.607

Inlining the two functions I got 136 but maybe that can be golfed more (uses i=0 to mean b() and uses R,D for n,k).

– Jonathan Allan – 2017-11-20T06:18:46.130

7

R, 90 85 83 bytes

function(R,D,l=sum(D|1),K=choose)prod(K(R,D),1:l,1/gamma(1+table(D)))/K(R*l,sum(D))

Try it online!

I observed the same thing as orlp, but I picked a nice language that has combinatorics builtins.

Explanation:

function(R,D,             # next are optional arguments
 l=sum(D|1),              # alias for length of D, aka S
 K=choose)                # alias for choose
  prod(                   # take the product of:
    K(R,D),               # "choose" is vectorized over R and D
    1:l,                  # S!
    1/gamma(1+            # gamma(n+1) = n! for integer n
     table(D))            # multiplicities of unique elements of D
  ) /                     # divide by
  K(R*l, sum(D))          # R*S choose H
                          # return last computation (which is all the computation)

Giuseppe

Posted 2017-11-19T23:55:50.680

Reputation: 21 077

You can save a few more with this: "<"=choose (outside of function) and potentially use seq depending on ngn's answer to the comment I posted this morning.

– JayCe – 2018-06-14T20:57:10.193

6

Jelly,  22  20 bytes

-2 bytes using a new quick, ʋ, and a new monadic atom

ĠẈ!;L×c⁸S¤ʋ
L!;c@֍P

A dyadic link, taking the dealt-distribution, D, on the left and the number-of-ranks, R, on the right, which returns the probability of occurrence.

Try it online! or see the test-suite

How?

ĠẈ!;L×c⁸S¤ʋ - Link 1, denomParts: list, distribution (D); number, ranks (R)
                                                                 e.g. [3,3,3,2,2]; 8
Ġ           - group indices of D by their values                      [[4,5],[1,2,3]]
 Ẉ          - length of each group                                    [2,3]
  !         - factorial (vectorises)                                  [2,6]
          ʋ - last four links as a dyad
            - ... i.e. totalWaysToDeal = f(list, distribution (D); number, ranks (R)):
    L       - length of D                                             5
     ×      - multiply by R = total number of cards                   40
         ¤  - nilad followed by link(s) as a nilad:
       ⁸    -   chain's left argument, D                              [3,3,3,2,2]
        S   -   sum = total cards dealt                               13
      c     - binomial                                        40C13 = 12033222880
   ;        - concatenate                                             [2,6,12033222880]                                                  

L!;c@֍P - Main link: list, distribution (D); number, ranks (R)
         -                                                  e.g. [3,3,3,2,2]; 8
L        - length of D = number of suits                         5
 !       - factorial                                             120
   c@    - R binomial (vectorised across) D     (8C3=56;8C2=28)  [56,56,56,28,28]
  ;      - concatenate                                           [120,56,56,56,28,28]
      ç  - call the last link (1) as a dyad = denomParts(D,R)    [2,6,12033222880]
     ÷   - divide (vectorises)                                   [120/2,56/6,56/12033222880,56,28,28]
       P - product                                               0.11441900924883391

Jonathan Allan

Posted 2017-11-19T23:55:50.680

Reputation: 67 804

5

05AB1E, 21 bytes

cP¹g!*¹γ€g!P¹gI*¹Oc*/

Try it online!

Explanation

 P                      # product of
c                       # bin(input1,input2)
     *                  # multiplied by
    !                   # fac of
  ¹g                    # length of input1
                    /   # divided by
           P            # product of
          !             # fac of each
        €g              # length of each
      ¹γ                # chunk of consecutive equal elements of input1
                   *    # multiplied by
                  c     # bin of
            ¹g          # length of input1
              I*        # times input2
                ¹O      # and sum of input1

Emigna

Posted 2017-11-19T23:55:50.680

Reputation: 50 798

3

Pyth, 32 bytes

cc*.!lQ*F.cLvzQ*F.!hMr8Q.c*vzlQs

Try it here! or Verify all the test cases!

How this works?

cc*.!lQ*F.cLvzQ*F.!hMr8Q.c*vzlQs ~ Full program. D = list, R = number.

   .!                            ~ The factorial of...
     lQ                            ~ The length of D.
  *                              ~ Multiplied by...
       *F                          ~ The product of the elements of...
         .c                          ~ The nCr between...
           L  Q                        ~ Each element of D, and...
            vz                         ~ R.
 c                               ~ Divided by...
               *F                  ~ The product of the elements of...
                 .!                  ~ The factorial of each...
                   hM                  ~ Heads. Count of adjacent elements in...
                     r8Q                 ~ The run length encoding of D.
c                                ~ Divided by...
                        .c         ~ The nCr between...
                          *          ~ The product of...
                           vz          ~ R, and...
                             lQ        ~ The length of D.
                               s   ~ And the sum of D.
                                 ~ Output implicitly.

Mr. Xcoder

Posted 2017-11-19T23:55:50.680

Reputation: 39 774

3

APL (Dyalog), 42 bytes

{×/(!≢⍵),(⍵!⍺),÷((+/⍵)!⍺×≢⍵),!≢¨⍵⊂⍨1,2≠/⍵}

Try it online!

Still golfing.

Uriel

Posted 2017-11-19T23:55:50.680

Reputation: 11 708

challenge: 30 bytes – ngn – 2017-11-23T10:01:13.120

@ngn challenge accepted – Uriel – 2017-11-23T13:31:15.990

Sorry, it's actually 30 chars. With the risk of giving away information: one of my chars is not in the Classic charset, I didn't realise that at first. – ngn – 2017-11-23T21:35:23.200

@ngn Can't you just use Adám's character set to make it 30 bytes?

– Probie – 2018-06-14T10:40:40.470

@Probie yep, that's what I meant by "SBCS" in the bounty description – ngn – 2018-06-14T11:37:47.270

≢¨⍵⊂⍨1,2≠/⍵ can become ⊢∘≢⌸⍵ – user41805 – 2018-06-14T17:25:23.333

2

Clojure, 153 bytes

#(apply +(for[_(range 1e06):when(=(remove #{0}%)(reverse(sort(vals(frequencies(take(apply + %)(shuffle(for[i(range %2)j(range(count %))]j))))))))]1e-06))

Just a brute-force simulation, to get more precision increase the iteration count and the "1 / N" value at the end accordingly. First argument is the counts and 2nd argument is the number of cards in the deck per suite.

NikoNyrh

Posted 2017-11-19T23:55:50.680

Reputation: 2 361

2

J, 57 bytes

](#@]%~[:+/[-:"1[:\:~@(#/.~)"1+/@[{."1])i.@!@(*+/)A.(##\)

Try it online!

This runs in O(golf) and will choke on many of the test cases (though works theoretically), which would be fine if it were golfier. But I'm stuck on trimming it down, especially with avoiding those repeated "1. If anyone wants to help, here's the parsed version...

The right side of the main fork is all possible deals of the deck, and the left side of the main fork is just the original right arg, ie, the suit mask we're matching against.

Inside, from each "shuffled" deck, we take the first hand elements, then group them using key /. and sort the result, and check if that matches the suit mask in question. We add add up the total number that do match, and divide that into the length of all possible decks.

┌─┬─────────────────────────────────────────────────────────────────────────────────────────────────┬─────────────────────────────────────┐
│]│┌───────┬─────┬─────────────────────────────────────────────────────────────────────────────────┐│┌──────────────────────┬──┬─────────┐│
│ ││┌─┬─┬─┐│┌─┬─┐│┌──┬─────┬──────────────────────────────────────────────────────────────────────┐│││┌────────┬─┬─────────┐│A.│┌─┬─────┐││
│ │││#│@│]│││%│~│││[:│┌─┬─┐│┌─┬────────┬─────────────────────────────────────────────────────────┐│││││┌──┬─┬─┐│@│┌─┬─────┐││  ││#│┌─┬─┐│││
│ ││└─┴─┴─┘│└─┴─┘││  ││+│/│││[│┌──┬─┬─┐│┌──┬───────────────────────────┬────────────────────────┐│││││││i.│@│!││ ││*│┌─┬─┐│││  ││ ││#│\││││
│ ││       │     ││  │└─┴─┘││ ││-:│"│1│││[:│┌─────────────────────┬─┬─┐│┌───────────┬────────┬─┐│││││││└──┴─┴─┘│ ││ ││+│/││││  ││ │└─┴─┘│││
│ ││       │     ││  │     ││ │└──┴─┴─┘││  ││┌──────┬─┬──────────┐│"│1│││┌─────┬─┬─┐│┌──┬─┬─┐│]││││││││        │ ││ │└─┴─┘│││  │└─┴─────┘││
│ ││       │     ││  │     ││ │        ││  │││┌──┬─┐│@│┌──────┬─┐││ │ ││││┌─┬─┐│@│[│││{.│"│1││ ││││││││        │ │└─┴─────┘││  │         ││
│ ││       │     ││  │     ││ │        ││  ││││\:│~││ ││┌─┬──┐│~│││ │ │││││+│/││ │ ││└──┴─┴─┘│ │││││││└────────┴─┴─────────┘│  │         ││
│ ││       │     ││  │     ││ │        ││  │││└──┴─┘│ │││#│/.││ │││ │ ││││└─┴─┘│ │ ││        │ ││││││└──────────────────────┴──┴─────────┘│
│ ││       │     ││  │     ││ │        ││  │││      │ ││└─┴──┘│ │││ │ │││└─────┴─┴─┘│        │ ││││││                                     │
│ ││       │     ││  │     ││ │        ││  │││      │ │└──────┴─┘││ │ ││└───────────┴────────┴─┘│││││                                     │
│ ││       │     ││  │     ││ │        ││  ││└──────┴─┴──────────┘│ │ ││                        │││││                                     │
│ ││       │     ││  │     ││ │        ││  │└─────────────────────┴─┴─┘│                        │││││                                     │
│ ││       │     ││  │     ││ │        │└──┴───────────────────────────┴────────────────────────┘││││                                     │
│ ││       │     ││  │     │└─┴────────┴─────────────────────────────────────────────────────────┘│││                                     │
│ ││       │     │└──┴─────┴──────────────────────────────────────────────────────────────────────┘││                                     │
│ │└───────┴─────┴─────────────────────────────────────────────────────────────────────────────────┘│                                     │
└─┴─────────────────────────────────────────────────────────────────────────────────────────────────┴─────────────────────────────────────┘

Jonah

Posted 2017-11-19T23:55:50.680

Reputation: 8 729

1Orlp's formula scored 42 for APL, maybe that would score less than 58 on J? – Uriel – 2017-11-20T21:05:08.743

1

I get 45 so far that way f=:(([:!#)%[:*/[:!#/.~)@]**/@(]![)%+/@]![*#@] TIO

– jayprich – 2018-06-14T18:27:04.580