Arbitrary Dice with Probability

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP

3

$begingroup$

I wrote a script that gives a “generalized” function of an arbitrary dice. Is this any good? How can it improve so to make it cleaner?

import random
def roll(die):
    number = random.randint(0,len(die)-1)
    b = die[number]
    return b

Die1 = [1,2,3,4]
Die2 = [1,2,3,4,5,6] #num lists

def inptchacc():
    ending_conditions = ['stop','Stop','quit','Quit']
    end = False
    inpu = input('what number would you like to add to the new face of the Die? (to end the die please type "stop or Stop or quit or Quit to finish the DIE" )')
    while end == False:
        if not(inpu in ending_conditions):
            try:
                retr = int(inpu)
                return retr
            except:
                string = 'invalid input please try again'
                inpu = input('invalid input please try again ')
        else:
            stop = 'stop'
            return stop

def deeper(IDie):
    list = 
    Adding = True
    while Adding:
        print('The Die ' + IDie + ' is currently ' + str(list) )
        toadd = inptchacc()
        if toadd != 'stop':
            list.append(toadd)
        else:
            Adding = False
    return list


def chance_overlap(Die1,Die2):
    highnumber = 1000
    counter = 0
    for n in range(highnumber):
        x = roll(Die1)
        y = roll(Die2)
        if x == y:
            counter += 1
    chance = counter/highnumber
    return chance

chance = chance_overlap(Die1,Die2)

print(chance)
Doing = True
while Doing:
    try:
        IDie1 = deeper('IDie1')
        IDie2 = deeper('IDie1')
        chance2 = chance_overlap(IDie1,IDie2)
        Doing = False
    except:
        print ('incompatible options selected returning....... ')

print(chance2) 

share|improve this question

$endgroup$

  • $begingroup$
    What is the overall function of this, just rolling some arbitrary dice? It looks awfully complicated for such a simple task if that’s it.
    $endgroup$
    – Mast
    Jan 7 at 12:15

  • $begingroup$
    ” The assignment deals with a game in which two players both have a set of dice. In the game, each player casts his/her dice and sums the outcomes of the dice. The player with the highest sum wins. If the sums are equal, it’s a draw. Usually the dice within one set are equal, but different from the kind of dice in the other set.” This is the assignment
    $endgroup$
    – Beginner-Coder123
    Jan 7 at 12:22

  • 1

    $begingroup$
    As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review – Asking Questions for guidance on writing good question titles.
    $endgroup$
    – BCdotWEB
    Jan 7 at 12:24

  • 3

    $begingroup$
    @Beginner-Coder123: You should add that description to the question body.
    $endgroup$
    – Graipher
    Jan 7 at 13:28

  • 2

    $begingroup$
    Please do not vandalize your posts. By posting on the Stack Exchange network, you’ve granted a non-revocable right for SE to distribute that content (under the CC BY-SA 3.0 license). By SE policy, any vandalism will be reverted.
    $endgroup$
    – Glorfindel
    Jan 8 at 19:07

3

$begingroup$

I wrote a script that gives a “generalized” function of an arbitrary dice. Is this any good? How can it improve so to make it cleaner?

import random
def roll(die):
    number = random.randint(0,len(die)-1)
    b = die[number]
    return b

Die1 = [1,2,3,4]
Die2 = [1,2,3,4,5,6] #num lists

def inptchacc():
    ending_conditions = ['stop','Stop','quit','Quit']
    end = False
    inpu = input('what number would you like to add to the new face of the Die? (to end the die please type "stop or Stop or quit or Quit to finish the DIE" )')
    while end == False:
        if not(inpu in ending_conditions):
            try:
                retr = int(inpu)
                return retr
            except:
                string = 'invalid input please try again'
                inpu = input('invalid input please try again ')
        else:
            stop = 'stop'
            return stop

def deeper(IDie):
    list = 
    Adding = True
    while Adding:
        print('The Die ' + IDie + ' is currently ' + str(list) )
        toadd = inptchacc()
        if toadd != 'stop':
            list.append(toadd)
        else:
            Adding = False
    return list


def chance_overlap(Die1,Die2):
    highnumber = 1000
    counter = 0
    for n in range(highnumber):
        x = roll(Die1)
        y = roll(Die2)
        if x == y:
            counter += 1
    chance = counter/highnumber
    return chance

chance = chance_overlap(Die1,Die2)

print(chance)
Doing = True
while Doing:
    try:
        IDie1 = deeper('IDie1')
        IDie2 = deeper('IDie1')
        chance2 = chance_overlap(IDie1,IDie2)
        Doing = False
    except:
        print ('incompatible options selected returning....... ')

print(chance2) 

share|improve this question

$endgroup$

  • $begingroup$
    What is the overall function of this, just rolling some arbitrary dice? It looks awfully complicated for such a simple task if that’s it.
    $endgroup$
    – Mast
    Jan 7 at 12:15

  • $begingroup$
    ” The assignment deals with a game in which two players both have a set of dice. In the game, each player casts his/her dice and sums the outcomes of the dice. The player with the highest sum wins. If the sums are equal, it’s a draw. Usually the dice within one set are equal, but different from the kind of dice in the other set.” This is the assignment
    $endgroup$
    – Beginner-Coder123
    Jan 7 at 12:22

  • 1

    $begingroup$
    As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review – Asking Questions for guidance on writing good question titles.
    $endgroup$
    – BCdotWEB
    Jan 7 at 12:24

  • 3

    $begingroup$
    @Beginner-Coder123: You should add that description to the question body.
    $endgroup$
    – Graipher
    Jan 7 at 13:28

  • 2

    $begingroup$
    Please do not vandalize your posts. By posting on the Stack Exchange network, you’ve granted a non-revocable right for SE to distribute that content (under the CC BY-SA 3.0 license). By SE policy, any vandalism will be reverted.
    $endgroup$
    – Glorfindel
    Jan 8 at 19:07

