Показать сообщение отдельно
  #1 (permalink)  
Старый 14.11.2011, 20:42
Профессор
Отправить личное сообщение для Matre Посмотреть профиль Найти все сообщения от Matre
 
Регистрация: 07.01.2011
Сообщений: 582

Вычисление значения выражения без eval
Всем доброго времени суток.

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

Алгоритм следующий: ищем такие последовательности символов, чтобы было число, оператор и снова число, например, 5*2. Вычисляем значение, т.е. 10, и ставим на место 5*2. Если с обеих сторон скобки, то убираем их из строки. И так, пока не в строке не останется только одно-единственное число, которое и будет ответом.

Проблема в следующем:
Хоть и регулярка, которая ловит последовательность символов число-оператор-число, имеет флаг g, за один проход почему-то обрабатывается только один из нескольких одноимённых операторов, стоящих рядом. Т.е. у меня есть 3*3*3+1, сначала обрабатывается 3*3, потом 3+1, и в итоге мы имеем 9*4, что неверно.

Рассмотрим пример.
Итак, у меня есть выражение 3*3*3+1
По идее, должно обработаться сначала 3*3, тогда строка будет равна 9*3+1. Потом обрабатывается 9*3, строка равна 27+1. Потом 27+1, строка равна 28, ответ получен, всё хорошо.
Но у меня всё работает по-другому.
Сначала обрабатывается 3*3. Это верно. Мы имеем 9*3+1. Но потом второе умножение почему-то пропускается, и начинает обрабатываться 3+1, и строка равна 9*4.

Почему так?
Вот мой код:

var S = "3 * 3 * 3 + 1 * 2", i = 0;
S = S.replace(/\s/g, "");

/*
	Вычисляет выражение вида a + b, a * b, т.е.
	число, оператор и число
*/
function expr(A, O) {
	var D = A.replace(/\(|\)/g, "").split(O);
	if ( O == "+" ) return +D[0] + +D[1];
	if ( O == "-" ) return D[0] - D[1];
	if ( O == "*" ) return D[0] * D[1];
	if ( O == "/" ) return D[0] / D[1];
	if ( O == "^" ) return Math.pow(D[0], D[1]);
}

/*
	Проверяет, не осталось ли в S только число
	Если да — выражение вычислено
*/
function simple() {
	return /^-?\d+(?:\.\d+)?$/.test(S);
}

/*
	Ищем такие последовательности символов, чтобы
	было "число, оператор, число". Если с обеих сторон
	выражения есть скобки, то убираем их
*/
function compute(operator) {
	var re = new RegExp("(\\()?-?\\d+(?:\\.\\d+)?\\" + operator + "-?\\d+(?:\\.\\d+)?(\\))?", "g");
	S = S.replace(re, function (a, b, c) {
		var C, R = "";
		if ( b == undefined || c == undefined )
			C = 1;
		else
			C = 0;
		if (C) R += b || "";
		R += expr(a, operator);
		if (C) R += c || "";
		return R;
	});
}

/*
	В бесконечном цикле обрабатываем все операции
	в порядке их приоритета: ^, *, /, +, -
	Если в S осталось только число, то прерываем цикл
	и показываем результат
*/
while (true) {
	compute("^");
	compute("*"); compute("/");
	compute("+"); compute("-");
	if (simple()) {
		alert(S);
		break;
	}
	if (++i > 10000) {
		alert("Unexpercted error");
		break;
	}alert(S)
}
Ответить с цитированием