Спасибо за все ваши советы! С тех пор я реализовал решение, в котором я очень доволен. Хотя ни один из ответов здесь напрямую не сообщил мне об этом подходе, они меня вдохновили.
Я решил разобрать введенные пользователем инфиксные выражения в польский нотации (префикс), а затем в двоичные деревья. Я специально для этого создал структуру TreeNode, которая по существу является дважды связанным списком, но с 2 детьми вместо 1. Каждый TreeNode может быть задан как переменная, фиксированное число или оператор. Если он задан как оператор, то его оппонент функции opFunc установлен на предопределенную функцию, которая выполняет этот оператор.
Каждый узел имеет функцию-член оценки (tVal). Если это фиксированное число, это просто возвращает это число. Если это переменная, она просто возвращает tVal. Если это оператор, он возвращает opFunc (first-> evaluation (tVal), last-> evaluation (tVal)), который рекурсивно вычисляет два узла под ним, а затем выполняет на нем свой оператор.
Я также внедрил класс ExpressionTree, который управляет деревом и анализирует ввод пользователя в дерево. У него есть публичные члены, которые рекурсивно разрушают дерево, строят дерево из пользовательской строки ввода и оценивают дерево (ему просто нужно оценить корневой узел).
Потому что это использует указатели функций в простом рекурсивном дереве, это невероятно быстро, чтобы оценить ExpressionTree, даже когда формулы получаются очень большими. По сути, я переместил более или менее такую большую обработку, насколько это возможно, когда функция оценивается и когда вводится формула.
Ниже приведен код, который я написал для деревьев выражений. Обратите внимание, что существуют только очень ограниченные операторы, однако легко добавить новые двоичные операторы, включив их в карту приоритета, добавив для них функцию и включив в setOp переключатель. Также разрешена только одна переменная, которая будет использоваться всем моим приложением.
В настоящий момент также не выполняется проверка ошибок, поэтому недопустимые операторы, пробелы или несогласованные круглые скобки приведут к неопределенному поведению ATM.
Также обратите внимание, что выражения обрабатываются как uint32_t и превращаются в uint8_t в конце, так как это требует мое приложение. Когда я закончу этот проект, я могу обобщить это как библиотеку и опубликовать ее.
TreeExpressions.hpp:
#include <cstdint>
#include <string>
#include <map>
#include <stack>
#include <vector>
#include <algorithm>
struct TreeNode {
private:
bool isOp = false;
bool isVar = false;
uint32_t value = 0;
uint32_t(*opFunc)(uint32_t, uint32_t) = NULL;
static inline uint32_t xorOp(uint32_t a, uint32_t b) { return(a^b); };
static inline uint32_t andOp(uint32_t a, uint32_t b) { return(a & b); };
static inline uint32_t orOp(uint32_t a, uint32_t b) { return(a | b); };
static inline uint32_t lshiftOp(uint32_t a, uint32_t b) { return(a << b); };
static inline uint32_t rshiftOp(uint32_t a, uint32_t b) { return(a >> b); };
static inline uint32_t addOp(uint32_t a, uint32_t b) { return(a + b); };
static inline uint32_t subOp(uint32_t a, uint32_t b) { return(a - b); };
static inline uint32_t multOp(uint32_t a, uint32_t b) { return(a * b); };
static inline uint32_t divOp(uint32_t a, uint32_t b) { return(a/b); };
static inline uint32_t modOp(uint32_t a, uint32_t b) { return(a % b); };
public:
TreeNode *first = NULL;
TreeNode *last = NULL;
TreeNode *parent = NULL;
uint32_t evaluate(uint32_t tVal);
void setOp(std::string op);
void setVal(uint32_t val);
void setVar();
};
class ExpressionTree
{
private:
std::map<std::string, int> precedence;
TreeNode *treeRoot;
TreeNode *insertNode(TreeNode *leaf);
void destroyTree(TreeNode *leaf);
public:
ExpressionTree();
~ExpressionTree();
void destroyTree();
bool build(std::string formulaStr);
uint8_t evaluate(uint32_t tVal);
};
TreeExpressions.cpp:
#include "TreeExpressions.h"
void TreeNode::setOp(std::string op)
{
isVar = false;
isOp = true;
if (op == "^")
{
opFunc = &xorOp;
}
else if (op == "&")
{
opFunc = &andOp;
}
else if (op == "|")
{
opFunc = &orOp;
}
else if (op == "<<")
{
opFunc = &lshiftOp;
}
else if (op == ">>")
{
opFunc = &rshiftOp;
}
else if (op == "+")
{
opFunc = &addOp;
}
else if (op == "-")
{
opFunc = &subOp;
}
else if (op == "*")
{
opFunc = &multOp;
}
else if (op == "/")
{
opFunc = &divOp;
}
else if (op == "%")
{
opFunc = &modOp;
}
}
void TreeNode::setVal(uint32_t val)
{
isVar = false;
isOp = false;
value = val;
}
void TreeNode::setVar()
{
isVar = true;
isOp = false;
}
uint32_t TreeNode::evaluate(uint32_t tVal)
{
if (isOp)
{
//if it's an op
return(opFunc(first->evaluate(tVal), last->evaluate(tVal)));
}
else if (isVar)
{
//if it's a var
return(tVal);
}
else
{
//if it's a number
return(value);
}
}
ExpressionTree::ExpressionTree()
{
treeRoot = NULL;
// http://en.cppreference.com/w/cpp/language/operator_precedence
precedence["*"] = 5;
precedence["/"] = 5;
precedence["%"] = 5;
precedence["+"] = 6;
precedence["-"] = 6;
precedence["<<"] = 7;
precedence[">>"] = 7;
precedence["&"] = 10;
precedence["^"] = 11;
precedence["|"] = 12;
}
ExpressionTree::~ExpressionTree()
{
destroyTree();
}
void ExpressionTree::destroyTree(TreeNode *leaf)
{
if (leaf != NULL)
{
destroyTree(leaf->first);
destroyTree(leaf->last);
delete leaf;
}
}
TreeNode *ExpressionTree::insertNode(TreeNode *leaf)
{
if (leaf->first == NULL)
{
leaf->first = new TreeNode;
leaf->first->parent = leaf;
return(leaf->first);
}
else
{
leaf->last = new TreeNode;
leaf->last->parent = leaf;
return(leaf->last);
}
}
void ExpressionTree::destroyTree()
{
destroyTree(treeRoot);
}
bool ExpressionTree::build(std::string formulaStr)
{
std::string::iterator stringIterator;
std::vector<std::string>::iterator stringVectorIterator;
std::vector<std::string> formulaTokens;
std::vector<std::string> pnTokens;
std::stack<std::string> stringStack;
std::string currentNumString = "";
std::string currentTokenString = "";
std::stack<TreeNode> nodeStack;
TreeNode *currentNode;
std::string currToken;
bool treeIsDone;
//tokenization
for (stringIterator = formulaStr.begin(); stringIterator != formulaStr.end(); stringIterator++)
{
std::string currCharString(1, *stringIterator);
currentTokenString.push_back(*stringIterator);
if ((precedence.find(currentTokenString) != precedence.end()) || (currentTokenString == "(") || (currentTokenString == ")"))
{
//if the current token string is found in the precedence list (or is a parentheses)
if (currentNumString != "")
{
formulaTokens.push_back(currentNumString);
currentNumString = "";
}
formulaTokens.push_back(currentTokenString);
currentTokenString = "";
}
else if (std::all_of(currentTokenString.begin(), currentTokenString.end(), ::isdigit))
{
//if the current token string is all digits
currentNumString.append(currentTokenString);
currentTokenString = "";
}
else if (currentTokenString == "t")
{
//if the current token string is the t variable
formulaTokens.push_back(currentTokenString);
currentTokenString = "";
}
}
//convert to polish notation
std::reverse(formulaTokens.begin(), formulaTokens.end());
stringStack.push(")");
for (stringVectorIterator = formulaTokens.begin(); stringVectorIterator != formulaTokens.end(); stringVectorIterator++)
{
currToken = *stringVectorIterator;
if ((precedence.find(currToken) == precedence.end()) && (currToken != "(") && (currToken != ")"))
{
pnTokens.push_back(currToken);
}
else if (currToken == ")")
{
stringStack.push(currToken);
}
else if (precedence.find(currToken) != precedence.end())
{
if (stringStack.size() > 0)
{
if (stringStack.top() != ")")
{
while (precedence[stringStack.top()] <= precedence[currToken])
{
if (stringStack.top() != ")")
{
pnTokens.push_back(stringStack.top());
stringStack.pop();
}
if (stringStack.size() <= 0)
{
break;
}
if (stringStack.top() == ")")
{
break;
}
}
}
}
stringStack.push(currToken);
}
else if (currToken == "(")
{
if (stringStack.size() > 0)
{
while (stringStack.top() != ")")
{
pnTokens.push_back(stringStack.top());
stringStack.pop();
if (stringStack.size() <= 0)
{
break;
}
}
stringStack.pop();
}
}
}
while (stringStack.size() > 0)
{
if (stringStack.top() != ")")
{
pnTokens.push_back(stringStack.top());
}
stringStack.pop();
}
std::reverse(pnTokens.begin(), pnTokens.end());
//if it's gotten this far, the formula was valid
//destroy the current tree to make room
destroyTree();
//parse polish notation into tree
treeRoot = new TreeNode;
currentNode = treeRoot;
treeIsDone = false;
for (stringVectorIterator = pnTokens.begin(); stringVectorIterator != pnTokens.end(); stringVectorIterator++)
{
currToken = *stringVectorIterator;
if (precedence.find(currToken) != precedence.end())
{
//if the token is an operator
currentNode->setOp(currToken);
currentNode = insertNode(currentNode);
}
else
{
//if it's a number or a variable
if (currentNode->first != NULL)
{
//if the current node has it's first branch initialized
while (currentNode->last != NULL)
{
//while the last branch is initialized
currentNode = currentNode->parent;
}
currentNode = insertNode(currentNode);
}
if (std::all_of(currToken.begin(), currToken.end(), ::isdigit))
{
//if it's a number
currentNode->setVal((uint32_t)atoi(currToken.c_str()));
}
else
{
//if it's something else, a variable
currentNode->setVar();
}
if (currentNode != treeRoot)
{
currentNode = currentNode->parent;
}
//since we just moved up, we know at least the first branch is used
//so only check the last
while (currentNode->last != NULL)
{
//if the last node is not free
if (currentNode == treeRoot)
{
//if we're at the root, and it's totally populated
treeIsDone = true;
break;
}
currentNode = currentNode->parent;
}
if (!treeIsDone)
{
currentNode = insertNode(currentNode);
}
}
}
return(true);
}
uint8_t ExpressionTree::evaluate(uint32_t tVal)
{
return((uint8_t)treeRoot->evaluate(tVal));
}
Я не очень понимаю, что вы пытаетесь достичь, но, возможно, это поможет: Почему вы вычисляя одно и то же выражение снова и снова (44100 раз, если быть точным). Почему бы не сохранить результат и выполнить его один раз? – Rakete1111
JIT-ing это вариант. LLVM скорее всего поможет вам в некоторой степени. Конечно, это какая-то дополнительная работа, так как она превращает вашего интерпретатора в компилятор, а затем вам нужно беспокоиться об остальном, но производительность будет лучше. Однако я ничего не знаю о VST или звуке. Однако интерпретация, а не JITing, безусловно, замедлит вас. – Taywee
Если вы не хотите JIT, вам обязательно нужно разбить свое выражение RPN на какой-то АСТ или что-то в этом роде, поэтому вы не выполняете сравнения строк десятки тысяч раз в секунду. – Taywee