3

3

3

$begingroup$

I wrote a script that gives a “generalized” function of an arbitrary dice. Is this any good? How can it improve so to make it cleaner?

import random
def roll(die):
    number = random.randint(0,len(die)-1)
    b = die[number]
    return b

Die1 = [1,2,3,4]
Die2 = [1,2,3,4,5,6] #num lists

def inptchacc():
    ending_conditions = ['stop','Stop','quit','Quit']
    end = False
    inpu = input('what number would you like to add to the new face of the Die? (to end the die please type "stop or Stop or quit or Quit to finish the DIE" )')
    while end == False:
        if not(inpu in ending_conditions):
            try:
                retr = int(inpu)
                return retr
            except:
                string = 'invalid input please try again'
                inpu = input('invalid input please try again ')
        else:
            stop = 'stop'
            return stop

def deeper(IDie):
    list = 
    Adding = True
    while Adding:
        print('The Die ' + IDie + ' is currently ' + str(list) )
        toadd = inptchacc()
        if toadd != 'stop':
            list.append(toadd)
        else:
            Adding = False
    return list


def chance_overlap(Die1,Die2):
    highnumber = 1000
    counter = 0
    for n in range(highnumber):
        x = roll(Die1)
        y = roll(Die2)
        if x == y:
            counter += 1
    chance = counter/highnumber
    return chance

chance = chance_overlap(Die1,Die2)

print(chance)
Doing = True
while Doing:
    try:
        IDie1 = deeper('IDie1')
        IDie2 = deeper('IDie1')
        chance2 = chance_overlap(IDie1,IDie2)
        Doing = False
    except:
        print ('incompatible options selected returning....... ')

print(chance2) 

share|improve this question

$endgroup$

I wrote a script that gives a “generalized” function of an arbitrary dice. Is this any good? How can it improve so to make it cleaner?

import random
def roll(die):
    number = random.randint(0,len(die)-1)
    b = die[number]
    return b

Die1 = [1,2,3,4]
Die2 = [1,2,3,4,5,6] #num lists

def inptchacc():
    ending_conditions = ['stop','Stop','quit','Quit']
    end = False
    inpu = input('what number would you like to add to the new face of the Die? (to end the die please type "stop or Stop or quit or Quit to finish the DIE" )')
    while end == False:
        if not(inpu in ending_conditions):
            try:
                retr = int(inpu)
                return retr
            except:
                string = 'invalid input please try again'
                inpu = input('invalid input please try again ')
        else:
            stop = 'stop'
            return stop

def deeper(IDie):
    list = 
    Adding = True
    while Adding:
        print('The Die ' + IDie + ' is currently ' + str(list) )
        toadd = inptchacc()
        if toadd != 'stop':
            list.append(toadd)
        else:
            Adding = False
    return list


def chance_overlap(Die1,Die2):
    highnumber = 1000
    counter = 0
    for n in range(highnumber):
        x = roll(Die1)
        y = roll(Die2)
        if x == y:
            counter += 1
    chance = counter/highnumber
    return chance

chance = chance_overlap(Die1,Die2)

print(chance)
Doing = True
while Doing:
    try:
        IDie1 = deeper('IDie1')
        IDie2 = deeper('IDie1')
        chance2 = chance_overlap(IDie1,IDie2)
        Doing = False
    except:
        print ('incompatible options selected returning....... ')

print(chance2) 

python python-3.x dice

share|improve this question

share|improve this question

share|improve this question

share|improve this question

edited Jan 9 at 19:17

200_success

129k15152415

129k15152415

asked Jan 7 at 12:11

Beginner-Coder123Beginner-Coder123

293

293

  • $begingroup$
    What is the overall function of this, just rolling some arbitrary dice? It looks awfully complicated for such a simple task if that’s it.
    $endgroup$
    – Mast
    Jan 7 at 12:15

  • $begingroup$
    ” The assignment deals with a game in which two players both have a set of dice. In the game, each player casts his/her dice and sums the outcomes of the dice. The player with the highest sum wins. If the sums are equal, it’s a draw. Usually the dice within one set are equal, but different from the kind of dice in the other set.” This is the assignment
    $endgroup$
    – Beginner-Coder123
    Jan 7 at 12:22

  • 1

    $begingroup$
    As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review – Asking Questions for guidance on writing good question titles.
    $endgroup$
    – BCdotWEB
    Jan 7 at 12:24

  • 3

    $begingroup$
    @Beginner-Coder123: You should add that description to the question body.
    $endgroup$
    – Graipher
    Jan 7 at 13:28

  • 2

    $begingroup$
    Please do not vandalize your posts. By posting on the Stack Exchange network, you’ve granted a non-revocable right for SE to distribute that content (under the CC BY-SA 3.0 license). By SE policy, any vandalism will be reverted.
    $endgroup$
    – Glorfindel
    Jan 8 at 19:07

  • $begingroup$
    What is the overall function of this, just rolling some arbitrary dice? It looks awfully complicated for such a simple task if that’s it.
    $endgroup$
    – Mast
    Jan 7 at 12:15

  • $begingroup$
    ” The assignment deals with a game in which two players both have a set of dice. In the game, each player casts his/her dice and sums the outcomes of the dice. The player with the highest sum wins. If the sums are equal, it’s a draw. Usually the dice within one set are equal, but different from the kind of dice in the other set.” This is the assignment
    $endgroup$
    – Beginner-Coder123
    Jan 7 at 12:22

  • 1

    $begingroup$
    As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review – Asking Questions for guidance on writing good question titles.
    $endgroup$
    – BCdotWEB
    Jan 7 at 12:24

  • 3

    $begingroup$
    @Beginner-Coder123: You should add that description to the question body.
    $endgroup$
    – Graipher
    Jan 7 at 13:28

  • 2

    $begingroup$
    Please do not vandalize your posts. By posting on the Stack Exchange network, you’ve granted a non-revocable right for SE to distribute that content (under the CC BY-SA 3.0 license). By SE policy, any vandalism will be reverted.
    $endgroup$
    – Glorfindel
    Jan 8 at 19:07

