Random integers
Elixir doesn't have a module to generate random integers, but it can call underlying Erlang modules such as rand (nowadays, preferred over random) or crypto and can select random items from enumerables. This gives us some options to generate integers inside a range.
To generate integers between 0 and 255 (inclusive):
n = :rand.uniform(256) - 1
n = :crypto.rand_uniform(0, 256)
n = Enum.random(0..255)
Note that :rand.uniform(n)
returns integers so that 1 <= x <= n
- that's why we decrement the result. Also, the documentation for Enum.random states that it will efficiently "pick a random value between the range limits, without traversing the whole range".
You can also use Enum.random
with ranges or charlists to pick random code points from specific alphabets.
n = Enum.random(?a..?z)
n = Enum.random('0123456789abcdef')
Random strings
The next step is to use these building blocks to create a binary from a number of random code points using comprehensions:
s = for _ <- 1..10, into: "", do: <<Enum.random('0123456789abcdef')>>
Cryptographically secure strings
Sometimes there's a need for a cryptographically secure generator to create, for example, random passwords or authentication tokens.
If you simply need a sequence of random bytes, you can use strong_random_bytes from Erlang's crypto
module. It's also much faster than looping in Elixir using comprehensions.
s = :crypto.strong_rand_bytes(10)
If you need to use a specific alphabet, you can combine :crypto.rand_uniform
with Enum.at
:
symbols = '0123456789abcdef'
symbol_count = Enum.count(symbols)
s = for _ <- 1..10, into: "", do: <<Enum.at(symbols, :crypto.rand_uniform(0, symbol_count))>>
Benchmark
I've benchmarked these options using Benchee.
random_length = 10_000
Benchee.run(%{
"Enum.random comprehension" => fn ->
for _ <- 1..random_length, into: "", do: <<Enum.random(0..255)>>
end,
":rand.uniform comprehension" => fn ->
for _ <- 1..random_length, into: "", do: <<:rand.uniform(256) - 1>>
end,
":crypto.rand_uniform comprehension" => fn ->
for _ <- 1..random_length, into: "", do: <<:crypto.rand_uniform(0, 256)>>
end,
":crypto.strong_rand_bytes" => fn ->
:crypto.strong_rand_bytes(random_length)
end,
})
Which yielded these results:
Name ips average deviation median 99th %
:crypto.strong_rand_bytes 73695.16 0.0136 ms ±34.33% 0.0130 ms 0.0239 ms
:rand.uniform comprehension 488.88 2.05 ms ±10.39% 1.99 ms 2.62 ms
Enum.random comprehension 284.56 3.51 ms ±8.22% 3.53 ms 4.33 ms
:crypto.rand_uniform comprehension 44.04 22.71 ms ±2.16% 22.64 ms 24.54 ms
Comparison:
:crypto.strong_rand_bytes 73695.16
:rand.uniform comprehension 488.88 - 150.74x slower +2.03 ms
Enum.random comprehension 284.56 - 258.98x slower +3.50 ms
:crypto.rand_uniform comprehension 44.04 - 1673.55x slower +22.70 ms
Summary
Based on the benchmarks and thinking about readability, this is what I would use depending on what I need to generate:
- Binary strings:
:crypto.strong_rand_bytes
- Any other symbol alphabet:
Enum.random
- Passwords/tokens:
:crypto.rand_uniform
- Fast:
:rand.uniform
Top comments (0)