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).
2> lists:seq(634,637).    
3> lists:seq(1000,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) ->
    fun(X) ->
          fun(Y) ->
              case Y of 
                 X -> 0; 
                 _ -> random:uniform(MaxVal)

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).  

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.