$begingroup$
What is the overall function of this, just rolling some arbitrary dice? It looks awfully complicated for such a simple task if that’s it.
$endgroup$
– Mast
Jan 7 at 12:15

$begingroup$
What is the overall function of this, just rolling some arbitrary dice? It looks awfully complicated for such a simple task if that’s it.
$endgroup$
– Mast
Jan 7 at 12:15

$begingroup$
” The assignment deals with a game in which two players both have a set of dice. In the game, each player casts his/her dice and sums the outcomes of the dice. The player with the highest sum wins. If the sums are equal, it’s a draw. Usually the dice within one set are equal, but different from the kind of dice in the other set.” This is the assignment
$endgroup$
– Beginner-Coder123
Jan 7 at 12:22

$begingroup$
” The assignment deals with a game in which two players both have a set of dice. In the game, each player casts his/her dice and sums the outcomes of the dice. The player with the highest sum wins. If the sums are equal, it’s a draw. Usually the dice within one set are equal, but different from the kind of dice in the other set.” This is the assignment
$endgroup$
– Beginner-Coder123
Jan 7 at 12:22

1

1

$begingroup$
As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review – Asking Questions for guidance on writing good question titles.
$endgroup$
– BCdotWEB
Jan 7 at 12:24

$begingroup$
As we all want to make our code more efficient or improve it in one way or another, try to write a title that summarizes what your code does, not what you want to get out of a review. Please see How to get the best value out of Code Review – Asking Questions for guidance on writing good question titles.
$endgroup$
– BCdotWEB
Jan 7 at 12:24

3

3

$begingroup$
@Beginner-Coder123: You should add that description to the question body.
$endgroup$
– Graipher
Jan 7 at 13:28

$begingroup$
@Beginner-Coder123: You should add that description to the question body.
$endgroup$
– Graipher
Jan 7 at 13:28

2

2

$begingroup$
Please do not vandalize your posts. By posting on the Stack Exchange network, you’ve granted a non-revocable right for SE to distribute that content (under the CC BY-SA 3.0 license). By SE policy, any vandalism will be reverted.
$endgroup$
– Glorfindel
Jan 8 at 19:07

$begingroup$
Please do not vandalize your posts. By posting on the Stack Exchange network, you’ve granted a non-revocable right for SE to distribute that content (under the CC BY-SA 3.0 license). By SE policy, any vandalism will be reverted.
$endgroup$
– Glorfindel
Jan 8 at 19:07

3 Answers
3

active

oldest

votes

9

$begingroup$

Since you need to roll many times in the chance_overlap function, you might want to optimize making n rolls, using random.choices (Python 3.6+):

from itertools import groupby

def roll(die, n=1):
    if n == 1:
        return random.choice(die)
    return random.choices(die, k=n)

def all_equal(iterable):
    "Returns True if all the elements are equal to each other"
    g = groupby(iterable)
    return next(g, True) and not next(g, False)

def overlap_chance(*dice, n=1000):
    rolls = [roll(die, n) for die in dice]
    equal_rolls = sum(all_equal(roll) for roll in zip(*rolls))
    return equal_rolls / n

Here I chose to include it in your roll function, which is nice because you only have on function, but you do have different return types depending on the value of k, which is not so nice. If you want to you can make it into two separate functions instead.

I made chance_overlap take a variable number of dice so it even works for more than two (and also for one, which is a bit boring).

In addition, I followed Python’s official style-guide, PEP8 for variable names (lower_case).

The all_equal function is directly taken from the itertools recipes.


Using a Monte-Carlo method to determine the chance for the dice to get the same values is fine, but you could just use plain old math.

Each distinct value $j$ on each die $i$ has probability $p^i_j = n^i_j / k_i$, where $k_i$ is the number of faces of die $i$ and $n^i_j$ the number of times the value $j$ appears on that die. Then the chance to have an overlap is simply given by

$$
P(overlap) = sumlimits_j prodlimits_i p^i_j = sumlimits_j prodlimits_i n^i_j / k_i,
$$

where $i$ goes over all dice and $j$ over all values present on any dice (with $n^i_j = 0$ if value $j$ does not appear on die $i$).

In other words dice rolls are independent events and e.g. the chance to get two heads or two tails with a fair coin (dice = [["H", "T"], ["H", "T"]]) are $P(HH vee TT) = 0.5cdot0.5 + 0.5cdot0.5 = 0.5$.

from collections import Counter
from functools import reduce
from itertools import chain
from operator import mul

def overlap_chance_real(*dice):
    all_values = set(chain.from_iterable(dice))
    counters = [Counter(die) for die in dice]
    lengths = [len(die) for die in dice]
    return sum(reduce(mul, [counter[val] / length
                            for counter, length in zip(counters, lengths)])
               for val in all_values)

The nice thing about this is that we don’t need to worry if not all dice have the same values, since Counter objects return a count of zero for non-existing keys.

For dice = [[1,2,3], [1,1,1]] it returns the correct (and precise) value of 0.3333333333333333. and 0.5 for dice = [["H", "T"], ["H", "T"]].

The execution time of the first function (overlap_chance) increases linearly with the number of dice (all six sided) and it is in general slower than the second function (overlap_chance_real):

enter image description here

With two dice with increasing number of faces the first function is slower but basically constant in time while the second function is faster but the time increases with the number of faces. This plot also contains your function (since it can only deal with two dice it was not included in the previous plot), which is slower than both:

enter image description here

share|improve this answer

