matiasikonen
matiasikonen

Reputation: 23

String together several operations in javascript calculator

I need help with my calculator. I don't know how to string together several operations. Can someone help me out with this or give some tips how to work forward. My calculator can only 3 + 3 but not 3 + 3 + 3 etc several operations.

My calculator works like this:

  1. Get a number from user. Store to your array.
  2. if user keeps hitting number buttons, keep pushing them in to the array.
  3. When the user hits an operator, store it to currentOperator. Parse your array into an integer. Store result to firstNumber. Then empty array.
  4. Get another number from user, store to array, repeat step 2.
  5. When the user hits the equals button, parse your array into an integer again. Store result to secondNumber and call function calculate()

const operator = document.querySelectorAll('.operator');
const equalsKey = document.getElementById('equalsKey');
const display = document.getElementById('result');
const numbers = document.querySelectorAll('.number');
const operators = document.querySelectorAll('.operator');
const clear = document.getElementById('clear');

let firstNumber;
let currentOperator;
let secondNumber;
let tempArray = [];

numbers.forEach((number) => {
    number.addEventListener('click', (e) => {
        tempArray.push(e.target.value); // store value to temparray
        console.log(tempArray);
        display.innerHTML = Number(tempArray.join("")); // display number from array
    });
});

operators.forEach((operator) => {
    operator.addEventListener('click', (e) => {
        currentOperator = e.target.value; // store current operator
        console.log(currentOperator);
        firstNumber = Number(tempArray.join("")); // parse array to integers
        console.log(firstNumber);
        tempArray = []; // empty array
    });
});

clear.addEventListener('click', function () {
    display.textContent = '0';
})

function calculate() {
    secondNumber = Number(tempArray.join(""));
    let result = operate(parseFloat(firstNumber), parseFloat(secondNumber), currentOperator);
    display.textContent = result;
}


function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

function multiply(a, b) {
    return a * b;
}

function divide(a, b) {
    return a / b;
}

function operate(num1, num2, operator) {
    switch (operator) {
        case "+":
            return add(num1, num2);
        case "-":
            return subtract(num1, num2);
        case "*":
            return multiply(num1, num2);
        case "÷":
            return divide(num1, num2);
    }
};

equalsKey.addEventListener('click', calculate);

https://codepen.io/alcatel1962/pen/LYONKKW

I don't know how to think when try to string together several operations.

Upvotes: 2

Views: 1504

Answers (2)

Umendra Rajapakshe
Umendra Rajapakshe

Reputation: 134

Good job so far! to tackle your problem, you should put your knowledge of different kinds of loops which should allow you to solve this problem rather easily.

here's the link to a pen forked from yours that tackles your problem through a different approach(although a very lazy one). My approach tries to mimic the behavior of the Google Calculator(the one you get when you google for "calculator")

Note however that I have not considered negative numbers and floating-point numbers, Also there needs to be validation as shown in the comments of the code if you are to follow this approach, And of course, you will have to push the result of the previous calculation as the first number to the array after "=" is pressed, Try to identify/solve these problems/improvements by yourself ;) good luck!

const equalsKey = document.getElementById('equalsKey');
const display = document.getElementById('result');
const clear = document.getElementById('clear');
const numbersAndOperators = document.querySelectorAll('.operator, .number');

let firstNumber;
let currentOperator;
let secondNumber;
let tempArray = [];


function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

function multiply(a, b) {
    return a * b;
}

function divide(a, b) {
    return a / b;
}

// When the user hits any button or operator push them to the array after validating.
numbersAndOperators.forEach((numberOrOperator) => {
    numberOrOperator.addEventListener('click', (e) => {
       // TODO: validate next input here 
        tempArray.push(e.target.value); // store value to temparray
        console.log(tempArray);
        display.innerHTML = tempArray.join(""); // display from array
    });
});

