В этой статье вы узнаете, как написать калькулятор с использованием Javascript, который может вычислять элементарные арифметические выражения. Например 2 * (2 + 3 / (3-8)). Наконец, вместе мы напишем простую статическую веб-страницу, которая откроет наш калькулятор другим.
Мы будем использовать языки Javascript, HTML и CSS. Обратите внимание, что часть алгоритма (реализации) может быть немного трудной для понимания, так как это жесткий уровень Leetcode (базовый калькулятор), но я постараюсь объяснить это изо всех сил. Несмотря на то, что в Javascript есть встроенная функция eval (), более важно понять ее логику и реализовать ее самостоятельно.
Сначала рассмотрим выражение 2 * (2 + 3 / (3-8)).
Нам предстоит преодолеть несколько трудностей:
- Мы хотим вернуть числовой результат этого выражения, но это строка
- Обычно мы вводим пробелы между операндами и операторами, и это не должно влиять на наш результат.
- Мы должны убедиться, что круглые скобки имеют наивысший приоритет, сначала умножить и разделить, а затем сложить и вычесть.
function eval(expression) { expression = expression.replace(/\s/g, ''); return helper(Array.from(expression), 0); } function helper(s, idx) { }
Когда мы получаем строку, мы можем просто вызвать функцию замены, чтобы удалить все пробелы в выражении. / \ s / g - регулярное выражение, которое сообщает функции замены, что нужно заменить все пробельные символы (\ s) пустым символом ‘’. / g - это флаг, обеспечивающий замену всех совпадений. Помните, что мы должны присвоить его исходному выражению, потому что функция не изменяет его на месте. Затем мы вызываем вспомогательную функцию, которая оценивает и возвращает результат выражения. Мы превращаем выражение строкового типа в массив char, поэтому нам не нужно снова и снова вызывать charAt, а значение int idx сообщает нам, где мы находимся в выражении.
Следующим шагом будет реализация основного алгоритма. Сначала рассмотрим случай, когда есть только сложение и вычитание, а скобок нет.
Сначала мы определяем 3 переменные, которые будут часто использоваться для оценки результата.
function helper(s, idx) { var stk = []; let sign = '+'; let num = 0; }
Например, если мы хотим оценить «1–8», мы можем думать об этом как +1 + (-8). Что касается знаков, мы сохраняем знак перед текущим операндом в переменной «знак», которая теперь по умолчанию установлена на «+». Чтобы получить значение операндов, мы просматриваем массив символов слева направо и накапливаем значение текущего операнда до целого числа. Если мы встретим оператор, то мы поместим операнд в стек в соответствии со знаком перед ним. Наконец, мы вычисляем сумму всего в стеке, что дает окончательный результат.
function helper(s, idx) { var stk = []; let sign = '+'; let num = 0; for (let i = idx; i < s.length; i++) { let c = s[i]; if (c >= '0' && c <= '9') { num = num * 10 + (c - '0'); } if (!(c >= '0' && c <= '9') || i===s.length-1) { switch (sign) { case '+': stk.push(num); break; case '-': stk.push(num*-1); break; } sign = c; num = 0; } } let ans = 0; while (stk.length > 0) { ans += stk.pop(); } return ans; }
Продолжайте выражением «1–8». Когда мы встречаемся с оператором минус, у нас есть 1, хранящаяся в num, а знак - «+», поэтому мы помещаем положительную 1 в стек. Затем мы изменим знак на отрицательный для будущих операндов. Число установлено в ноль, потому что мы уже закончили с текущим операндом - ›1. Непосредственно перед тем, как цикл закончился, у нас есть 8, сохраненные в num и знак, равный отрицательному. Итак, мы поместим -8 в стек.
Условие i === s.length-1 гарантирует, что операнд -8 будет включен в стек. Если у нас нет этого условия, он просто проверяет, не является ли текущий символ («8») цифрой, что, очевидно, неверно. Тогда цикл завершится, и в стеке не будет числа -8, что означает, что программа выдаст неверный ответ.
Наконец, мы выделяем все элементы в стеке и суммируем их. Потом верните результат.
Теперь рассмотрим выражения с умножением и делением. Для поддержки умножения и деления требуется всего несколько строк кода.
Если мы рассмотрим «2 + 3 * 5», это будет то же самое, что и выше, перед 5. У нас будут положительные 2 и положительные 3, хранящиеся в стеке, а знак будет «*». Характеристика стека «последний пришел - первым ушел» позволяет нам получить последний элемент без потери относительного порядка. Фактически, мы извлечем последний элемент - ›3, умножим его на 5 и поместим результат (3 * 5) обратно в стек. Наконец, у нас будет 2 и 15 в стеке, и их суммирование дает результат 17. Идея деления та же. Ниже представлена измененная реализация.
function helper(s, idx) { var stk = []; let sign = '+'; let num = 0; for (let i = idx; i < s.length; i++) { let c = s[i]; if (c >= '0' && c <= '9') { num = num * 10 + (c - '0'); } if (!(c >= '0' && c <= '9') || i===s.length-1) { let pre = -1; switch (sign) { case '+': stk.push(num); break; case '-': stk.push(num*-1); break; case '*': pre = stk.pop(); stk.push(pre*num); break; case '/': pre = stk.pop(); stk.push(pre/num); break; } sign = c; num = 0; } } let ans = 0; while (stk.length > 0) { ans += stk.pop(); } return ans; }
Наконец, нам нужно разобраться со скобками. Обратите внимание, что скобки обладают рекурсивными свойствами. Рассмотрим выражение 2 * (2 + 4), оно равно 2 * helper (2 + 4). Это означает, что мы можем видеть (2 + 4) как число, и мы можем просто вычислить и вернуть результат 2 * 6, что является более простой проблемой, которую можно решить с помощью описанной выше процедуры. Вся идея состоит в том, что мы разделяем большую проблему (со скобками) на более мелкие подзадачи (чистое элементарное арифметическое выражение) с помощью рекурсии. Когда мы встречаем открывающую скобку в индексе «i», мы вызываем вспомогательную функцию, передавая текущий индекс как i + 1, и сохраняем результат в num. Рекурсия заканчивается, когда мы встречаемся с закрывающей скобкой ‘)’. Мы ставим его в качестве последнего условия, потому что мы не хотим пропускать операнды, которые идут прямо перед ним. Другими словами, мы можем закончить, только если каждый операнд будет помещен в стек. После того, как мы встретим закрывающую скобку, мы можем выйти и просто просуммировать все в стеке и вернуться. После того, как мы вычислили выражение в скобках, мы должны обновить текущий индекс, потому что мы не хотим снова переходить к выражению в скобках, что даст неправильный ответ. Рассмотрим это выражение «(1+ (4 + 5 + 2) -3) +1». После того, как мы оценили первую часть «(1+ (4 + 5 + 2) -3)». Наш индекс 'i' по-прежнему равен 0, но мы хотим, чтобы он находился на позиции второй закрывающей скобки (индекс 13), поэтому в следующей итерации 'i' увеличится на единицу с помощью оператора i ++ (в условии цикла ). Таким образом, выполнение программы вернется в нормальное русло. Мы достигаем этого путем подсчета пар скобок, и внешний индекс «i» будет установлен на индекс последней закрывающей скобки.
function eval(expression) { expression = expression.replace(/\s/g, ''); return helper(Array.from(expression), 0); } function helper(s, idx) { var stk = []; let sign = '+'; let num = 0; for (let i = idx; i < s.length; i++) { let c = s[i]; if (c >= '0' && c <= '9') { num = num * 10 + (c - '0'); } if (!(c >= '0' && c <= '9') || i===s.length-1) { if (c==='(') { num = helper(s, i+1); let l = 1, r = 0; for (let j = i+1; j < s.length; j++) { if (s[j]===')') { r++; if (r===l) { i=j; break; } } else if (s[j]==='(') l++; } } let pre = -1; switch (sign) { case '+': stk.push(num); break; case '-': stk.push(num*-1); break; case '*': pre = stk.pop(); stk.push(pre*num); break; case '/': pre = stk.pop(); stk.push(pre/num); break; } sign = c; num = 0; if (c===')') break; } } let ans = 0; while (stk.length > 0) { ans += stk.pop(); } return ans; }
Отлично! Теперь мы закончили самую сложную часть. Осталась только веб-страница.
Это примерный вид страницы. Необязательно иметь много визуалов, главное - функциональность.
Для начала не забудьте поместить файл js в папку проекта и добавить эту строку в свой HTML, чтобы мы могли вызвать функцию в файле js.
<script type="text/javascript" src="js/expressionEval.js"></script>
Я думаю, что строки между тегом ‹head› довольно просты, просто включая css и установите значок. Тег ‹body› содержит информацию, которую необходимо знать пользователю, включая функции калькулятора и некоторые правила ввода, о которых необходимо знать. Единственное, что стоит отметить:
‹Input type =” button ”value =” Evaluate ”onclick =” document.getElementById (‘result’). InnerHTML = eval (document.getElementById (‘expression-box’). Value) .toString () »›
По сути, когда нажимается кнопка «Оценить», мы извлекаем строку ввода в текстовое поле, вызывая document.getElementById ('expression-box'). Value, которое возвращает значение в текстовом поле, представленное ‹input type = »Размер текста» = «50%» id = «поле-выражения» ›. Затем мы передаем эту входную строку в нашу функцию Javascript, реализованную выше. Числовой результат преобразуется в строку и устанавливается как innerHTML тега h3 с идентификатором «результат». Таким образом, пользователь увидит результат.
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Simple Calculator</title> <link rel="shortcut icon" type="image/jpg" href="img/favicon.png"/> <link href="css/index.css" rel="stylesheet" type="text/css"> <script type="text/javascript" src="js/expressionEval.js"></script> </head> <body> <h1 style="text-align: center; ">Simple Calculator</h1> <div class="info"> <p>This calculator supports evaluating elementary arithmetic expressions</p> <p>Input Rules: </p> <p> ... </p> </div> <div class="main"> <p>Please enter the expression</p> <input type="text" size="50%" id="expression-box"> <input type="button" value="Evaluate" onclick="document.getElementById('result').innerHTML=eval(document.getElementById('expression-box').value).toString()"> </div> <div class="result"> <h3> The result is: </h3> <h3 id="result"></h3> </div> </body> </html>
Часть CSS довольно проста, просто централизует тексты.
.main { display: grid; place-items: center; } .info { text-align: center; } .result { display: grid; place-items: center; }
Надеюсь, вы кое-что узнали из этой статьи.
Исходный код можно найти по адресу https://github.com/TommyPang/SimpleCalculator.
Доступ к веб-странице можно получить по адресу https://tommypang.github.io/SimpleCalculator/.
Если вы заметили какую-либо проблему или ошибку, пожалуйста, оставьте это в комментарии.