Я написал этот метод eval
для арифметических выражений, чтобы ответить на этот вопрос. Это добавление, вычитание, умножение, деление, возведение в степень (использование символа ^
) и несколько основных функций, таких как sqrt
. Он поддерживает группировку с использованием (
... )
и получает правильные правила приоритета и ассоциативности .
public static double eval(final String str) {
return new Object() {
int pos = -1, ch;
void nextChar() {
ch = (++pos < str.length()) ? str.charAt(pos) : -1;
}
boolean eat(int charToEat) {
while (ch == ' ') nextChar();
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parse() {
nextChar();
double x = parseExpression();
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
return x;
}
// Grammar:
// expression = term | expression `+` term | expression `-` term
// term = factor | term `*` factor | term `/` factor
// factor = `+` factor | `-` factor | `(` expression `)`
// | number | functionName factor | factor `^` factor
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) x += parseTerm(); // addition
else if (eat('-')) x -= parseTerm(); // subtraction
else return x;
}
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) x *= parseFactor(); // multiplication
else if (eat('/')) x /= parseFactor(); // division
else return x;
}
}
double parseFactor() {
if (eat('+')) return parseFactor(); // unary plus
if (eat('-')) return -parseFactor(); // unary minus
double x;
int startPos = this.pos;
if (eat('(')) { // parentheses
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
x = Double.parseDouble(str.substring(startPos, this.pos));
} else if (ch >= 'a' && ch <= 'z') { // functions
while (ch >= 'a' && ch <= 'z') nextChar();
String func = str.substring(startPos, this.pos);
x = parseFactor();
if (func.equals("sqrt")) x = Math.sqrt(x);
else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
else throw new RuntimeException("Unknown function: " + func);
} else {
throw new RuntimeException("Unexpected: " + (char)ch);
}
if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation
return x;
}
}.parse();
}
Пример :
System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));
Выход: 7.5 (что правильно)
Парсер является рекурсивным парсером спуска , поэтому внутренне использует отдельные методы анализа для каждого уровня приоритета оператора в своей грамматике. Я сохранил его, поэтому его легко модифицировать, но вот несколько идей, которые вы можете расширить, с помощью:
eval
, например Map variables
. @FunctionalInterface
interface Expression {
double eval();
}
Теперь измените все методы, возвращающие double
s, поэтому вместо этого они возвращают экземпляр этого интерфейса. Синтаксис лямбды Java 8 отлично подходит для этого. Пример одного из измененных методов: Expression parseExpression() {
Expression x = parseTerm();
for (;;) {
if (eat('+')) { // addition
Expression a = x, b = parseTerm();
x = (() -> a.eval() + b.eval());
} else if (eat('-')) { // subtraction
Expression a = x, b = parseTerm();
x = (() -> a.eval() - b.eval());
} else {
return x;
}
}
}
Создает рекурсивное дерево объектов Expression
, представляющих скомпилированное выражение (дерево синтаксиса ). Затем вы можете скомпилировать его один раз и повторно оценить его разными значениями: public static void main(String[] args) {
Map variables = new HashMap<>();
Expression exp = parse("x^2 - x + 2", variables);
for (double x = -20; x <= +20; x++) {
variables.put("x", x);
System.out.println(x + " => " + exp.eval());
}
}
double
вы можете изменить оценщика, чтобы использовать что-то более мощное, например BigDecimal
, или класс, который реализует комплексные числа или рациональные числа (дроби). Вы даже можете использовать Object
, позволяя смешивать типы данных в выражениях, как и в реальном языке программирования. :) Весь код в этом ответе опубликовал в общедоступном домене . Получайте удовольствие!
Быстрый полупсевдо код:
List sub=one.subList(...);
List two=new XxxList(sub);
sub.clear(); // since sub is backed by one, this removes all sub-list items from one
, Который использует стандартные методы Реализации списка и избегает всего выполнения вокруг в циклах. Ясное () метод также собирается использовать внутреннее removeRange()
для большинства списков и быть намного более эффективным.
Получение возвращенного массива является довольно легким использованием метода подсписка, но нет никакого простого способа, которым я знаю об удалить диапазон объектов из Списка.
Вот то, что я имею:
<T> List<T> split(List<T> list, int i) {
List<T> x = new ArrayList<T>(list.subList(i, list.size()));
// Remove items from end of original list
while (list.size() > i) {
list.remove(list.size() - 1);
}
return x;
}
Поющий в припеве на решение Marc, это решение использует for
цикл, который сохраняет некоторые вызовы к list.size()
:
<T> List<T> split(List<T> list, int i) {
List<T> x = new ArrayList<T>(list.subList(i, list.size()));
// Remove items from end of original list
for (int j=list.size()-1; j>i; --j)
list.remove(j);
return x;
}
<T> List<T> split(List<T> list, int i) {
List<T> secondPart = list.sublist(i, list.size());
List<T> returnValue = new ArrayList<T>(secondPart());
secondPart.clear(),
return returnValue;
}
Аналогично ворча прочь списка Marc, мы будем использовать List.removeAll () для удаления дублирующихся записей из второго списка. Обратите внимание, что строго говоря это только следует за спецификациями, если исходный список не содержал дублирующихся объектов: иначе исходный список может пропускать объекты.
<T> List<T> split(List<T> list, int i) {
List<T> x = new ArrayList<T>(list.subList(i, list.size()));
// Remove items from end of original list
list.removeAll(x);
return x;
}
Общая функция для разделения списка на список определенного размера. Мне давно этого не хватало в java-коллекциях.
private List<List<T>> splitList(List<T> list, int maxListSize) {
List<List<T>> splittedList = new ArrayList<List<T>>();
int itemsRemaining = list.size();
int start = 0;
while (itemsRemaining != 0) {
int end = itemsRemaining >= maxListSize ? (start + maxListSize) : itemsRemaining;
splittedList.add(list.subList(start, end));
int sizeOfFinalList = end - start;
itemsRemaining = itemsRemaining - sizeOfFinalList;
start = start + sizeOfFinalList;
}
return splittedList;
}