// Go through the array and identify if operators according to the order of 
// BODMAS is present, and execute that operator for the operands left and right to it if found.
function validateBODMAS(inputs){
  let modifiedInputs = inputs
  if(modifiedInputs.includes("÷")){
    modifiedInputs = runOpp(modifiedInputs,"÷",divide)
  } 
  if (modifiedInputs.includes("*")){
    modifiedInputs = runOpp(modifiedInputs,"*",multiply)
  } 
  if (modifiedInputs.includes("+")){
    modifiedInputs = runOpp(modifiedInputs,"+",add)
  } 
  if (modifiedInputs.includes("-")){
    modifiedInputs = runOpp(modifiedInputs,"-",subtract)
  }
  return modifiedInputs;
}

// Run the operator for all opperands next to a particular operator.
// replace the result with operands and operator.
function runOpp(inputarr,oppSymbol,oppCallback){
  while(inputarr.includes(oppSymbol)){
        const indexOpp = inputarr.indexOf(oppSymbol)
        inputarr.splice(indexOpp - 1,3,oppCallback(Number(inputarr[indexOpp - 1]) , Number(inputarr[indexOpp + 1])))
        console.log(inputarr)
  }
  return inputarr
}

function calculate() {
    result = validateBODMAS(tempArray)
    display.textContent = result;
}

clear.addEventListener('click', function () {
    display.textContent = '';
    tempArray = [];
})

equalsKey.addEventListener('click', calculate);

Upvotes: 2

jsN00b
jsN00b

Reputation: 3691

Improving on the this existing code-pen solution, the below has been created.

To execute / view the code snippet below, please switch to FULL PAGE.

Code Snippet

const operator = document.querySelectorAll('.operator');
const equalsKey = document.getElementById('equalsKey');
const display = document.getElementById('result');
const numbers = document.querySelectorAll('.number');
const operators = document.querySelectorAll('.operator');
const clear = document.getElementById('clear');
const deci = document.getElementById('deci');
// to view debug info, change class 'hideDebug' display-style in CSS
const debug = document.getElementById('debug');

deci.addEventListener('click', () => alert('decimals not yet added...'));
// a temp array to hold input characters
let cache = [];

// a closure to handle creating, manipulating the parse-tree
function parser() {
  // a tree-structure to use for storing & parsing operators, operands.
  const myTree = { root: null, left: null, right: null };
  
  // reset tree to original state
  const reset = (mt) => {
    mt.root = null; mt.left = null; mt.right = null;
  };
  
  // add a new operand or operator into the proper location
  // uses BODMAS (also called PEDMAS, PEMDAS, BOMDAS, etc)
  // basically division-multiplication takes priority over addition-subtraction
  const add = (el, type, nt) => {
    if (type === 'operand') {
      if (nt.left === null) nt.left = +el;
      else if (nt.right === null) nt.right = +el;
      else add(el, type, nt.right);
    } else {
      if (nt.root === null) nt.root = el;
      else if ('+-'.includes(nt.root) && '/*'.includes(el)) {
        // if current operator is / or * and previous is + or -
        // structure tree so that the / or * takes precedence
        const t = structuredClone(nt.right);
        nt.right = { root: el, left: t, right: null };
      } else {
        // for all other cases left side calculation takes precedence
        const t = structuredClone(nt);
        nt.left = t;
        nt.root = el;
        nt.right = null;
      };
    };
  };
  
  // helper method to quickly compute simple operations
  const compute = (op1, op, op2) => {
    switch (op) {
      case '+': return op1 + op2;
      case '-': return op1 - op2;
      case '*': return op1 * op2;
      case '/': return op1 / op2;
    }
  };
  
  // recursive method to evaluate the parse-tree
  const evaluate = (et) => (
    compute(
      Number.isFinite(et.left) ? et.left : evaluate(et.left),
      et.root,
      Number.isFinite(et.right) ? et.right : evaluate(et.right)
    )
  );
  
  // return object with relevant methods
  return {
    isEmpty: () => myTree.root === null,
    addToTree: (el, type = 'operand') => {
      add(el, type, myTree);
    },
    showTree: () => (JSON.stringify(myTree)),
    clearTree: () => {reset(myTree)},
    calculate: () => evaluate(myTree)
  };
};
const myParser = parser();

// handle clear button click
clear.addEventListener('click', function () {
    display.textContent = '0';
    myParser.clearTree();
    debug.innerHTML = myParser.showTree();
    cache = [];
});

