efficient way of padding an array

Viewed 328

I was wondering if there is an efficient method to pad an array in python, without using numpy.pad()

I know a way that uses nested for loops, but I wanted to know if there's a faster method?

Input:

row padding on top- 2
column padding from left - 1
1 2 3
4 5 6
7 8 9

Output

0 0 0 0
0 0 0 0
0 1 2 3
0 4 5 6
0 7 8 9

what I've done

y = [[1,2,3],[4,5,6],[7,8,9]]

topPadding = 2
leftPadding = 1
noOfRows = len(y)+topPadding
noOfCols = len(y)+leftPadding

x = [[0 for i in range(noOfCols)] for j in range(noOfRows)]

for i in range(topPadding,noOfRows):
    for j in range(leftPadding,noOfCols):
        x[i][j] = y[i-topPadding][j-leftPadding]
    print()
        
print(x)

Output

[[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 2, 3], [0, 4, 5, 6], [0, 7, 8, 9]]
2 Answers

A solution which uses the list concatenation and repetition operators:

def concat(x, top, left):
    n = len(x[0])
    return [[0]*(n + left - len(row)) + row for row in [[]]*top + x]

Here are some very basic timing results using your nested for loop solution versus my concatenation solution on a 10000x10000 matrix of random digits:

nested: 122.26 s
concat: 5.66 s

And the code for the test:

import timeit
from random import randint


def concat(x, top, left):
    n = len(x[0])
    return [[0]*(n + left - len(row)) + row for row in [[]]*top + x]


def nested(x, topPadding, leftPadding):
    noOfRows = len(x)+topPadding
    noOfCols = len(x)+leftPadding

    z = [[0 for i in range(noOfCols)] for j in range(noOfRows)]

    for i in range(topPadding,noOfRows):
        for j in range(leftPadding,noOfCols):
            z[i][j] = x[i-topPadding][j-leftPadding]

    return z


test = [[randint(0, 9) for _ in range(10000)] for _ in range(10000)]

t1 = timeit.timeit(
    "nested(test, 4, 2)",
    number=10,
    globals=globals()
)

t2 = timeit.timeit(
    "concat(test, 4, 2)",
    number=10,
    globals=globals()
)

print(nested(test, 4, 2) == concat(test, 4, 2))
print(f"nested: {t1:.2f} s")
print(f"concat: {t2:.2f} s")

Full output:

True
nested: 122.26 s
concat: 5.66 s

A modified version in which you input the desired height and width:

def concat(x, h, w):
    H = h - len(x)
    return [[0]*(w - len(row)) + row for row in [[]]*H + x]

Another version which allows padding to the north, south, east, and west:

def nsew_concat(x, N, S, E, W):
    """Pad x with zeros to the north, south, east, and west."""
    k = len(x[0])
    stack = [[]]*N + x + [[]]*S
    return [([0]*W + [0]*(k - len(row)) + row + [0]*E) for row in stack]

This will work for any (non-empty) rectangular matrix, but not for jagged arrays (where each row doesn't have the same length).

def pad_matrix(matrix, element=0, *, left=0, top=0):
    full_width = left + len(matrix[0])
    e = [element]
    return [
        *(e * full_width for i in range(top)),
        *(e * left + row for row in matrix),
    ]

Here's a version allowing padding on all four sides:

def pad_matrix(matrix, element=0, *, left=0, top=0, right=0, bottom=0):
    full_width = left + len(matrix[0]) + right
    e = [element]
    return [
        *(e * full_width for i in range(top)),
        *(e * left + row + e * right for row in matrix),
        *(e * full_width for i in range(bottom)),
    ]

Usage is like pad_matrix(matrix, left=1, top=2).

Related