Repository: sympy/sympy
Pull request: Fixed DiscreteUniform to produce correct density and cdf
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->
Fixed DiscreteUniform to produce correct density and cdf
#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
Fixes #18611

#### Brief description of what is fixed or changed


#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* stats
  * `DiscreteUniform` raises ValueError for duplicate args
<!-- END RELEASE NOTES -->

Problem statement:
Incorrect density, cdf calculation in DiscreteUniform
```
>>> from sympy.stats import density, DiscreteUniform, cdf
>>> X = DiscreteUniform('X', [1, 1, 1, 2, 2])
>>> dict(density(X).items())
{1: 1/5, 2: 1/5}
>>> cdf(X)
{1: 1/5, 2: 2/5}
```
Issue is:
Considering that the arguments in the `DiscreteUniformDistribution`, it should be kept as finite set, and therefore should produce `density` as `{1: 1/2, 2: 1/2}`,
Also as `density` is wrongly evaluated, `cdf` produces wrong result, as it should attain value of `1` at some point in the domain of the distribution, which is not satisfied in the above result.

Hints from issue discussion:

Commit message: corrected to set of args

Patch to review:
diff --git a/sympy/stats/frv_types.py b/sympy/stats/frv_types.py
index d5b35606dc67..4dcdeee4773e 100644
--- a/sympy/stats/frv_types.py
+++ b/sympy/stats/frv_types.py
@@ -92,7 +92,7 @@ def FiniteRV(name, density):
 class DiscreteUniformDistribution(SingleFiniteDistribution):
     @property
     def p(self):
-        return Rational(1, len(self.args))
+        return Rational(1, len(set(self.args)))
 
     @property  # type: ignore
     @cacheit
@@ -104,7 +104,7 @@ def set(self):
         return set(self.args)
 
     def pmf(self, x):
-        if x in self.args:
+        if x in set(self.args):
             return self.p
         else:
             return S.Zero
diff --git a/sympy/stats/tests/test_finite_rv.py b/sympy/stats/tests/test_finite_rv.py
index 040262f060c9..13d886a96288 100644
--- a/sympy/stats/tests/test_finite_rv.py
+++ b/sympy/stats/tests/test_finite_rv.py
@@ -45,6 +45,13 @@ def test_discreteuniform():
 
     assert characteristic_function(X)(t) == exp(I*a*t)/3 + exp(I*b*t)/3 + exp(I*c*t)/3
     assert moment_generating_function(X)(t) == exp(a*t)/3 + exp(b*t)/3 + exp(c*t)/3
+    # issue 18611
+    Z = DiscreteUniform('Z', [a, a, a, b, b, c])
+    assert dict(density(Z).items()) == {a: S('1/3'), b: S('1/3'), c: S('1/3')}
+    assert E(Z) == (a + b + c)/3
+    Z = DiscreteUniform('Z', [1, 1, 1, 2, 2, 3, 3, 3, 4])
+    assert dict(density(Z).items()) == {1: S('1/4'), 2: S('1/4'), 3: S('1/4'), 4: S('1/4')}
+    assert cdf(Z) == {1: S('1/4'), 2: S('1/2'), 3: S('3/4'), 4: 1}
 
 def test_dice():
     # TODO: Make iid method!


Code review comments:
diff_hunk: @@ -104,7 +104,7 @@ def set(self):
         return set(self.args)
 
     def pmf(self, x):
-        if x in self.args:
+        if x in set(self.args):
path: sympy/stats/frv_types.py
text: @user1:
`set` would not be necessary here.

@author:
Okay, will remove it. 

@author:
@user1 Is it good to go now?