Skip to content

Commit aa02b75

Browse files
committed
Improved random.randstr (preimport)
1 parent a9ddb6a commit aa02b75

4 files changed

Lines changed: 50 additions & 18 deletions

File tree

docs/pages/enhancements.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ A context manager is also available:
8282

8383
`itertools` is extended with the following items:
8484

85-
- `product2`: this is an improvement of the original `product`, also handling generators
86-
- `reset`: given a generator function decorated by `resettable`, this functions can reset a generator instantiated by this function
87-
- `resettable`: decorator for registering the reference to the generator function and its arguments used to make a generator, then making resettable each generator made by this function
88-
- `NonResettableGeneratorException`: specific exception for handling a generator not decorated by `resettable` thrown while trying to reset it with the `reset` function
85+
- `product2`: this is an improvement of the original `product`, also handling generators.
86+
- `reset`: given a generator function decorated by `resettable`, this functions can reset a generator instantiated by this function.
87+
- `resettable`: decorator for registering the reference to the generator function and its arguments used to make a generator, then making resettable each generator made by this function.
88+
- `NonResettableGeneratorException`: specific exception for handling a generator not decorated by `resettable` thrown while trying to reset it with the `reset` function.
8989

9090
-----
9191

@@ -114,8 +114,8 @@ A context manager is also available:
114114

115115
`random` is slightly enhanced with a few new items:
116116

117-
- `choice`: redefined to add an argument for an exclusion list (aim is to provide a short form instead of using list comprehension) and an extra argument for setting if an error shall be thrown when the resulting list is empty
118-
- `randstr`: allows to generate a random string with a given length (8 by default) and alphabet
117+
- `choice`: redefined to add an argument for an exclusion list (aim is to provide a short form instead of using list comprehension) and an extra argument for setting if an error shall be thrown when the resulting list is empty.
118+
- `randstr`: allows to generate a random string with a given length (8 by default) and alphabet ; it also supports a 'balance' parameter that ensures that there is no character that can have more than n / (n_alphabet - 1) occurrences and a 'blocksize' parameter to enforce balancing on a per-block basis.
119119
- `LFSR`: adds an implementation of the Linear-Feedback Shifting Register stream generator, with the possibility of recovering its parameters by setting a target and using the Berlekamp-Massey algorithm.
120120
- `Geffe`: adds an implementation of the Geffe stream generator.
121121

@@ -125,10 +125,10 @@ A context manager is also available:
125125

126126
`re` is enhanced with some new (fully lazy) functions to generate strings from regular expression patterns:
127127

128-
- `randstr`: generates a single random string from the input regex
129-
- `randstrs`: provides a generator of N random strings from the input regex
130-
- `size`: computes the number of all possible strings from the input regex
131-
- `strings`: generates all possible strings from the input regex
128+
- `randstr`: generates a single random string from the input regex.
129+
- `randstrs`: provides a generator of N random strings from the input regex.
130+
- `size`: computes the number of all possible strings from the input regex.
131+
- `strings`: generates all possible strings from the input regex.
132132

133133
-----
134134

@@ -137,8 +137,8 @@ A context manager is also available:
137137
`string` is slightly enhanced with a few new functions:
138138

139139
- `shorten`: shortens a string, taking by default the terminal width, otherwise a length of 40 characters (unless user-defined), and using an end token (by default "`...`").
140-
- `sort_natural`: sort a list of strings taking numbers into account (returns nothing)
141-
- `sorted_natural`: return a list of strings taking numbers into account
140+
- `sort_natural`: sort a list of strings taking numbers into account (returns nothing).
141+
- `sorted_natural`: return a list of strings taking numbers into account.
142142

143143
-----
144144

src/tinyscript/VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.30.2
1+
1.30.4

src/tinyscript/preimports/rand.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,39 @@ def __choice(lst, exclusions=(), error=True):
1919
random.choice = __choice
2020

2121

22-
def __randstr(n=8, alphabet=string.ascii_lowercase+string.ascii_uppercase+string.digits):
23-
""" Compose a random string of the given length with the given alphabet. """
22+
def __randstr(n=8, alphabet=string.ascii_lowercase+string.ascii_uppercase+string.digits, balance=False, blocksize=0):
23+
""" Compose a random string of the given length with the given alphabet. It can be chosen if it has to be balanced,
24+
either in its whole or per block (given a block size). Note that, when balancing per block, it is not ensured
25+
that the whole string is balanced too. """
26+
na = len(alphabet)
2427
if n < 0:
2528
raise ValueError("Bad random string length")
26-
if len(alphabet) == 0:
29+
if na == 0:
2730
raise ValueError("Bad alphabet")
28-
s = ""
31+
is_b = isinstance(alphabet, bytes)
32+
s = ["", b""][is_b]
33+
if is_b:
34+
alphabet = [alphabet[i:i+1] for i in range(na)]
35+
if balance:
36+
bs = min(n, blocksize) or n
37+
t = bs / (na-1 or 1)
38+
if bs <= (na-1)/(1-(na-1)/na):
39+
t = bs / (na-2)
40+
orig_alphabet = alphabet[:] if is_b else alphabet
2941
for i in range(n):
30-
s += random.choice(alphabet)
42+
if balance:
43+
if i == 0 or blocksize > 0 and i % blocksize == 0:
44+
alphabet, cnts = orig_alphabet[:] if is_b else orig_alphabet, {}
45+
while cnts.get(c := random.choice(alphabet), 0) >= t - 1:
46+
if is_b:
47+
alphabet.remove(c)
48+
else:
49+
alphabet = alphabet.replace(c, "")
50+
cnts.setdefault(c, 0)
51+
cnts[c] += 1
52+
else:
53+
c = random.choice(alphabet)
54+
s += c
3155
return s
3256
random.randstr = __randstr
3357

tests/test_preimports_random.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"""Preimports random assets' tests.
33
44
"""
5+
from collections import Counter
6+
from math import log
57
from tinyscript.preimports import random
68

79
from utils import *
@@ -19,6 +21,12 @@ def test_utility_functions(self):
1921
self.assertNotIn("e", random.randstr(alphabet="abcd"))
2022
self.assertRaises(ValueError, random.randstr, -1)
2123
self.assertRaises(ValueError, random.randstr, 8, "")
24+
self.assertTrue(isinstance(random.randstr(16, b"\x00\x01\x02"), bytes))
25+
for n, na in zip([8, 16, 64], [3, 4, 5]):
26+
for bs in [0, 16, 256]:
27+
for i in range(512):
28+
self.assertLess(max(Counter(random.randstr(n, "".join(chr(i) for i in range(na)), True, bs))\
29+
.values()), n/(na-1) if (bs or n) > (na-1)/(1-(na-1)/na) else n/(na-2))
2230

2331
def test_random_lfsr(self):
2432
l = random.LFSR(target="0123456789abcdef")

0 commit comments

Comments
 (0)