23

I would like to generate one or few random numbers separated by new line.

How this can be done?

  • 1
    Pure vim? Or, say, using :python? – muru Feb 14 '15 at 22:28
  • Pure vim preferred, but if it's not build-in, any easy memorable method is fine. – kenorb Feb 14 '15 at 22:36
20

There is no built-in facility for this, so you'll need to use something external.

UNIX Shell (/bin/sh)

Calling:

strings -n 1 < /dev/urandom | tr -d '[:space:]' | head -c15

with system() is a good way. You can get only numbers by replacing tr with grep:

strings -n 1 < /dev/urandom | grep -o '[[:digit:]]' | head -c15

You can use this in Vim like so:

:echo system("strings -n 1 < /dev/urandom | grep -o '[[:digit:]]'  | head -c15")

The number 15 is the amount of numbers you want (adjust accordingly). This should work on Linux, BSD, OSX, and other UNIX systems. It won't work on MS Windows.

Also see my weblog post "Generate passwords from the commandline" (there are a lot of bad solutions for this out there).

Ruby

Ruby is probably the next best choice, since Ruby scripting seems to be a bit more common than Python scripting. Getting a random number is easy:

:ruby puts Random.rand(10)

Or to get 15 numbers:

:ruby 15.times { puts Random.rand(10) }

Python

You can use the random module; to get a single number:

:py import random; print(random.randint(0, 9))

Or a 15 numbers:

:py import random
:py for i in range(0, 15): print(random.randint(0, 9))

This should work for both Python 2 & 3.

Windows PowerShell

You can use Get-Random to get a random number:

:echo system('Get-Random')

Windows cmd.exe

Windows 7 and later should ship with PowerShell, but if you want maximum compatibility you can use cmd.exe. It has a special variable %RANDOM%:

:echo system('@echo %RANDOM%')

Note: This is not very random! , it uses the time (!)


Note that you don't need to use the Ruby or Python bindings to use the Ruby or Python solutions; you could also create a separate script and call them with system("python -c '...'") (this does require that ruby/python is installed, obviously.

  • Instead of tr -d '[:space:]', perhaps tr -cd '[:digit:]' for the grep filter? – muru Feb 14 '15 at 23:33
  • @muru I'm not sure that will work on OSX, which is why I didn't use it... You also get the numbers seperated by a newline this way, like the OP asked.... – Martin Tournoij Feb 14 '15 at 23:35
  • If that's the case.... GNU beats BSD. :P – muru Feb 14 '15 at 23:36
  • One can also use the shell (system) for inputting random numbers, i.e. :r! hexdump -n $((3*4)) -e '"%d"' /dev/urandom, would generate 3 random signed integers. – HAL 9001 Feb 15 '15 at 7:13
  • 1
    @kenorb This will correctly use the shell for inputting 3 signed random integers: :r! hexdump -n $((3*4)) -e '"\%d\n"' /dev/urandom – HAL 9001 Feb 16 '15 at 17:49
12

Here is a pure vimscript solution. I did not create it, it was developed by Charles E. Campbell. You can find a Github repo with his code here.


The algorithm uses 3 seeds generated at Vim startup and generate a pseudo-random number based on calculations and permutations applied to the seeds:

" Randomization Variables
" with a little extra randomized start from localtime()
let g:rndm_m1 = 32007779 + (localtime()%100 - 50)
let g:rndm_m2 = 23717810 + (localtime()/86400)%100
let g:rndm_m3 = 52636370 + (localtime()/3600)%100

The variables scope is declared as global because they are used by the generator function but it could be restricted to the script (s:)

And here is the generator function:

function! Rndm()
    let m4= g:rndm_m1 + g:rndm_m2 + g:rndm_m3
    if( g:rndm_m2 < 50000000 )
        let m4= m4 + 1357
    endif
    if( m4 >= 100000000 )
        let m4= m4 - 100000000
        if( m4 >= 100000000 )
            let m4= m4 - 100000000
        endif
    endif
    let g:rndm_m1 = g:rndm_m2
    let g:rndm_m2 = g:rndm_m3
    let g:rndm_m3 = m4
    return g:rndm_m3
endfun

The repo includes the following functions:

  • A "randomish" initialization of the seeds;
  • A way to read some user-defined seed from a text file;
  • A pseudo random generator
  • Several random functions:
    • A function that generates a random number within a range
    • A dice rolling function
    • A function to randomly swap a list of sequential integers

Here is a quick test I wrote to test the generator: I generated 1000000 numbers between 0 and 9 and counted the number of occurrences of each number here are the results:

