Finding and removing palindrome rows in 2D numpy array

Viewed 349

What would be pythonic and effective way to find/remove palindrome rows from matrix. Though the title suggests matrix to be a numpy ndarray, it can be pandas DataFrame if it lead to more elegant solution.

Obvious way would be to implement this using for-loop, but I'm interested is there a more effective and succint way.

My first idea was to concatenate rows and rows-inverse, and then extract duplicates from concatenated matrix. But this list of duplicates will contain both initial row and its inverse. So to remove second instance of a palindrome I'd still have to do some for-looping.

My second idea was to somehow use broadcasting to get cartesian product of rows and apply my own ufunc (perhaps created using numba) to get 2D bool matrix. But I don't know how to create ufunc that would get matrix axis, instead of scalar.

EDIT:

I guess I should apologize for poorly formulated question (English is not my native language). I don't need to find out if any row itself is palindrome, but if there are pairs of rows within matrix that are palindromes.

2 Answers

I simply check if the array is equal its reflection (around axis 1) in all elements, if true it is a palindrome (correct me if I am wrong). Then I index out the rows that aren't palindromes.

import numpy as np

a = np.array([
    [1,0,0,1], # Palindrome
    [0,2,2,0], # Palindrome
    [1,2,3,4],
    [0,1,4,0],
])

wherepalindrome = (a == a[:,::-1]).all(1)
print(a[~wherepalindrome])
#[[1 2 3 4]
# [0 1 4 0]]

Naphat's answer is the pythonic (numpythonic) way to go. That should be the accepted answer.

But if your array is really large, you don't want to create a temporary copy, and you wish to explore Numba's intricacies, you can use something like this:

import numba as nb

@nb.njit(parallel=True)
def palindromic_rows(a):
    rows, cols = a.shape
    palindromes = np.full(rows, True, dtype=nb.boolean)
    mid = cols // 2
    for r in nb.prange(rows):   # <-- parallel loop
        for c in range(mid):
            if a[r, c] != a[r, -c-1]:
                palindromes[r] = False
                break
    return palindromes

This contraption just replaces the elegant (a == a[:,::-1]).all(axis=1), but it's almost an order of magnitude faster for very large arrays and it doesn't duplicate them.

Related