$endgroup$

  • 1

    $begingroup$
    What did you use to make those graphs?
    $endgroup$
    – esote
    Jan 7 at 18:24

  • 1

    $begingroup$
    @esote timeit and matplotlib, as detailed in this question (and it’s answer).
    $endgroup$
    – Graipher
    Jan 7 at 18:38

  • $begingroup$
    I tried to implement this, but I broke the code.
    $endgroup$
    – Beginner-Coder123
    Jan 8 at 16:56

  • $begingroup$
    I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
    $endgroup$
    – Beginner-Coder123
    Jan 8 at 17:01

  • 1

    $begingroup$
    Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
    $endgroup$
    – Beginner-Coder123
    Jan 8 at 17:05

8

$begingroup$

The right functions

def roll(die):
    number = random.randint(0,len(die)-1)
    b = die[number]
    return b

Can be:

def roll(die):
    return random.choice(die)

inptchacc()

You could instead check only the first character of the input (case insensitive), rather than expecting the whole keyword.

Your return variable stop isn’t needed. Just return directly.

stop = 'stop'
return stop

Should be:

return 'stop'

Likewise elsewhere in your code.

Use __main__

Rather than putting code between functions and also at the end of the file, instead use:

if __name__ == '__main__':

See here for more details.

share|improve this answer

