Skip to content

Commit e8bb4a1

Browse files
authored
add lib methods: random.randstr, fix typos (#45)
* draft for randstr * random value * case 1 * fix nil / none * for case cjk * rename to chars * Update docs * '我爱你'
1 parent bfd06b8 commit e8bb4a1

File tree

3 files changed

+162
-12
lines changed

3 files changed

+162
-12
lines changed

lib/random/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,30 @@ print(b)
2727
# Output: b'K\xaa\xbb4\xbaEh0\x19\x9c'
2828
```
2929

30+
### `randstr(chars, n)`
31+
32+
Generate a random string containing n number of unicode characters from the given unicode string.
33+
34+
#### Parameters
35+
36+
| name | type | description |
37+
|---------|----------|-----------------------------------------------------------------------------------------------|
38+
| `chars` | `string` | The characters to choose from. |
39+
| `n` | `int` | The length of the string. If n is non-positive or not supplied, a reasonable default is used. |
40+
41+
#### Examples
42+
43+
**basic**
44+
45+
Generate a random string containing 10 characters from the given unicode string.
46+
47+
```python
48+
load("random", "randstr")
49+
s = randstr("abcdefghijklmnopqrstuvwxyz", 10)
50+
print(s)
51+
# Output: "enfknqfbra"
52+
```
53+
3054
### `randint(a,b)`
3155

3256
Return a random integer N such that a <= N <= b.

lib/random/random.go

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func LoadModule() (starlark.StringDict, error) {
2828
Name: "random",
2929
Members: starlark.StringDict{
3030
"randbytes": starlark.NewBuiltin("random.randbytes", randbytes),
31+
"randstr": starlark.NewBuiltin("random.randstr", randstr),
3132
"randint": starlark.NewBuiltin("random.randint", randint),
3233
"choice": starlark.NewBuiltin("random.choice", choice),
3334
"shuffle": starlark.NewBuiltin("random.shuffle", shuffle),
@@ -40,6 +41,13 @@ func LoadModule() (starlark.StringDict, error) {
4041
return module, nil
4142
}
4243

44+
// for convenience
45+
var (
46+
emptyStr string
47+
none = starlark.None
48+
defaultLenN = big.NewInt(10)
49+
)
50+
4351
// randbytes(n) returns a random byte string of length n.
4452
func randbytes(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
4553
// precondition checks
@@ -50,7 +58,7 @@ func randbytes(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tupl
5058
// set default value if n is not provided correctly
5159
nInt := n.BigInt()
5260
if nInt.Sign() <= 0 {
53-
nInt = big.NewInt(10)
61+
nInt = defaultLenN
5462
}
5563
// get random bytes
5664
buf := make([]byte, nInt.Int64())
@@ -60,6 +68,29 @@ func randbytes(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tupl
6068
return starlark.Bytes(buf), nil
6169
}
6270

71+
// randstr(chars, n) returns a random string of given length from given characters.
72+
func randstr(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
73+
// precondition checks
74+
var (
75+
ab starlark.String
76+
n starlark.Int
77+
)
78+
if err := starlark.UnpackArgs(bn.Name(), args, kwargs, "chars", &ab, "n?", &n); err != nil {
79+
return nil, err
80+
}
81+
// set default value if n is not provided correctly
82+
nInt := n.BigInt()
83+
if nInt.Sign() <= 0 {
84+
nInt = defaultLenN
85+
}
86+
// get random strings
87+
s, err := getRandStr(ab.GoString(), nInt.Int64())
88+
if err != nil {
89+
return nil, err
90+
}
91+
return starlark.String(s), nil
92+
}
93+
6394
// randint(a, b) returns a random integer N such that a <= N <= b. Alias for randrange(a, b+1).
6495
func randint(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
6596
// precondition checks
@@ -92,16 +123,16 @@ func choice(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple,
92123
// precondition checks
93124
var seq starlark.Indexable
94125
if err := starlark.UnpackArgs(bn.Name(), args, kwargs, "seq", &seq); err != nil {
95-
return starlark.None, err
126+
return nil, err
96127
}
97128
l := seq.Len()
98129
if l == 0 {
99-
return starlark.None, errors.New(`cannot choose from an empty sequence`)
130+
return nil, errors.New(`cannot choose from an empty sequence`)
100131
}
101132
// get random index
102133
i, err := getRandomInt(l)
103134
if err != nil {
104-
return starlark.None, err
135+
return nil, err
105136
}
106137
// return element at index
107138
return seq.Index(i), nil
@@ -112,12 +143,12 @@ func shuffle(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple,
112143
// precondition checks
113144
var seq starlark.HasSetIndex
114145
if err := starlark.UnpackArgs(bn.Name(), args, kwargs, "seq", &seq); err != nil {
115-
return starlark.None, err
146+
return nil, err
116147
}
117148
// nothing to do if seq is empty or has only one element
118149
l := seq.Len()
119150
if l <= 1 {
120-
return starlark.None, nil
151+
return none, nil
121152
}
122153
// The shuffle algorithm is the Fisher-Yates Shuffle and its complexity is O(n).
123154
var (
@@ -137,27 +168,27 @@ func shuffle(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple,
137168
)
138169
for i := uint64(l - 1); i > 0; {
139170
if _, err := rand.Read(randBytes); err != nil {
140-
return starlark.None, err
171+
return nil, err
141172
}
142173
randBig.SetBytes(randBytes)
143174
for num := randBig.Uint64(); num > i && i > 0; i-- {
144175
max := i + 1
145176
j := int(num % max)
146177
num /= max
147178
if e := swap(int(i), j); e != nil {
148-
return starlark.None, e
179+
return nil, e
149180
}
150181
}
151182
}
152183
// done
153-
return starlark.None, nil
184+
return none, nil
154185
}
155186

156187
// random() returns a random floating point number in the range 0.0 <= X < 1.0.
157188
func random(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
158189
f, err := getRandomFloat(1 << 53)
159190
if err != nil {
160-
return starlark.None, err
191+
return nil, err
161192
}
162193
return starlark.Float(f), nil
163194
}
@@ -167,12 +198,12 @@ func uniform(thread *starlark.Thread, bn *starlark.Builtin, args starlark.Tuple,
167198
// precondition checks
168199
var a, b itn.FloatOrInt
169200
if err := starlark.UnpackArgs(bn.Name(), args, kwargs, "a", &a, "b", &b); err != nil {
170-
return starlark.None, err
201+
return nil, err
171202
}
172203
// get random float
173204
f, err := getRandomFloat(1 << 53)
174205
if err != nil {
175-
return starlark.None, err
206+
return nil, err
176207
}
177208
// a + (b-a) * random()
178209
diff := float64(b - a)
@@ -192,6 +223,7 @@ func getRandomInt(max int) (int, error) {
192223
return int(n.Int64()), nil
193224
}
194225

226+
// getRandomFloat returns a random floating point number in the range [0.0, 1.0).
195227
func getRandomFloat(prec int64) (n float64, err error) {
196228
if prec <= 0 {
197229
return 0, errors.New(`prec must be > 0`)
@@ -203,3 +235,31 @@ func getRandomFloat(prec int64) (n float64, err error) {
203235
}
204236
return float64(nBig.Int64()) / float64(prec), nil
205237
}
238+
239+
// getRandStr returns a random string of given length from given characters.
240+
func getRandStr(chars string, length int64) (string, error) {
241+
// basic checks
242+
if length <= 0 {
243+
return emptyStr, errors.New(`length must be > 0`)
244+
}
245+
if chars == emptyStr {
246+
return emptyStr, errors.New(`chars must not be empty`)
247+
}
248+
249+
// split chars into runes
250+
runes := []rune(chars)
251+
rc := len(runes)
252+
253+
// get random runes
254+
buf := make([]rune, length)
255+
for i := range buf {
256+
idx, err := getRandomInt(rc)
257+
if err != nil {
258+
return emptyStr, err
259+
}
260+
buf[i] = runes[idx]
261+
}
262+
263+
// convert to string
264+
return string(buf), nil
265+
}

lib/random/random_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,72 @@ func TestLoadModule_Random(t *testing.T) {
259259
assert.eq(len(x), 20)
260260
`),
261261
},
262+
{
263+
name: "randstr with less than 1 args",
264+
script: itn.HereDoc(`
265+
load('random', 'randstr')
266+
x = randstr()
267+
`),
268+
wantErr: `random.randstr: missing argument for chars`,
269+
},
270+
{
271+
name: "randstr with invalid args",
272+
script: itn.HereDoc(`
273+
load('random', 'randstr')
274+
x = randstr(123)
275+
`),
276+
wantErr: `random.randstr: for parameter chars: got int, want string`,
277+
},
278+
{
279+
name: "randstr with invalid N",
280+
script: itn.HereDoc(`
281+
load('random', 'randstr')
282+
x = randstr('abc', -2)
283+
assert.eq(len(x), 10)
284+
y = randstr('abc', 0)
285+
assert.eq(len(y), 10)
286+
`),
287+
},
288+
{
289+
name: "randstr with empty chars",
290+
script: itn.HereDoc(`
291+
load('random', 'randstr')
292+
x = randstr('', 10)
293+
`),
294+
wantErr: `chars must not be empty`,
295+
},
296+
{
297+
name: "randstr with n=1",
298+
script: itn.HereDoc(`
299+
load('random', 'randstr')
300+
x = randstr('我爱你', 1)
301+
assert.eq(len(x), 3)
302+
cs = ["我", "爱", "你"]
303+
assert.true(x in cs)
304+
print(x)
305+
`),
306+
},
307+
{
308+
name: "randstr with same chars",
309+
script: itn.HereDoc(`
310+
load('random', 'randstr')
311+
x = randstr('AAA', 10)
312+
assert.eq(len(x), 10)
313+
assert.eq(x, 'AAAAAAAAAA')
314+
`),
315+
},
316+
{
317+
name: "randstr for unicode",
318+
script: itn.HereDoc(`
319+
load('random', 'randstr')
320+
x = randstr('你好', 2)
321+
assert.eq(len(x), 6)
322+
cs = ["你好", "好你", "好好", "你你"]
323+
assert.true(x in cs)
324+
print(x)
325+
print(randstr("abcdefghijklmnopqrstuvwxyz", 10))
326+
`),
327+
},
262328
{
263329
name: "random",
264330
script: itn.HereDoc(`

0 commit comments

Comments
 (0)