Skip to content

Commit b68b8f5

Browse files
committed
[GR-10178] Add _random.Random and make random module work
PullRequest: graalpython/71
2 parents 0b0a6c0 + 0cb9edf commit b68b8f5

File tree

21 files changed

+1936
-211
lines changed

21 files changed

+1936
-211
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Copyright (c) 2018, Oracle and/or its affiliates.
2+
# Copyright (C) 1996-2017 Python Software Foundation
3+
#
4+
# Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5+
6+
import unittest
7+
import random
8+
import time
9+
10+
11+
class TestBasicOps:
12+
def __init__(self):
13+
super().__init__()
14+
self.gen = random.Random()
15+
16+
def randomlist(self, n):
17+
"""Helper function to make a list of random numbers"""
18+
return [self.gen.random() for i in range(n)]
19+
20+
def test_autoseed(self):
21+
self.gen.seed()
22+
state1 = self.gen.getstate()
23+
time.sleep(0.1)
24+
self.gen.seed() # diffent seeds at different times
25+
state2 = self.gen.getstate()
26+
self.assertNotEqual(state1, state2)
27+
28+
def test_saverestore(self):
29+
N = 1000
30+
self.gen.seed()
31+
state = self.gen.getstate()
32+
randseq = self.randomlist(N)
33+
self.gen.setstate(state) # should regenerate the same sequence
34+
self.assertEqual(randseq, self.randomlist(N))
35+
36+
def test_seedargs(self):
37+
# Seed value with a negative hash.
38+
class MySeed(object):
39+
def __hash__(self):
40+
return -1729
41+
for arg in [None, 0, 0, 1, 1, -1, -1, 10**20, -(10**20),
42+
3.14, 1+2j, 'a', tuple('abc'), MySeed()]:
43+
self.gen.seed(arg)
44+
for arg in [list(range(3)), dict(one=1)]:
45+
self.assertRaises(TypeError, self.gen.seed, arg)
46+
self.assertRaises(TypeError, self.gen.seed, 1, 2, 3, 4)
47+
self.assertRaises(TypeError, type(self.gen), [])
48+
49+
def test_shuffle(self):
50+
shuffle = self.gen.shuffle
51+
lst = []
52+
shuffle(lst)
53+
self.assertEqual(lst, [])
54+
lst = [37]
55+
shuffle(lst)
56+
self.assertEqual(lst, [37])
57+
seqs = [list(range(n)) for n in range(10)]
58+
shuffled_seqs = [list(range(n)) for n in range(10)]
59+
for shuffled_seq in shuffled_seqs:
60+
shuffle(shuffled_seq)
61+
for (seq, shuffled_seq) in zip(seqs, shuffled_seqs):
62+
self.assertEqual(len(seq), len(shuffled_seq))
63+
self.assertEqual(set(seq), set(shuffled_seq))
64+
# The above tests all would pass if the shuffle was a
65+
# no-op. The following non-deterministic test covers that. It
66+
# asserts that the shuffled sequence of 1000 distinct elements
67+
# must be different from the original one. Although there is
68+
# mathematically a non-zero probability that this could
69+
# actually happen in a genuinely random shuffle, it is
70+
# completely negligible, given that the number of possible
71+
# permutations of 1000 objects is 1000! (factorial of 1000),
72+
# which is considerably larger than the number of atoms in the
73+
# universe...
74+
lst = list(range(1000))
75+
shuffled_lst = list(range(1000))
76+
shuffle(shuffled_lst)
77+
self.assertTrue(lst != shuffled_lst)
78+
shuffle(lst)
79+
self.assertTrue(lst != shuffled_lst)
80+
81+
# def test_choice(self):
82+
# choice = self.gen.choice
83+
# with self.assertRaises(IndexError):
84+
# choice([])
85+
# self.assertEqual(choice([50]), 50)
86+
# self.assertIn(choice([25, 75]), [25, 75])
87+
88+
def test_sample(self):
89+
# For the entire allowable range of 0 <= k <= N, validate that
90+
# the sample is of the correct length and contains only unique items
91+
N = 100
92+
population = range(N)
93+
for k in range(N+1):
94+
s = self.gen.sample(population, k)
95+
self.assertEqual(len(s), k)
96+
uniq = set(s)
97+
self.assertEqual(len(uniq), k)
98+
self.assertTrue(uniq <= set(population))
99+
self.assertEqual(self.gen.sample([], 0), []) # test edge case N==k==0
100+
# Exception raised if size of sample exceeds that of population
101+
self.assertRaises(ValueError, self.gen.sample, population, N+1)
102+
self.assertRaises(ValueError, self.gen.sample, [], -1)
103+
104+
def test_sample_distribution(self):
105+
# For the entire allowable range of 0 <= k <= N, validate that
106+
# sample generates all possible permutations
107+
n = 5
108+
pop = range(n)
109+
trials = 10000 # large num prevents false negatives without slowing normal case
110+
def factorial(n):
111+
if n == 0:
112+
return 1
113+
return n * factorial(n - 1)
114+
for k in range(n):
115+
expected = factorial(n) // factorial(n-k)
116+
perms = {}
117+
for i in range(trials):
118+
perms[tuple(self.gen.sample(pop, k))] = None
119+
if len(perms) == expected:
120+
break
121+
else:
122+
self.fail()
123+
124+
def test_sample_inputs(self):
125+
# SF bug #801342 -- population can be any iterable defining __len__()
126+
self.gen.sample(set(range(20)), 2)
127+
self.gen.sample(range(20), 2)
128+
self.gen.sample(range(20), 2)
129+
self.gen.sample(str('abcdefghijklmnopqrst'), 2)
130+
self.gen.sample(tuple('abcdefghijklmnopqrst'), 2)
131+
132+
def test_sample_on_dicts(self):
133+
self.assertRaises(TypeError, self.gen.sample, dict.fromkeys('abcdef'), 2)
134+
135+
def test_choices(self):
136+
import sys
137+
if sys.version_info.minor < 6:
138+
return
139+
140+
choices = self.gen.choices
141+
data = ['red', 'green', 'blue', 'yellow']
142+
str_data = 'abcd'
143+
range_data = range(4)
144+
set_data = set(range(4))
145+
146+
# basic functionality
147+
for sample in [
148+
choices(data, k=5),
149+
choices(data, range(4), k=5),
150+
choices(k=5, population=data, weights=range(4)),
151+
]:
152+
self.assertEqual(len(sample), 5)
153+
self.assertEqual(type(sample), list)
154+
self.assertTrue(set(sample) <= set(data))
155+
156+
def test_gauss(self):
157+
# Ensure that the seed() method initializes all the hidden state. In
158+
# particular, through 2.2.1 it failed to reset a piece of state used
159+
# by (and only by) the .gauss() method.
160+
161+
for seed in 1, 12, 123, 1234, 12345, 123456, 654321:
162+
self.gen.seed(seed)
163+
x1 = self.gen.random()
164+
y1 = self.gen.gauss(0, 1)
165+
166+
self.gen.seed(seed)
167+
x2 = self.gen.random()
168+
y2 = self.gen.gauss(0, 1)
169+
170+
self.assertEqual(x1, x2)
171+
self.assertEqual(y1, y2)
172+
173+
def test_bug_9025(self):
174+
# Had problem with an uneven distribution in int(n*random())
175+
# Verify the fix by checking that distributions fall within expectations.
176+
n = 100000
177+
randrange = self.gen.randrange
178+
k = sum(randrange(6755399441055744) % 3 == 2 for i in range(n))
179+
self.assertTrue(0.30 < k/n < .37, (k/n))

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
import com.oracle.graal.python.builtins.objects.module.PythonModule;
112112
import com.oracle.graal.python.builtins.objects.object.ObjectBuiltins;
113113
import com.oracle.graal.python.builtins.objects.object.PythonObject;
114+
import com.oracle.graal.python.builtins.objects.random.RandomBuiltins;
114115
import com.oracle.graal.python.builtins.objects.range.RangeBuiltins;
115116
import com.oracle.graal.python.builtins.objects.referencetype.ReferenceTypeBuiltins;
116117
import com.oracle.graal.python.builtins.objects.reversed.ReversedBuiltins;
@@ -240,6 +241,7 @@ public final class Python3Core implements PythonCore {
240241
new MathModuleBuiltins(),
241242
new MarshalModuleBuiltins(),
242243
new RandomModuleBuiltins(),
244+
new RandomBuiltins(),
243245
new TruffleCextBuiltins(),
244246
new WeakRefModuleBuiltins(),
245247
new ReferenceTypeBuiltins(),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public enum PythonBuiltinClassType {
7373
PMethod(com.oracle.graal.python.builtins.objects.method.PMethod.class, "method"),
7474
PNone(com.oracle.graal.python.builtins.objects.PNone.class, "NoneType"),
7575
PNotImplemented(com.oracle.graal.python.builtins.objects.PNotImplemented.class, "NotImplementedType"),
76+
PRandom(com.oracle.graal.python.builtins.objects.random.PRandom.class, "random"),
7677
PRange(com.oracle.graal.python.builtins.objects.range.PRange.class, "range"),
7778
PRangeIterator(com.oracle.graal.python.builtins.objects.iterator.PRangeIterator.class, "iterator"),
7879
PRangeReverseIterator(com.oracle.graal.python.builtins.objects.iterator.PRangeIterator.PRangeReverseIterator.class, "iterator"),

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MathModuleBuiltins.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -872,19 +872,24 @@ public double sin(double value) {
872872
}
873873
}
874874

875-
@Builtin(name = "log", fixedNumOfArguments = 1)
875+
@Builtin(name = "log", minNumOfArguments = 1, maxNumOfArguments = 2)
876876
@GenerateNodeFactory
877877
public abstract static class LogNode extends PythonBuiltinNode {
878878

879879
@Specialization
880-
public double log(int value) {
880+
public double log(int value, @SuppressWarnings("unused") PNone novalue) {
881881
return Math.log(value);
882882
}
883883

884884
@Specialization
885-
public double log(double value) {
885+
public double log(double value, @SuppressWarnings("unused") PNone novalue) {
886886
return Math.log(value);
887887
}
888+
889+
@Specialization
890+
public double log(int value, int base) {
891+
return Math.log(value) / Math.log(base);
892+
}
888893
}
889894

890895
@Builtin(name = "fabs", fixedNumOfArguments = 1)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PosixModuleBuiltins.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.io.InputStream;
3636
import java.io.InputStreamReader;
3737
import java.io.OutputStream;
38+
import java.math.BigInteger;
3839
import java.nio.ByteBuffer;
3940
import java.nio.channels.NonWritableChannelException;
4041
import java.nio.channels.SeekableByteChannel;
@@ -50,6 +51,7 @@
5051
import java.util.List;
5152
import java.util.Map;
5253
import java.util.Map.Entry;
54+
import java.util.Random;
5355
import java.util.Set;
5456

5557
import com.oracle.graal.python.builtins.Builtin;
@@ -894,4 +896,18 @@ int system(String cmd) {
894896
}
895897
}
896898
}
899+
900+
@Builtin(name = "urandom", fixedNumOfArguments = 1)
901+
@GenerateNodeFactory
902+
abstract static class URandomNode extends PythonBuiltinNode {
903+
@Specialization
904+
@TruffleBoundary
905+
PBytes urandom(int size) {
906+
// size is in bytes
907+
BigInteger bigInteger = new BigInteger(size * 8, new Random());
908+
// sign may introduce an extra byte
909+
byte[] range = Arrays.copyOfRange(bigInteger.toByteArray(), 0, size);
910+
return factory().createBytes(range);
911+
}
912+
}
897913
}

0 commit comments

Comments
 (0)