$endgroup$

    3

    $begingroup$

    Make the computer count

    These:

    Die1 = [1,2,3,4]
    Die2 = [1,2,3,4,5,6]
    

    should be

    die1 = list(range(1, 5))
    die2 = list(range(1, 7))
    

    Only compare once

    This list:

    ending_conditions = ['stop','Stop','quit','Quit']
    

    has two problems. First, don’t store multiple cases for one word – just store lower (or upper) case – I prefer lower, because no shouting.

    Also, it shouldn’t be a list. It’s getting tested for membership, so make it a set. In other words,

    ending_conditions = {'stop', 'quiet'}
    

    then for use, write “not in”, instead of “not x in”; i.e.

    if inpu.lower() not in ending_conditions:
    

    PEP8

    Run your code through a linter. It will catch several things, including

    • Don’t capitalize your variables (Die1, Die2, Adding, Doing) if they’re local. If they’re global constants, they need to be all-uppercase. In the case of Die*, they probably shouldn’t be global at all.
    • Add a couple of newlines after your imports

    Grammar

    Write:

    print('Incompatible options selected. Returning...')
    

    share|improve this answer

    $endgroup$

      Your Answer

      StackExchange.ifUsing(“editor”, function () {
      return StackExchange.using(“mathjaxEditing”, function () {
      StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
      StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [[“\$”, “\$”]]);
      });
      });
      }, “mathjax-editing”);

      StackExchange.ifUsing(“editor”, function () {
      StackExchange.using(“externalEditor”, function () {
      StackExchange.using(“snippets”, function () {
      StackExchange.snippets.init();
      });
      });
      }, “code-snippets”);

      StackExchange.ready(function() {
      var channelOptions = {
      tags: “”.split(” “),
      id: “196”
      };
      initTagRenderer(“”.split(” “), “”.split(” “), channelOptions);

      StackExchange.using(“externalEditor”, function() {
      // Have to fire editor after snippets, if snippets enabled
      if (StackExchange.settings.snippets.snippetsEnabled) {
      StackExchange.using(“snippets”, function() {
      createEditor();
      });
      }
      else {
      createEditor();
      }
      });

      function createEditor() {
      StackExchange.prepareEditor({
      heartbeatType: ‘answer’,
      autoActivateHeartbeat: false,
      convertImagesToLinks: false,
      noModals: true,
      showLowRepImageUploadWarning: true,
      reputationToPostImages: null,
      bindNavPrevention: true,
      postfix: “”,
      imageUploader: {
      brandingHtml: “Powered by u003ca class=”icon-imgur-white” href=”https://imgur.com/”u003eu003c/au003e”,
      contentPolicyHtml: “User contributions licensed under u003ca href=”https://creativecommons.org/licenses/by-sa/3.0/”u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href=”https://stackoverflow.com/legal/content-policy”u003e(content policy)u003c/au003e”,
      allowUrls: true
      },
      onDemand: true,
      discardSelector: “.discard-answer”
      ,immediatelyShowMarkdownHelp:true
      });

      }
      });

      draft saved
      draft discarded

      StackExchange.ready(
      function () {
      StackExchange.openid.initPostLogin(‘.new-post-login’, ‘https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211022%2farbitrary-dice-with-probability%23new-answer’, ‘question_page’);
      }
      );

      Post as a guest

      Required, but never shown

      3 Answers
      3

      active

      oldest

      votes

      3 Answers
      3

      active

      oldest

      votes

      active

      oldest

      votes

      active

      oldest

      votes

      9

      $begingroup$

      Since you need to roll many times in the chance_overlap function, you might want to optimize making n rolls, using random.choices (Python 3.6+):

      from itertools import groupby
      
      def roll(die, n=1):
          if n == 1:
              return random.choice(die)
          return random.choices(die, k=n)
      
      def all_equal(iterable):
          "Returns True if all the elements are equal to each other"
          g = groupby(iterable)
          return next(g, True) and not next(g, False)
      
      def overlap_chance(*dice, n=1000):
          rolls = [roll(die, n) for die in dice]
          equal_rolls = sum(all_equal(roll) for roll in zip(*rolls))
          return equal_rolls / n
      

      Here I chose to include it in your roll function, which is nice because you only have on function, but you do have different return types depending on the value of k, which is not so nice. If you want to you can make it into two separate functions instead.

      I made chance_overlap take a variable number of dice so it even works for more than two (and also for one, which is a bit boring).

      In addition, I followed Python’s official style-guide, PEP8 for variable names (lower_case).

      The all_equal function is directly taken from the itertools recipes.


      Using a Monte-Carlo method to determine the chance for the dice to get the same values is fine, but you could just use plain old math.

      Each distinct value $j$ on each die $i$ has probability $p^i_j = n^i_j / k_i$, where $k_i$ is the number of faces of die $i$ and $n^i_j$ the number of times the value $j$ appears on that die. Then the chance to have an overlap is simply given by

      $$
      P(overlap) = sumlimits_j prodlimits_i p^i_j = sumlimits_j prodlimits_i n^i_j / k_i,
      $$

      where $i$ goes over all dice and $j$ over all values present on any dice (with $n^i_j = 0$ if value $j$ does not appear on die $i$).

      In other words dice rolls are independent events and e.g. the chance to get two heads or two tails with a fair coin (dice = [["H", "T"], ["H", "T"]]) are $P(HH vee TT) = 0.5cdot0.5 + 0.5cdot0.5 = 0.5$.

      from collections import Counter
      from functools import reduce
      from itertools import chain
      from operator import mul
      
      def overlap_chance_real(*dice):
          all_values = set(chain.from_iterable(dice))
          counters = [Counter(die) for die in dice]
          lengths = [len(die) for die in dice]
          return sum(reduce(mul, [counter[val] / length
                                  for counter, length in zip(counters, lengths)])
                     for val in all_values)
      

      The nice thing about this is that we don’t need to worry if not all dice have the same values, since Counter objects return a count of zero for non-existing keys.

      For dice = [[1,2,3], [1,1,1]] it returns the correct (and precise) value of 0.3333333333333333. and 0.5 for dice = [["H", "T"], ["H", "T"]].

      The execution time of the first function (overlap_chance) increases linearly with the number of dice (all six sided) and it is in general slower than the second function (overlap_chance_real):

      enter image description here

      With two dice with increasing number of faces the first function is slower but basically constant in time while the second function is faster but the time increases with the number of faces. This plot also contains your function (since it can only deal with two dice it was not included in the previous plot), which is slower than both:

      enter image description here

      share|improve this answer

      $endgroup$

      • 1

        $begingroup$
        What did you use to make those graphs?
        $endgroup$
        – esote
        Jan 7 at 18:24

      • 1

        $begingroup$
        @esote timeit and matplotlib, as detailed in this question (and it’s answer).
        $endgroup$
        – Graipher
        Jan 7 at 18:38

      • $begingroup$
        I tried to implement this, but I broke the code.
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 16:56

      • $begingroup$
        I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:01

      • 1

        $begingroup$
        Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:05

      9

      $begingroup$

      Since you need to roll many times in the chance_overlap function, you might want to optimize making n rolls, using random.choices (Python 3.6+):

      from itertools import groupby
      
      def roll(die, n=1):
          if n == 1:
              return random.choice(die)
          return random.choices(die, k=n)
      
      def all_equal(iterable):
          "Returns True if all the elements are equal to each other"
          g = groupby(iterable)
          return next(g, True) and not next(g, False)
      
      def overlap_chance(*dice, n=1000):
          rolls = [roll(die, n) for die in dice]
          equal_rolls = sum(all_equal(roll) for roll in zip(*rolls))
          return equal_rolls / n
      

      Here I chose to include it in your roll function, which is nice because you only have on function, but you do have different return types depending on the value of k, which is not so nice. If you want to you can make it into two separate functions instead.

      I made chance_overlap take a variable number of dice so it even works for more than two (and also for one, which is a bit boring).

      In addition, I followed Python’s official style-guide, PEP8 for variable names (lower_case).

      The all_equal function is directly taken from the itertools recipes.


      Using a Monte-Carlo method to determine the chance for the dice to get the same values is fine, but you could just use plain old math.

      Each distinct value $j$ on each die $i$ has probability $p^i_j = n^i_j / k_i$, where $k_i$ is the number of faces of die $i$ and $n^i_j$ the number of times the value $j$ appears on that die. Then the chance to have an overlap is simply given by

      $$
      P(overlap) = sumlimits_j prodlimits_i p^i_j = sumlimits_j prodlimits_i n^i_j / k_i,
      $$

      where $i$ goes over all dice and $j$ over all values present on any dice (with $n^i_j = 0$ if value $j$ does not appear on die $i$).

      In other words dice rolls are independent events and e.g. the chance to get two heads or two tails with a fair coin (dice = [["H", "T"], ["H", "T"]]) are $P(HH vee TT) = 0.5cdot0.5 + 0.5cdot0.5 = 0.5$.

      from collections import Counter
      from functools import reduce
      from itertools import chain
      from operator import mul
      
      def overlap_chance_real(*dice):
          all_values = set(chain.from_iterable(dice))
          counters = [Counter(die) for die in dice]
          lengths = [len(die) for die in dice]
          return sum(reduce(mul, [counter[val] / length
                                  for counter, length in zip(counters, lengths)])
                     for val in all_values)
      

      The nice thing about this is that we don’t need to worry if not all dice have the same values, since Counter objects return a count of zero for non-existing keys.

      For dice = [[1,2,3], [1,1,1]] it returns the correct (and precise) value of 0.3333333333333333. and 0.5 for dice = [["H", "T"], ["H", "T"]].

      The execution time of the first function (overlap_chance) increases linearly with the number of dice (all six sided) and it is in general slower than the second function (overlap_chance_real):

      enter image description here

      With two dice with increasing number of faces the first function is slower but basically constant in time while the second function is faster but the time increases with the number of faces. This plot also contains your function (since it can only deal with two dice it was not included in the previous plot), which is slower than both:

      enter image description here

      share|improve this answer

      $endgroup$

      • 1

        $begingroup$
        What did you use to make those graphs?
        $endgroup$
        – esote
        Jan 7 at 18:24

      • 1

        $begingroup$
        @esote timeit and matplotlib, as detailed in this question (and it’s answer).
        $endgroup$
        – Graipher
        Jan 7 at 18:38

      • $begingroup$
        I tried to implement this, but I broke the code.
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 16:56

      • $begingroup$
        I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:01

      • 1

        $begingroup$
        Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:05

      9

      9

      9

      $begingroup$

      Since you need to roll many times in the chance_overlap function, you might want to optimize making n rolls, using random.choices (Python 3.6+):

      from itertools import groupby
      
      def roll(die, n=1):
          if n == 1:
              return random.choice(die)
          return random.choices(die, k=n)
      
      def all_equal(iterable):
          "Returns True if all the elements are equal to each other"
          g = groupby(iterable)
          return next(g, True) and not next(g, False)
      
      def overlap_chance(*dice, n=1000):
          rolls = [roll(die, n) for die in dice]
          equal_rolls = sum(all_equal(roll) for roll in zip(*rolls))
          return equal_rolls / n
      

      Here I chose to include it in your roll function, which is nice because you only have on function, but you do have different return types depending on the value of k, which is not so nice. If you want to you can make it into two separate functions instead.

      I made chance_overlap take a variable number of dice so it even works for more than two (and also for one, which is a bit boring).

      In addition, I followed Python’s official style-guide, PEP8 for variable names (lower_case).

      The all_equal function is directly taken from the itertools recipes.


      Using a Monte-Carlo method to determine the chance for the dice to get the same values is fine, but you could just use plain old math.

      Each distinct value $j$ on each die $i$ has probability $p^i_j = n^i_j / k_i$, where $k_i$ is the number of faces of die $i$ and $n^i_j$ the number of times the value $j$ appears on that die. Then the chance to have an overlap is simply given by

      $$
      P(overlap) = sumlimits_j prodlimits_i p^i_j = sumlimits_j prodlimits_i n^i_j / k_i,
      $$

      where $i$ goes over all dice and $j$ over all values present on any dice (with $n^i_j = 0$ if value $j$ does not appear on die $i$).

      In other words dice rolls are independent events and e.g. the chance to get two heads or two tails with a fair coin (dice = [["H", "T"], ["H", "T"]]) are $P(HH vee TT) = 0.5cdot0.5 + 0.5cdot0.5 = 0.5$.

      from collections import Counter
      from functools import reduce
      from itertools import chain
      from operator import mul
      
      def overlap_chance_real(*dice):
          all_values = set(chain.from_iterable(dice))
          counters = [Counter(die) for die in dice]
          lengths = [len(die) for die in dice]
          return sum(reduce(mul, [counter[val] / length
                                  for counter, length in zip(counters, lengths)])
                     for val in all_values)
      

      The nice thing about this is that we don’t need to worry if not all dice have the same values, since Counter objects return a count of zero for non-existing keys.

      For dice = [[1,2,3], [1,1,1]] it returns the correct (and precise) value of 0.3333333333333333. and 0.5 for dice = [["H", "T"], ["H", "T"]].

      The execution time of the first function (overlap_chance) increases linearly with the number of dice (all six sided) and it is in general slower than the second function (overlap_chance_real):

      enter image description here

      With two dice with increasing number of faces the first function is slower but basically constant in time while the second function is faster but the time increases with the number of faces. This plot also contains your function (since it can only deal with two dice it was not included in the previous plot), which is slower than both:

      enter image description here

      share|improve this answer

      $endgroup$

      Since you need to roll many times in the chance_overlap function, you might want to optimize making n rolls, using random.choices (Python 3.6+):

      from itertools import groupby
      
      def roll(die, n=1):
          if n == 1:
              return random.choice(die)
          return random.choices(die, k=n)
      
      def all_equal(iterable):
          "Returns True if all the elements are equal to each other"
          g = groupby(iterable)
          return next(g, True) and not next(g, False)
      
      def overlap_chance(*dice, n=1000):
          rolls = [roll(die, n) for die in dice]
          equal_rolls = sum(all_equal(roll) for roll in zip(*rolls))
          return equal_rolls / n
      

      Here I chose to include it in your roll function, which is nice because you only have on function, but you do have different return types depending on the value of k, which is not so nice. If you want to you can make it into two separate functions instead.

      I made chance_overlap take a variable number of dice so it even works for more than two (and also for one, which is a bit boring).

      In addition, I followed Python’s official style-guide, PEP8 for variable names (lower_case).

      The all_equal function is directly taken from the itertools recipes.


      Using a Monte-Carlo method to determine the chance for the dice to get the same values is fine, but you could just use plain old math.

      Each distinct value $j$ on each die $i$ has probability $p^i_j = n^i_j / k_i$, where $k_i$ is the number of faces of die $i$ and $n^i_j$ the number of times the value $j$ appears on that die. Then the chance to have an overlap is simply given by

      $$
      P(overlap) = sumlimits_j prodlimits_i p^i_j = sumlimits_j prodlimits_i n^i_j / k_i,
      $$

      where $i$ goes over all dice and $j$ over all values present on any dice (with $n^i_j = 0$ if value $j$ does not appear on die $i$).

      In other words dice rolls are independent events and e.g. the chance to get two heads or two tails with a fair coin (dice = [["H", "T"], ["H", "T"]]) are $P(HH vee TT) = 0.5cdot0.5 + 0.5cdot0.5 = 0.5$.

      from collections import Counter
      from functools import reduce
      from itertools import chain
      from operator import mul
      
      def overlap_chance_real(*dice):
          all_values = set(chain.from_iterable(dice))
          counters = [Counter(die) for die in dice]
          lengths = [len(die) for die in dice]
          return sum(reduce(mul, [counter[val] / length
                                  for counter, length in zip(counters, lengths)])
                     for val in all_values)
      

      The nice thing about this is that we don’t need to worry if not all dice have the same values, since Counter objects return a count of zero for non-existing keys.

      For dice = [[1,2,3], [1,1,1]] it returns the correct (and precise) value of 0.3333333333333333. and 0.5 for dice = [["H", "T"], ["H", "T"]].

      The execution time of the first function (overlap_chance) increases linearly with the number of dice (all six sided) and it is in general slower than the second function (overlap_chance_real):

      enter image description here

      With two dice with increasing number of faces the first function is slower but basically constant in time while the second function is faster but the time increases with the number of faces. This plot also contains your function (since it can only deal with two dice it was not included in the previous plot), which is slower than both:

      enter image description here

      share|improve this answer

      share|improve this answer

      share|improve this answer

      edited Jan 9 at 14:08

      answered Jan 7 at 15:44

      GraipherGraipher

      23.9k53585

      23.9k53585

      • 1

        $begingroup$
        What did you use to make those graphs?
        $endgroup$
        – esote
        Jan 7 at 18:24

      • 1

        $begingroup$
        @esote timeit and matplotlib, as detailed in this question (and it’s answer).
        $endgroup$
        – Graipher
        Jan 7 at 18:38

      • $begingroup$
        I tried to implement this, but I broke the code.
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 16:56

      • $begingroup$
        I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:01

      • 1

        $begingroup$
        Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:05

      • 1

        $begingroup$
        What did you use to make those graphs?
        $endgroup$
        – esote
        Jan 7 at 18:24

      • 1

        $begingroup$
        @esote timeit and matplotlib, as detailed in this question (and it’s answer).
        $endgroup$
        – Graipher
        Jan 7 at 18:38

      • $begingroup$
        I tried to implement this, but I broke the code.
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 16:56

      • $begingroup$
        I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:01

      • 1

        $begingroup$
        Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
        $endgroup$
        – Beginner-Coder123
        Jan 8 at 17:05

      1

      1

      $begingroup$
      What did you use to make those graphs?
      $endgroup$
      – esote
      Jan 7 at 18:24

      $begingroup$
      What did you use to make those graphs?
      $endgroup$
      – esote
      Jan 7 at 18:24

      1

      1

      $begingroup$
      @esote timeit and matplotlib, as detailed in this question (and it’s answer).
      $endgroup$
      – Graipher
      Jan 7 at 18:38

      $begingroup$
      @esote timeit and matplotlib, as detailed in this question (and it’s answer).
      $endgroup$
      – Graipher
      Jan 7 at 18:38

      $begingroup$
      I tried to implement this, but I broke the code.
      $endgroup$
      – Beginner-Coder123
      Jan 8 at 16:56

      $begingroup$
      I tried to implement this, but I broke the code.
      $endgroup$
      – Beginner-Coder123
      Jan 8 at 16:56

      $begingroup$
      I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
      $endgroup$
      – Beginner-Coder123
      Jan 8 at 17:01

      $begingroup$
      I just don’t seem to know what functions you’re specifying with the “all_equal”, and “overlap_chance_real” If I intend to replace them with some of my written functions, it doesn’t work :/. What changes have to be made exactly when reviewing the answer from you? I’m a newbie to be honest
      $endgroup$
      – Beginner-Coder123
      Jan 8 at 17:01

      1

      1

      $begingroup$
      Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
      $endgroup$
      – Beginner-Coder123
      Jan 8 at 17:05

      $begingroup$
      Haha, yes I made the mistake with specifying who it was. but alright, I’ll give it a try. Much appreciated!
      $endgroup$
      – Beginner-Coder123
      Jan 8 at 17:05

      8

      $begingroup$

      The right functions

      def roll(die):
          number = random.randint(0,len(die)-1)
          b = die[number]
          return b
      

      Can be:

      def roll(die):
          return random.choice(die)
      

      inptchacc()

      You could instead check only the first character of the input (case insensitive), rather than expecting the whole keyword.

      Your return variable stop isn’t needed. Just return directly.

      stop = 'stop'
      return stop
      

      Should be:

      return 'stop'
      

      Likewise elsewhere in your code.

      Use __main__

      Rather than putting code between functions and also at the end of the file, instead use:

      if __name__ == '__main__':
      

      See here for more details.

      share|improve this answer

      $endgroup$

        8

        $begingroup$

        The right functions

        def roll(die):
            number = random.randint(0,len(die)-1)
            b = die[number]
            return b
        

        Can be:

        def roll(die):
            return random.choice(die)
        

        inptchacc()

        You could instead check only the first character of the input (case insensitive), rather than expecting the whole keyword.

        Your return variable stop isn’t needed. Just return directly.

        stop = 'stop'
        return stop
        

        Should be:

        return 'stop'
        

        Likewise elsewhere in your code.

        Use __main__

        Rather than putting code between functions and also at the end of the file, instead use:

        if __name__ == '__main__':
        

        See here for more details.

        share|improve this answer

        $endgroup$

          8

          8

          8

          $begingroup$

          The right functions

          def roll(die):
              number = random.randint(0,len(die)-1)
              b = die[number]
              return b
          

          Can be:

          def roll(die):
              return random.choice(die)
          

          inptchacc()

          You could instead check only the first character of the input (case insensitive), rather than expecting the whole keyword.

          Your return variable stop isn’t needed. Just return directly.

          stop = 'stop'
          return stop
          

          Should be:

          return 'stop'
          

          Likewise elsewhere in your code.

          Use __main__

          Rather than putting code between functions and also at the end of the file, instead use:

          if __name__ == '__main__':
          

          See here for more details.

          share|improve this answer

          $endgroup$

          The right functions

          def roll(die):
              number = random.randint(0,len(die)-1)
              b = die[number]
              return b
          

          Can be:

          def roll(die):
              return random.choice(die)
          

          inptchacc()

          You could instead check only the first character of the input (case insensitive), rather than expecting the whole keyword.

          Your return variable stop isn’t needed. Just return directly.

          stop = 'stop'
          return stop
          

          Should be:

          return 'stop'
          

          Likewise elsewhere in your code.

          Use __main__

          Rather than putting code between functions and also at the end of the file, instead use:

          if __name__ == '__main__':
          

          See here for more details.

          share|improve this answer

          share|improve this answer

          share|improve this answer

          answered Jan 7 at 13:51

          esoteesote

          2,6391934

          2,6391934

              3

              $begingroup$

              Make the computer count

              These:

              Die1 = [1,2,3,4]
              Die2 = [1,2,3,4,5,6]
              

              should be

              die1 = list(range(1, 5))
              die2 = list(range(1, 7))
              

              Only compare once

              This list:

              ending_conditions = ['stop','Stop','quit','Quit']
              

              has two problems. First, don’t store multiple cases for one word – just store lower (or upper) case – I prefer lower, because no shouting.

              Also, it shouldn’t be a list. It’s getting tested for membership, so make it a set. In other words,

              ending_conditions = {'stop', 'quiet'}
              

              then for use, write “not in”, instead of “not x in”; i.e.

              if inpu.lower() not in ending_conditions:
              

              PEP8

              Run your code through a linter. It will catch several things, including

              • Don’t capitalize your variables (Die1, Die2, Adding, Doing) if they’re local. If they’re global constants, they need to be all-uppercase. In the case of Die*, they probably shouldn’t be global at all.
              • Add a couple of newlines after your imports

              Grammar

              Write:

              print('Incompatible options selected. Returning...')
              

              share|improve this answer

              $endgroup$

                3

                $begingroup$

                Make the computer count

                These:

                Die1 = [1,2,3,4]
                Die2 = [1,2,3,4,5,6]
                

                should be

                die1 = list(range(1, 5))
                die2 = list(range(1, 7))
                

                Only compare once

                This list:

                ending_conditions = ['stop','Stop','quit','Quit']
                

                has two problems. First, don’t store multiple cases for one word – just store lower (or upper) case – I prefer lower, because no shouting.

                Also, it shouldn’t be a list. It’s getting tested for membership, so make it a set. In other words,

                ending_conditions = {'stop', 'quiet'}
                

                then for use, write “not in”, instead of “not x in”; i.e.

                if inpu.lower() not in ending_conditions:
                

                PEP8

                Run your code through a linter. It will catch several things, including

                • Don’t capitalize your variables (Die1, Die2, Adding, Doing) if they’re local. If they’re global constants, they need to be all-uppercase. In the case of Die*, they probably shouldn’t be global at all.
                • Add a couple of newlines after your imports

                Grammar

                Write:

                print('Incompatible options selected. Returning...')
                

                share|improve this answer

                $endgroup$

                  3

                  3

                  3

                  $begingroup$

                  Make the computer count

                  These:

                  Die1 = [1,2,3,4]
                  Die2 = [1,2,3,4,5,6]
                  

                  should be

                  die1 = list(range(1, 5))
                  die2 = list(range(1, 7))
                  

                  Only compare once

                  This list:

                  ending_conditions = ['stop','Stop','quit','Quit']
                  

                  has two problems. First, don’t store multiple cases for one word – just store lower (or upper) case – I prefer lower, because no shouting.

                  Also, it shouldn’t be a list. It’s getting tested for membership, so make it a set. In other words,

                  ending_conditions = {'stop', 'quiet'}
                  

                  then for use, write “not in”, instead of “not x in”; i.e.

                  if inpu.lower() not in ending_conditions:
                  

                  PEP8

                  Run your code through a linter. It will catch several things, including

                  • Don’t capitalize your variables (Die1, Die2, Adding, Doing) if they’re local. If they’re global constants, they need to be all-uppercase. In the case of Die*, they probably shouldn’t be global at all.
                  • Add a couple of newlines after your imports

                  Grammar

                  Write:

                  print('Incompatible options selected. Returning...')
                  

                  share|improve this answer

                  $endgroup$

                  Make the computer count

                  These:

                  Die1 = [1,2,3,4]
                  Die2 = [1,2,3,4,5,6]
                  

                  should be

                  die1 = list(range(1, 5))
                  die2 = list(range(1, 7))
                  

                  Only compare once

                  This list:

                  ending_conditions = ['stop','Stop','quit','Quit']
                  

                  has two problems. First, don’t store multiple cases for one word – just store lower (or upper) case – I prefer lower, because no shouting.

                  Also, it shouldn’t be a list. It’s getting tested for membership, so make it a set. In other words,

                  ending_conditions = {'stop', 'quiet'}
                  

                  then for use, write “not in”, instead of “not x in”; i.e.

                  if inpu.lower() not in ending_conditions:
                  

                  PEP8

                  Run your code through a linter. It will catch several things, including

                  • Don’t capitalize your variables (Die1, Die2, Adding, Doing) if they’re local. If they’re global constants, they need to be all-uppercase. In the case of Die*, they probably shouldn’t be global at all.
                  • Add a couple of newlines after your imports

                  Grammar

                  Write:

                  print('Incompatible options selected. Returning...')
                  

                  share|improve this answer

                  share|improve this answer

                  share|improve this answer

                  answered Jan 7 at 15:41

                  ReinderienReinderien

                  4,140822

                  4,140822

                      draft saved
                      draft discarded

                      Thanks for contributing an answer to Code Review Stack Exchange!

                      • Please be sure to answer the question. Provide details and share your research!

                      But avoid

                      • Asking for help, clarification, or responding to other answers.
                      • Making statements based on opinion; back them up with references or personal experience.

                      Use MathJax to format equations. MathJax reference.

                      To learn more, see our tips on writing great answers.

                      draft saved

                      draft discarded

                      StackExchange.ready(
                      function () {
                      StackExchange.openid.initPostLogin(‘.new-post-login’, ‘https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211022%2farbitrary-dice-with-probability%23new-answer’, ‘question_page’);
                      }
                      );

                      Post as a guest

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Required, but never shown

                      Related Post

                      Leave a Reply

                      Your email address will not be published. Required fields are marked *