Programming Noob
Programming Noob

Reputation: 1332

compare each row with each column in matrix using only pure python

I have a certain function that I made and I want to run it on each column and each row of a matrix, to check if there are rows and columns that produce the same output.

for example:

matrix = [[1,2,3],
          [7,8,9]]

I want to run the function, lets call it myfun, on each column [1,7], [2,8] and [3,9] separatly, and also run it on each row [1,2,3] and [7,8,9]. If there is a row and a column that produce the same result, the counter ct would go up 1. All of this is found in another function, called count_good, which basically counts rows and columns that produce the same result.

here is the code so far:

def count_good(mat):
    ct = 0
    for i in mat:
        for j in mat:
            if myfun(i) == myfun(j):
                ct += 1
    return ct
            

However, when I use print to check my code I get this:

mat = [[1,2,3],[7,8,9]]
​
for i in mat:
    for j in mat:
        print(i,j)
​
[1, 2, 3] [1, 2, 3]
[1, 2, 3] [7, 8, 9]
[7, 8, 9] [1, 2, 3]
[7, 8, 9] [7, 8, 9]

I see that the code does not return what I need' which means that the count_good function won't work. How can I run a function on each row and each column? I need to do it without any help of outside libraries, no map,zip or stuff like that, only very pure python.

Upvotes: 3

Views: 595

Answers (3)

gboffi
gboffi

Reputation: 25093

TL;DR
To get a column from a 2D list of N lists of M elements, first flatten the list to a 1D list of N×M elements, then choosing elements from the 1D list with a stride equal to M, the number of columns, gives you a column of the original 2D list.


First, I create a matrix of random integers, as a list of lists of equal length — Here I take some liberty from the objective of "pure" Python, the OP will probably input by hand some assigned matrix.

from random import randrange, seed
seed(20220914)
dim = 5
matrix = [[randrange(dim) for column in range(dim)] for row in range(dim)]
print(*matrix, sep='\n')

We need a function to be applied to each row and each column of the matrix, that I intend must be supplied as a list. Here I choose a simple summation of the elements.

def myfun(l_st):
    the_sum = 0
    for value in l_st: the_sum = the_sum+value
    return the_sum

To proceed, we are going to do something unexpected, that is we unwrap the matrix, starting from an empty list we do a loop on the rows and "sum" the current row to unwrapped, note that summing two lists gives you a single list containing all the elements of the two lists.

unwrapped = []
for row in matrix: unwrapped = unwrapped+row

In the following we will need the number of columns in the matrix, this number can be computed counting the elements in the last row of the matrix.

ncols = 0
for value in row: ncols = ncols+1

Now, we can compute the values produced applying myfunc to each column, counting how many times we have the same value.

We use an auxiliary variable, start, that is initialized to zero and incremented in every iteration of the following loop, that scans, using a dummy variable, all the elements of the current row, hence start has the values 0, 1, ..., ncols-1, so that unwrapped[start::ncols] is a list containing exactly one of the columns of the matrix.

count_of_column_values = {}
start = 0
for dummy in row:
    column_value = myfun(unwrapped[start::ncols])
    if column_value not in count_of_column_values:
        count_of_column_values[column_value] = 1
    else:
        count_of_column_values[column_value] = count_of_column_values[column_value] + 1
    start = start+1

At this point, we are ready to apply myfun to the rows

count = 0
for row in matrix:
    row_value = myfun(row)
    if row_value in count_of_column_values: count = count+count_of_column_values[row_value]
print(count)

Executing the code above prints

[1, 4, 4, 1, 0]
[1, 2, 4, 1, 4]
[1, 4, 4, 0, 1]
[4, 0, 3, 1, 2]
[0, 0, 4, 2, 2]
3

Upvotes: 0

Ryo Suzuki
Ryo Suzuki

Reputation: 132

Using native python:

def count_good(mat):
    ct = 0
    columns = [[row[col_idx] for row in mat] for col_idx in range(len(mat[0]))]
    for row in mat:
        for column in columns:
            if myfun(row) == myfun(column):
                ct += 1
    return ct

However, this is very inefficient as it is a triple nested for-loop. I would suggest using numpy instead.

e.g.

def count_good(mat):
    ct = 0
    mat = np.array(mat)
    for row in mat:
        for column in mat.T:
            if myfun(row) == myfun(column):
                ct += 1
    return ct

Upvotes: 1

Mad Physicist
Mad Physicist

Reputation: 114478

Let's start by using itertools and collections for this, then translate it back to "pure" python.

from itertools import product, starmap, chain # combinations?
from collections import Counter

To iterate in a nested loop efficiently, you can use itertools.product. You can use starmap to expand the arguments of a function as well. Here is a generator of the values of myfun over the rows:

starmap(myfun, product(matrix, repeat=2))

To transpose the matrix and iterate over the columns, use the zip(* idiom:

starmap(myfun, product(zip(*matrix), repeat=2))

You can use collections.Counter to map all the repeats for each possible return value:

Counter(starmap(myfun, chain(product(matrix, repeat=2), product(zip(*matrix), repeat=2))))

If you want to avoid running myfun on the same elements, replace product(..., repeat=2) with combinations(..., 2).

Now that you have the layout of how to do this, replace all the external library stuff with equivalent builtins:

counter = {}
for i in range(len(matrix)):
    for j in range(len(matrix)):
        result = myfun(matrix[i], matrix[j])
        counter[result] = counter.get(result, 0) + 1
for i in range(len(matrix[0])):
    for j in range(len(matrix[0])):
        c1 = [matrix[row][i] for row in range(len(matrix))]
        c2 = [matrix[row][j] for row in range(len(matrix))]
        result = myfun(c1, c2)
        counter[result] = counter.get(result, 0) + 1

If you want combinations instead, replace the loop pairs with

for i in range(len(...) - 1):
    for j in range(i + 1, len(...)):

Upvotes: 1

Related Questions