Грамматика комбинаций массивов Numpy

Для конкретного приложения я создаю графический интерфейс для управления некоторыми данными (внутренне: одномерные массивы numpy) и рисую их график.

Конечный пользователь может выбрать в пользовательском интерфейсе возможность построения различных серий a, b, c.

Теперь мне также нужно разрешить «пользовательскую комбинацию» a, b, c. Точнее, пользователь (который не знает Python/Numpy, но может выучить несколько ключевых слов) должен ввести в текстовое поле графического интерфейса «формулу», а затем моя программа должна транскрибировать ее в реальный код numpy (вероятно, с использованием eval(...), здесь мало проблем с безопасностью, поскольку конечный пользователь является единственным пользователем), и отобразите данные.

Примеры ввода конечного пользователя:

a * 3 + 1.234 * c - d
a + b.roll(2)
a + b / b.max() * a.max()

Например, разрешенный синтаксис: базовая арифметика (+ * - / и круглые скобки), числа с плавающей запятой, a.max() и a.roll(3) для сдвига массивов.

Вопрос: есть ли внутри Numpy или Scipy функция, позволяющая интерпретировать комбинации массивов с помощью базовой арифметической грамматики?

Как насчет библиотеки numexpr? В стандартной библиотеке также есть eval().

Nick ODell 18.03.2024 16:05

Я бы посмотрел на модуль «ast» абстрактного синтаксического дерева в Python и настроил проверку грамматики в соответствии с вашими требованиями.

SteelFeather 18.03.2024 16:33

Вы говорите, что можете принять a.max(), но ваш пример ввода пользователя показывает max(a). Какой из них действителен?

Booboo 27.03.2024 12:03

Рассматривали ли вы возможность использования Jupyter Notebook, предоставив базовый шаблон и добавив необходимый графический интерфейс с помощью ipywidgets. Таким образом пользователь получит доступ к стандартной библиотеке Numpy.

igrinis 27.03.2024 13:02

@igrinis Нет, Jupyter / ipywidgets выходит за рамки проекта.

Basj 27.03.2024 14:08
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
5
143
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

import numpy as np
import numexpr as ne

a = np.random.rand(10)
b = np.random.rand(10)
c = np.random.rand(10)
d = np.random.rand(10)

ne.evaluate("a * 3 + 1.234 * c - d")

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

import numpy as np
import numexpr as ne
import re

a = np.random.rand(10)
b = np.random.rand(10)
c = np.random.rand(10)
d = np.random.rand(10)

def expression_eval(
    expression:str, a:np.array, b: np.array, c:np.array, d:np.array
) -> np.array:

    #Snippet to manage max values:
    a_max = a.max()
    b_max = b.max()
    c_max = c.max()
    d_max = d.max()

    for label in ["a", "b", "c", "d"]:
        expression = expression.replace(f"{label}.max()", f"{label}_max")

    #Snippet to manage rolling windows:
    pattern = r'(\w)\.roll\((\d+)\)'

    matches = re.findall(pattern, expression)
    if matches: roll_results = [(match[0], int(match[1])) for match in matches]
    else: roll_results = []

    rolls = {}

    for arr, window in roll_results:
        expression = expression.replace(f"{arr}.roll({window})", f"{arr}_roll_{window}")
        rolls[f"{arr}_roll_{window}"] = np.concatenate([
            vars()[arr][window:],
            np.zeros(window)
        ])

    return ne.evaluate(expression, global_dict=rolls)

#Evaluation:

expression_1 = "a * 3 + 1.234 * c - d"
expression_2 = "a + b / b.max() * a.max()"
expression_3 = "a + b.roll(3) + c.roll(2) + d.roll(4)"

print(f"{expression_1}\n{expression_eval(expression_1, a, b, c, d)}\n")
print(f"{expression_2}\n{expression_eval(expression_2, a, b, c, d)}\n")
print(f"{expression_3}\n{expression_eval(expression_3, a, b, c, d)}\n")

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

Обновлено(30.03.2024)

