В этой статье вы узнаете, как написать калькулятор с использованием Javascript, который может вычислять элементарные арифметические выражения. Например 2 * (2 + 3 / (3-8)). Наконец, вместе мы напишем простую статическую веб-страницу, которая откроет наш калькулятор другим.

Мы будем использовать языки Javascript, HTML и CSS. Обратите внимание, что часть алгоритма (реализации) может быть немного трудной для понимания, так как это жесткий уровень Leetcode (базовый калькулятор), но я постараюсь объяснить это изо всех сил. Несмотря на то, что в Javascript есть встроенная функция eval (), более важно понять ее логику и реализовать ее самостоятельно.

Сначала рассмотрим выражение 2 * (2 + 3 / (3-8)).

Нам предстоит преодолеть несколько трудностей:

  1. Мы хотим вернуть числовой результат этого выражения, но это строка
  2. Обычно мы вводим пробелы между операндами и операторами, и это не должно влиять на наш результат.
  3. Мы должны убедиться, что круглые скобки имеют наивысший приоритет, сначала умножить и разделить, а затем сложить и вычесть.
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/.

Если вы заметили какую-либо проблему или ошибку, пожалуйста, оставьте это в комментарии.