numpy vectorized: проверьте, заканчиваются ли строки в массиве строками в другом массиве

У меня есть два массива, содержащие строки. Для каждой строки в одном массиве я хочу проверить, заканчивается ли она строками во втором массиве.

Вход:

strings = ['val1', 'val2', 'val3']
ends = ['1', '2', 'al1']

Желаемый результат:

[[ True, False,  True],
 [False,  True, False],
 [False, False, False]]

Поскольку val1 заканчивается на 1, а также на al1, оба (0,0) и (0,2) верны.

У меня есть следующий рабочий код:

import numpy as np

strings = ['val1', 'val2', 'val3']
ends = ['1', '2', 'al1']

def buildFunction(ending):
    return lambda x: x.endswith(ending)

funcs = list(map(buildFunction, ends))

def end_function_vector(val):
    return np.vectorize(lambda f, x: f(x))(funcs, np.repeat(val, len(funcs)))

result = np.array(list(map(end_function_vector, strings)))

И он возвращает желаемый результат.

Однако для больших массивов (~109 выходных элементов) map в последней строке занимает довольно много времени, поскольку np.vectorize и map в значительной степени являются просто оболочкой цикла for. Кто-нибудь знает более быстрый векторный метод для этого?


person Swier    schedule 12.06.2017    source источник


Ответы (2)


У Numpy есть такие операции для массивов символов: numpy.core.defchararray.endswith().

Следующий фрагмент кода немного ускоряет работу, но требует много памяти, поскольку вы создаете два массива того же размера, что и ваш выходной массив:

A = np.array(['val1', 'val2', 'val3'])
B = np.array(['1', '2', 'al1'])

A_matrix = np.repeat(A[:, np.newaxis], len(B), axis=1)
B_matrix = np.repeat(B[:, np.newaxis], len(A), axis=1).transpose()

result = np.core.defchararray.endswith(A_matrix, B_matrix)

Обновление:
Как заметил Дивакар, приведенный выше код можно объединить в:

A = np.array(['val1', 'val2', 'val3'])
B = np.array(['1', '2', 'al1'])

np.core.defchararray.endswith(A[:,None], B)
person Swier    schedule 12.06.2017
comment
Думаю, вы можете просто сделать: np.core.defchararray.endswith(A[:,None], B) и у нас есть победитель! - person Divakar; 12.06.2017
comment
Отличная реализация! Он работает в 4-5 раз быстрее, чем база OP. - person Ébe Isaac; 12.06.2017

Вот почти* векторизованный подход с использованием NumPy broadcasting-

# Get lengths of strings in each array
lens_strings = np.array(list(map(len,strings)))
lens_ends = np.array(list(map(len,ends)))

# Get the right most index of match, add the ends strings.
# The matching ones would cover the entire lengths of strings.
# So, do a final comparison against those lengths.
rfind = np.core.defchararray.rfind
out = rfind(strings[:,None], ends) + lens_ends == lens_strings[:,None]

Пробный запуск -

In [224]: strings = np.array(['val1', 'val2', 'val3', 'val1y', 'val341'])
     ...: ends = np.array(['1', '2', 'al1', 'l2'])
     ...: 

In [225]: out
Out[225]: 
array([[ True, False,  True, False],
       [False,  True, False,  True],
       [False, False, False, False],
       [False, False, False, False],
       [ True, False, False, False]], dtype=bool)

*Почти из-за использования map, но поскольку мы используем его только для получения длин строк входных элементов, его стоимость должна быть минимальной по сравнению с другими операциями, необходимыми для решения нашего случая.

person Divakar    schedule 12.06.2017
comment
Хорошо, но это занимает в два раза больше места в рабочей памяти. - person Ébe Isaac; 12.06.2017
comment
@AndrewJaffe Я думаю, что OP имел в виду векторизованный. Параллельность означала бы сохранение Python, но использование параллельных архитектур. Вопрос не похож. - person Divakar; 12.06.2017
comment
@ÉbeIsaac Только один раз, потому что мы добавляем к индексам, полученным из rfind. - person Divakar; 12.06.2017
comment
Я действительно имел в виду векторизованный вместо параллельного, я обновил вопрос. - person Swier; 12.06.2017
comment
Я понял крутой трюк, просто хотел получить хедз-ап на ~10^9. Дублирование такого размера нельзя воспринимать легкомысленно. Но опять же, так происходит компромисс между размером и памятью в алгоритмах. - person Ébe Isaac; 12.06.2017