4

I am trying to generate a matrix A == [f(i,j) for i,j in range(0,n)]. The matrix is symmetric (f(i,j) == f(j,i)) and the diagonal elements are zero (f(i,i) == 0).

Question: Is it possible to produce a list comprehension that generates this matrix but that only calls the function f(i,j) for i < j?

Not my question: Generate the symmetric matrix in some other way.


Possible solution: Make the call to f through an auxiliary function g that saves the value of f in an additional storage or returns the stored value.

Would it be possible to solve avoiding the additional storage? I am not sure if this additional handicap implies that a reference to the list comprehension from itself (which I read doesn't exist in Python) is necessary, hopefully I am missing some other trick.

wwl
  • 2,025
  • 2
  • 30
  • 51
myfirsttime1
  • 287
  • 2
  • 12
  • This might give you a good starting point: http://stackoverflow.com/questions/6981717/pythonic-way-to-combine-for-loop-and-if-statement – BrandonM Oct 23 '16 at 13:46
  • @BrandonM Yes, it is easy to make an upper triangular matrix using list comprehension, have the zero in the diagonal too. – myfirsttime1 Oct 23 '16 at 13:48

2 Answers2

2

Assuming you really need the full matrix, in spite of its being symmetric, you could do this.

Create the matrix A first. (I know, this means you're not generating it in a list comprehension.) Here I define a 'dummy' function f and assume a value of 3 for n.

>>> n=3
>>> subs=((i,j) for i in range(n) for j in range(3) if i<=j)
>>> for i,j in subs:
...     i,j
...     
(0, 0)
(0, 1)
(0, 2)
(1, 1)
(1, 2)
(2, 2)
>>> def f(i,j):
...     return
... 
>>> def g(i,j):
...     if i==j:
...         A[i,i] = 0
...     else:
...         val=f(i,j)
...         A[i,j]=val
...         A[j,i]=val

If I've understood you correctly, there's no need for extra storage. Call g in a list comprehension that exercises subs.

myfirsttime1
  • 287
  • 2
  • 12
Bill Bell
  • 21,021
  • 5
  • 43
  • 58
1

If you want the function to execute only when i < j, you can use lru_cache (@lru_cache) to save the results of the function in cache, and use it directly without re-calculating it when i > j.

from typing import Any
from functools import lru_cache

@lru_cache()
def f(i: int, j: int) -> Any:
    return i * (-j)

n = 5
m = [f(i, j) if i < j else f(j, i) if i > j else 0 for i in range(n) for j in range(n)]

for i in range(0, n * n, n):
    print(m[i: i + n])

Results in

[0, 0, 0, 0, 0]
[0, 0, -2, -3, -4]
[0, -2, 0, -6, -8]
[0, -3, -6, 0, -12]
[0, -4, -8, -12, 0]
Thomas Perrot
  • 376
  • 3
  • 8