Thinking Erlang, or Creating a Random Matrix without Loops
In which I think like an functional programmer
February 26, 2009
programming
erlang
For a project, my Erlang implementation of a fast PFNET algorithm,
I needed to find a way to create a random matrix of integers (for
path weights), with the diagonal being filled with zeroes. I was
wondering how best to do that, and started off with two loops, an
inner one for each row, and an outer one for the full set of rows.
Then the problem was how to tell the inner loop at what position
the ‘0’ should be inserted. I was thinking about passing a row-ID,
when it suddenly clicked: lists:seq/2 was what I needed! This
method, which I previously thought was pretty useless, creates a
list with a sequence of numbers (the range is specified in the two
parameters). For example,
1> lists:seq(1,4).
[1,2,3,4]
2> lists:seq(634,637).
[634,635,636,637]
3> lists:seq(1000,1003).
[1000,1001,1002,1003]
Now I would simply generate a list with a number for each row, and then send the inner loop off to do its thing, filling the slot given by the sequence number with a zero, and others with a random value.
But now it gets even better. Using a separate (tail-)recursive
function for the inner loop didn’t quite seem right, so I thought
a bit more about it and came to the conclusion that this is simply
a mapping; mapping an integer to a list (a vector of numbers, one
of which (given by the integer) is a zero). So instead of using a
function for filling the row, I call lists:seq/2 again and then map
the whole thing. This is the final version I arrived at, and I’m
sure it can still be improved upon using list comprehensions:
random_matrix(Size, MaxVal) ->
random:seed(),
lists:map(
fun(X) ->
lists:map(
fun(Y) ->
case Y of
X -> 0;
_ -> random:uniform(MaxVal)
end
end,
lists:seq(1,Size))
end,
lists:seq(1,Size)).
This solution seems to be far more idiomatic, and I am beginning to think that I finally no longer think in an imperative way of loops, but more in the Erlang-way of list operations. Initially this is hard to achieve, but with any luck it will become a lot easier once one is used to it. Elegance, here I come!
Example run:
4> random_matrix(6,7).
[[0,1,4,6,7,4],
[3,0,5,7,5,4],
[5,1,0,2,5,2],
[4,2,4,0,3,1],
[4,4,3,3,0,1],
[5,7,3,2,2,0]]
Note: I have used random:seed/0 above, as I am happy for the function
to return identical matrices on subsequent runs with the same
parameters. To get truly random results, that would have to be left
out. However, for my benchmarking purposes it saved me having to
save the matrix to a file and read it in, as I can easily generate
a new copy of the same matrix I used before.