Search code examples
pythonmultidimensional-arrayfunctional-programmingstateless

Changing nested element in matrix only using constants python


Hi I was working with a matrix in python call it a :

    a = [
         [0,0,0],
         [0,0,0],
         [0,0,0]
        ]

I would like to change the element on the second row in the first column (a[1][0]) to 1 yielding the following result :

    a = [
         [0,0,0],
         [1,0,0],
         [0,0,0]
        ]

You can of course accomplish this easily with :

    a[1][0] = 1

Unfortunately I'm a loony who would like to accomplish this purely functional :) The conditions being :

  1. No variables state ever gets changed and you should be able to replace all variable with constants.
  2. No state full operators such as for in are used.
  3. The result of the variable a with a changed element is stored in a second variable b without changing a.
  4. The solution should not use any imports or dependencies.

The wished for result should look something like this :

    a = [
         [0,0,0],
         [0,0,0],
         [0,0,0]
        ]
    
    b = someOperation(a)
    
    assert a == [[0,0,0],[0,0,0],[0,0,0]]
    assert b == [[0,0,0],[1,0,0],[0,0,0]]
    # the above asserts should not trigger

Does anyone know a (purely functional) solution to my problem? Thanks in advance.


Solution

  • If you want the purely functional way of doing something like this, you may want to consider how to do it in Haskell - the gold standard of functional programming.

    For small, ad-hoc problems, if you need to access elements by index, you'd typically zip your data sequence with a number generator:

    ghci> zip [0..] "abc"
    [(0,'a'),(1,'b'),(2,'c')]
    

    You can do the same in Python:

    from itertools import count
    
    def index_list(lst):
        return zip(count(), lst)
    

    Using it on 'abc':

    >>> list(index_list('abc'))
    [(0, 'a'), (1, 'b'), (2, 'c')]
    

    (I'm only using list in the above example in order to show the result.)

    Likewise, you can index a nested list, which includes your matrix:

    def index_matrix(matrix):
        return index_list(map(index_list, matrix))
    

    You now have an map of a map of tuples, where the first element of the tuple is the row or column index, and the second element is the value that was indexed.

    We can do the same in Haskell, with the OP's input:

    ghci> fmap (fmap (zip [0..])) $ zip [0..] [[0,0,0],[0,0,0],[0,0,0]]
    [(0,[(0,0),(1,0),(2,0)]),(1,[(0,0),(1,0),(2,0)]),(2,[(0,0),(1,0),(2,0)])]
    

    The index_matrix Python function produces conceptually the same shape of output, but made up from zip and map objects.

    Since index_matrix has indexed both rows and columns, you can now iterate through the map of maps and replace the value at a particular row and column:

    def replace_in_matrix(matrix, row, col, value):
        return map(lambda r:
            map(lambda c:
                value if r[0] == row and c[0] == col else c[1], r[1]), index_matrix(matrix))
    

    Try it out on the OP matrix:

    >>> m = [[0,0,0],[0,0,0],[0,0,0]]                                         
    >>> result = replace_in_matrix(m, 1, 0, 1)
    >>> list(map(list, result))
    [[0, 0, 0], [1, 0, 0], [0, 0, 0]]
    >>> m
    [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
    

    This also shows that m remains unmodified.

    The ad-hoc projection in Haskell is similar:

    ghci> fmap (fmap snd)
          $ fmap snd
          $ fmap (\(i, rs) -> (i, fmap (\(j,  x) -> (j, if i == 1 && j == 0 then 1 else x)) rs))
          $ fmap (fmap (zip [0..]))
          $ zip [0..] [[0,0,0],[0,0,0],[0,0,0]]
    [[0,0,0],[1,0,0],[0,0,0]]
    

    (I've inserted some line breaks for slightly improved readability.)