0 : 100409
1 : 99435
2 : 100433
3 : 99578
4 : 100484
5 : 99948
6 : 100394
7 : 99649
8 : 99803
9 : 99867

As you can see the generation seems to be well distributed. I'm aware that this is largely not enough to test a random generator so I'll try to make some extra analysis if I have some free time.

6

This is a method found in the vim-randomtag plugin, which is based on reading ... current time microseconds, usable when you just want some number, you don't care much about randomness quality, or have security concerns etc.:

function! s:randnum(max) abort
  return str2nr(matchstr(reltimestr(reltime()), '\v\.@<=\d+')[1:]) % a:max
endfunction
4

Vim doesn't offer native random generator, however if you have vim compiled with Python, the following method will append a random digit at the end of your line:

:py import vim, random; vim.current.line += str(random.randint(0, 9))

Note: To check if your vim supports Python, try: :echo has('python') (1 for yes).

You can also use shell which offers $RANDOM variable (works with bash/ksh/zsh) which returns a pseudorandom (0-32767), in example:

:r! echo $RANDOM

or:

:put =system('echo $RANDOM')

or:

:r! od -An -td -N1 /dev/urandom

On Windows, you've to have Cygwin/MSYS/SUA installed, or use %RANDOM% variable as Carpetsmoker suggested.

If you don't have access to shell and Python, as for workaround, you use last few digits from the current timestamp, in example:

:put =reltimestr(reltime())[-2:]

Note: If you're using it quite often, write a simple function which will return reltimestr(reltime())[-4:].

Note: Above methods returns only a pseudorandom integer which should not be used to generate an encryption key.


To add more random numbers please press @: to repeat the command again. Or prefix with number (like 10@:) to add much more of random numbers separated by new lines.


Related:

  • It also works with zsh, but not with sh, dash, fish, csh, or tcsh ... You could use :r! bash -c 'echo $RANDOM' ... – Martin Tournoij Feb 15 '15 at 23:24
  • "return last few digits from the current timestamp" -> This is not random. Using the time to get a pseudo-random number is almost always a bad idea, even for non-crypto purposes. What if the timestamp's resolution is in seconds and you create a "file.$random" twice in a second? Oops! ... Plus, you never know when someone's going to use your GetRandom() function where good prng does matter, so it's better to just get it right from the start if possible (and it's almost always possible here!) – Martin Tournoij Feb 16 '15 at 10:06
  • I don't know what the quality of $RANDOM is, but if this is a poor PRNG then that's not a reason to use an even poorer PRNG :-) Instead, upgrade to a better PRNG! As far as I know, /dev/urandom is available on all platforms where bash is commonly available, so I don't see a reason to not use it. – Martin Tournoij Feb 16 '15 at 10:59
  • 1
    I didn't know about $RANDOM. Seems like a very nice little tool, and even though it may be a "poor's man RNG" (as @Carpetsmoker points out), it definitely fits @kenorb's (who asked the question) requirement of "easily memorable". – Dalker Oct 7 '16 at 14:46
3

I don't think there's a native function for random numbers in Vimscript.

A way using :python (use it in a function, perhaps, with 10000 and 60 replaced by parameters):

:python <<EOF
import vim
import random

line = vim.current.window.cursor[0]
r = getattr(__builtins__, 'xrange', range) # To make it work for both Python 2 & 3
vim.current.buffer[line:line] = list(map(str, random.sample(r(10000), 60)))
EOF

See my answer to Making a box in vim via python for a quick intro on Python scripting in Vim.

Since vim.current.buffer is a list of strings, we can assign a list of strings to it the way we would in Python. random.sample is just the simplest way I can think of to get a list of random integers in Python.

  • @Carpetsmoker random.sample repuires only two arguments on Python 2, str is the builtin function to convert things to strings. Let me look up the Py3 equivalents (str would be the same, will have to check xrange and random.sample). – muru Feb 14 '15 at 23:47
  • @Carpetsmoker aargh, typo. The bracket after 60 is supposed to be after 10000. And xrange is not in Python3 because range is equivalent (neither actually constructs a list, unlike range in Py2). – muru Feb 14 '15 at 23:55
  • @Carpetsmoker And the final difference is that map in Py3 returns an iterable and not a list, so the last line would use list(map(str, random.sample(range(10000), 60))) – muru Feb 14 '15 at 23:58
  • Okay, I've taken the liberty to edit your post with some (minor) changes to make it compatible with both Python 2 and 3... – Martin Tournoij Feb 15 '15 at 0:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.