Changing nested element in matrix only using constants python

Question:

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.

Asked By: Jip Helsen

||

Answers:

Using a generator function to iterate through the container elements while performing a deep copy. NOTE: The do_deep_copy function is a simple implementation that will only handle lists of integers.

def generator_for_container(container):
    for element in container:
        yield element

def do_deep_copy(container):
    if isinstance(container, list):
        result = []
        elements = generator_for_container(container)
        for item in elements:
            result.append(do_deep_copy(item))
    elif isinstance(container, int):
        result = container
    else:
        raise ValueError("Unexpected type encountered in 'do_deep_copy'.")
    return result

def someOperation(container):
    new_container = do_deep_copy(container)
    new_container[1][0] = 1
    return new_container

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]]
Answered By: Dan Nagle

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

Answered By: Mark Seemann

If for loops were allowed, I would do this:

For completion sake I’m also deep copying tuples, but that may not be
necessary if you don’t have references inside and you don’t unpack and repack
them

def deepcopy(obj):
    obj_type = type(obj)
    if obj_type == list:
        return [deepcopy(value) for value in obj]
    if obj_type == dict:
        return  {key:deepcopy(value) for key, value in obj.items()}
    if obj_type == tuple:
        return tuple(deepcopy(value) for value in obj)
    return obj
def someOperation(obj):
    obj = deepcopy(obj)
    obj[1][0] = 1
    return obj

a = {
    1: {
        0: 0
    }
}
b = someOperation(a)
assert a == {1: {0: 0}}
assert b == {1: {0: 1}}
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]]

But since they’re not, I need to use map:

def deepcopy(obj):
    obj_type = type(obj)
    if obj_type == list:
        return list(map(deepcopy, obj))
    if obj_type == dict:
        return dict(map(deepcopy, obj.items()))
    if obj_type == tuple:
        return tuple(map(deepcopy, obj))
    return obj
def someOperation(obj):
    obj = deepcopy(obj)
    obj[1][0] = 1
    return obj

a = {
    1: {
        0: 0
    }
}
b = someOperation(a)
assert a == {1: {0: 0}}
assert b == {1: {0: 1}}
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]]
Answered By: Nineteendo