// for each number clicked,
// cache the value, and update display
numbers.forEach(el => {
  el.addEventListener('click', (ev) => {
    if (cache.length === 0 && myParser.isEmpty()) display.innerHTML = ev.target.value;
    else display.innerHTML += ev.target.value;
    cache.push(ev.target.value);
  });
});

// for each operator clicked,
// process existing cache and operator into parse-tree
operator.forEach(el => {
  el.addEventListener('click', (ev) => {
    if (cache.length > 0 || ev.target.value === '-') {
      if (cache.length === 0) {  // negative number as operand-1
        display.innerHTML = '0-';
        cache.push(0);
      } else if (!('+-*/'.includes(cache.slice(-1)))) display.innerHTML += ev.target.value;
      myParser.addToTree(cache.join(''));
      cache = [];
      myParser.addToTree(ev.target.value, 'operator');
      debug.innerHTML = myParser.showTree();
    }
  });
});

// calculate method
// add last operand to parse-tree, clear cache
// obtain the calculated-result, display & cache it
// finally, clear the parse-tree
const calculate = () => {
  myParser.addToTree(cache.join(''));
  cache = [];
  debug.innerHTML = myParser.showTree();
  const calcResult =  myParser.calculate();
  display.innerHTML = calcResult;
  cache.push(calcResult);
  myParser.clearTree();
};

equalsKey.addEventListener('click', calculate);
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: 10px;
}

body {
  font-family: "Open Sans", sans-serif;
}

p {
  font-size: 16px;
  line-height: 1.5;
}

img {
  width: 100%;
}

.container {
  max-width: 900px;
  margin: 0 auto;
  padding: 0 20px;
}

.box {
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}

.calc {
  width: 300px;
  height: 350px;
  margin: 0 auto;
  background-color: #ccc;
  padding: 15px;
  border-radius: 3px;
  position: relative;
}

.result {
  color: rgb(68, 66, 66);
  background-color: #fff;
  height: 45px;
  width: 70%;
  border-radius: 3px;
  margin-bottom: 5px;
  border: 2px solid grey;
  padding: 10px;
  overflow: hidden;
}

.clear {
  width: 20%;
  height: 45px;
  padding: 10px;
  position: absolute;
  right: 20px;
  top: 5px;
  border-radius: 3px;
  cursor: pointer;
  font-size: 16px;
}


.keys {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-gap: 5px;
  font-size: 16px;
}

.keys button {
  list-style: none;
  width: 50px;
  background-color: #fff;
  margin: 10px 5px;
  padding: 10px;
  /* border-bottom: 1px solid #dadedc; */
  border-radius: 3px;
  color: rgb(68, 66, 66);
  text-align: center;
  cursor: pointer;
  transition: all 0.1s;
}

.keys button:hover {
  background-color: #dadedc;
  color: #000;
}

.keys .equals {
  background-color: blue;
  color: #fff;
}

.hideDebug { display: none; }
<div class='hideDebug' id='debug'>aa</div>
<section>
        <div class="container box">
            <div class="calc">
                <div class="result">
                    <p id="result">0</p>
                </div>
                <button class="clear" id="clear">C</button>

                <ul class="keys">
                    <button class="number" value="1">1</button>
                    <button class="number" value="2">2</button>
                    <button class="number" value="3">3</button>
                    <button class="operator" value="+">+</button>
                    <button class="number" value="4">4</button>
                    <button class="number" value="5">5</button>
                    <button class="number" value="6">6</button>
                    <button class="operator" value="-">-</button>
                    <button class="number" value="7">7</button>
                    <button class="number" value="8">8</button>
                    <button class="number" value="9">9</button>
                    <button class="operator" value="*">*</button>
                    <button class="number" value="0">0</button>
                    <button id='deci' class="decimal" value=".">.</button>
                    <button class="equals" id="equalsKey" value="=">=</button>
                    <button class="operator" value="/">÷</button>
                </ul>
            </div>
        </div>
    </section>

Explanation

Detailed comments added inline in the above code-snippet

Upvotes: 1

Related Questions