@cards спросил в комментариях, действительно ли этот код может обрабатывать некоторые вложенные выражения. Ответ: не может. Однако мы можем расширить этот базовый прототип для обработки более сложных выражений, таких как выражение_4. Библиотека numexpr уже обрабатывает вложенность алгебраических выражений, и мы можем разрешить некоторые дополнительные возможности вложенности, такие как получение максимального, минимального и рулонов пользовательских выражений путем предварительного вычисления вложенных выражений, замены их в конечном выражении и передачи значения тега в окончательная оценка.

import numpy as np
import numexpr as ne
import re

a = np.random.rand(10)
b = np.random.rand(10)
c = np.random.rand(10)
d = np.random.rand(10)

def expression_eval(
    expression:str, a:np.array, b: np.array, c:np.array, d:np.array
) -> np.array:
    
    variable_dict = {"a":a, "b":b, "c":c, "d":d}
    
    #Snippet to evaluate inner algebraic expressions:
    pattern = r'\(.*?\)(?:\.max\(\)|\.min\(\)|\.roll\(.*\))'
    matches = list(set(re.findall(pattern, expression)))

    for expr_ind, match in enumerate(matches):
        expression = re.sub(re.escape(match), f"expr_{expr_ind}", expression)
        variable_dict[f"expr_{expr_ind}"] = ne.evaluate(expr_ind)

    #Snippet to manage max values:
    pattern = r'(\w)\.max\(\)'
    matches = re.findall(pattern, expression)

    for match in matches:
        expression = expression.replace(f"{match}.max()", f"{match}_max")
        variable_dict[f"{match}_max"] = variable_dict[match].max()

    #Snippet to manage min values:
    pattern = r'(\w)\.min\(\)'
    matches = re.findall(pattern, expression)

    for match in matches:
        expression = expression.replace(f"{match}.max()", f"{match}_max")
        variable_dict[f"{match}_max"] = variable_dict[match].max()
        
    #Snippet to manage rolling windows:
    pattern = r'(\w)\.roll\((\d+)\)'

    matches = re.findall(pattern, expression)
    if matches: roll_results = [(match[0], int(match[1])) for match in matches]
    else: roll_results = []

    for arr, window in roll_results:
        expression = expression.replace(f"{arr}.roll({window})", f"{arr}_roll_{window}")
        variable_dict[f"{arr}_roll_{window}"] = np.concatenate([
            vars()[arr][window:],
            np.zeros(window)
        ])

    return ne.evaluate(expression, global_dict=variable_dict)

#Evaluation:

expression_1 = "a * 3 + 1.234 * c - d"
expression_2 = "a + b / b.max() * a.max()"
expression_3 = "a + b.roll(3) + c.roll(2) + d.roll(4)"
expression_4 = "((a+b)**3).min()) + ((c-d)*5).roll(3)"

print(f"{expression_1}\n{expression_eval(expression_1, a, b, c, d)}\n")
print(f"{expression_2}\n{expression_eval(expression_2, a, b, c, d)}\n")
print(f"{expression_3}\n{expression_eval(expression_3, a, b, c, d)}\n")
print(f"{expression_4}\n{expression_eval(expression_3, a, b, c, d)}\n")

Спасибо за ваш ответ! В чем в этом контексте преимущество использования numexpr.evaluate("a * 3 + 1.234 * c - d") перед eval("a * 3 + 1.234 * c - d")?

Basj 29.03.2024 10:18

Обрабатывает ли он вложенные выражения? вроде как ((a+b)**3).max())?

cards 29.03.2024 20:41

@Basj оценивать буквальные выражения от третьих лиц рискованно с точки зрения безопасности. Вот блог realpython.com/python-eval-function/… , в котором подробно объясняется проблема. Вы разрешаете внешнему пользователю потенциально внедрить вредоносный код (злонамеренно или по незнанию). Что-то вроде вставки «__import__('subprocess').getoutput('rm –rf *')» (linux) или аналогичного в powershell позволяет удалить все ваши файлы. Прежде чем выражение будет выполнено в eval, необходим тщательный анализ. Использование numexpr ограничивает использование пользователем только алгебраических функций.

AlGM93 30.03.2024 12:56

Другие вопросы по теме