5 * - 5 = -25 |OR| 5 * - + 5 = 10, but not both at the same time #13

Passing 15 out of 16
Stuck on number 13
I can get 5 * - 5 to evaluate to -25 or
5 * - + 5 to equal 10
but not both at the same time
anything I come up with feels really hacky and would most likely break other more normal operations.

Passing 15 out of 16, for the Calculator at FreeCodeCamp.com

repo
live demo, be sure to use the NavBar to get to Calculator
use the hamburger menu on the top left and select JavaScript Calculator to run the test suite, they say it’s designed for Chrome and may encounter bugs in other browsers.

Calculator.js

  handleClick = (buttonName) => {
    let { displayNumber, operatorFlag, decimalFlag } = this.state;
    // let lastChar = displayNumber[displayNumber.length - 1];
    switch (true) {
      // 24:00
      case buttonName === "0" ||
        buttonName === "1" ||
        buttonName === "2" ||
        buttonName === "3" ||
        buttonName === "4" ||
        buttonName === "5" ||
        buttonName === "6" ||
        buttonName === "7" ||
        buttonName === "8" ||
        buttonName === "9":
        if (displayNumber !== "0") {
          displayNumber += buttonName;
          operatorFlag = false;
        } else {
          displayNumber = buttonName;
        }
        break;
      case buttonName === "-":
        if (!operatorFlag) {
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        } else {
          // const newNumber = displayNumber.slice(0, displayNumber.length - 1);
          // displayNumber = newNumber;
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        }
        break;
      //25:00
      case buttonName === "+" ||
        buttonName === "-" ||
        buttonName === "*" ||
        buttonName === "/":
        if (!operatorFlag) {
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        } else {
          const newNumber = displayNumber.slice(0, displayNumber.length - 1);
          displayNumber = newNumber;
          displayNumber += buttonName;
        }
        break;
      //33:00 clear
      case buttonName === "C":
        displayNumber = "0";
        operatorFlag = false;
        this.setState({ decimalFlag: false });
        break;
      //35:00
      case buttonName === "=":
        displayNumber = evaluate(displayNumber);
        operatorFlag = false;
        this.setState({ decimalFlag: true });
        break;
      //38:00
      case buttonName === ".":
        if (!decimalFlag) {
          displayNumber += ".";
          this.setState({ decimalFlag: true });
        }
        break;
      default:
    }
    this.setState({ operatorFlag });
    this.setState({ displayNumber });
  };

(Grain of salt: It’s midnight. Brain may not be firing on 100% gas at the moment)

I see two things.
#1: Why is your third case looking for a minus sign, when that was case number 2.
#2: Let us examine the IF inside your second case.
If we do not have an operator, and this button has been pushed, then the button push indicates the operation “subtract” is desired. So we set our operator flag to true.
If we DO have an operator, and this button has been pushed, then the button push indicates that the second operand is a negative number. So we… set our operator flag to true? Why?
Right now, there is no difference in your code between the if-true and if-false clauses. That should be a red flag.

5 * - + 5 is not valid mathematical syntax, as it is ambiguous. Is the desired outcome 10 (5 + 5)? or -25 (5*(-(+5))? or 0 (5-(+)5)?

It’s what they are putting in the automated testing. the idea is that entering additional operators replace previous operators. So 5 times negative 5 is suppose to be -25, but if it’s not a negative, it should replace the last operator, so 5 times, minus, plus just becomes 5 + 5 for 10

This, by the way, is why actual calculators have a +/- key that is separate from the - key. Ambiguity.
Look at it this way:

If I enter into your system: (literal keypresses follow)
5 * - 5 as separate keypresses, did i want -25? or 0?

According to the statement you just gave me:

Their system expects the answer to be 0. I told the system to do 5 - 5. The whole problem exists of whether “-” is an operator, or a negator.

yeah, the assignment feels wonky… I’m not allowed to add an additional key, because one of the things it uses for testing is the button id’s and it’s only expecting one of minus. I’m pushing an update now. My best hack so far is based on regular expressions, but if I get it to pass this one I’m assuming it’ll fail another variant on the same one.

  handleClick = (buttonName) => {
    let { displayNumber, operatorFlag, decimalFlag } = this.state;
    // let lastChar = displayNumber[displayNumber.length - 1];
    let scndLastChar = displayNumber[displayNumber.length - 2];
    switch (true) {
      // 24:00
      case buttonName === "0" ||
        buttonName === "1" ||
        buttonName === "2" ||
        buttonName === "3" ||
        buttonName === "4" ||
        buttonName === "5" ||
        buttonName === "6" ||
        buttonName === "7" ||
        buttonName === "8" ||
        buttonName === "9":
        if (displayNumber !== "0") {
          displayNumber += buttonName;
          operatorFlag = false;
        } else {
          displayNumber = buttonName;
        }
        break;
      case buttonName === "-":
        if (!operatorFlag) {
          displayNumber += buttonName;
          operatorFlag = false;
          this.setState({ decimalFlag: false });
        } else {
          // const newNumber = displayNumber.slice(0, displayNumber.length - 1);
          // displayNumber = newNumber;
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        }
        break;
      //25:00
      case buttonName === "+" ||
        // buttonName === "-" ||
        buttonName === "*" ||
        buttonName === "/":
        // if (scndLastChar === "*") {
        //   let newNumber = displayNumber.slice(0, displayNumber.length - 2);
        //   // console.log(newNumber);
        //   displayNumber = newNumber;
        // }
        if (!operatorFlag) {
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        } else {
          let newNumber = displayNumber.slice(0, displayNumber.length - 1);
          displayNumber = newNumber;
          displayNumber += buttonName;
        }
        break;
      //33:00 clear
      case buttonName === "C":
        displayNumber = "0";
        operatorFlag = false;
        this.setState({ decimalFlag: false });
        break;
      //35:00
      case buttonName === "=":
        displayNumber = evaluate(displayNumber);
        operatorFlag = false;
        this.setState({ decimalFlag: true });
        break;
      //38:00
      case buttonName === ".":
        if (!decimalFlag) {
          displayNumber += ".";
          this.setState({ decimalFlag: true });
        }
        break;
      default:
    }
    this.setState({ operatorFlag });
    this.setState({ displayNumber });
  };

the version I just pushed can get -25 and when I press 5 * - + 5, the plus will replace the minus, but it’s still trying to multiply. So it comes out 25 when the automated testing is expecting it to be 10

If their test cases say that 5 * - 5 is -25, and 5 * - + 5 is 10, then their system is reading as “Assume - is a negator, until such time that it is replaced by an operator.”

the following logic is what i would employ on a keypress:

Special Case: If my current “calculation” string is “0”, and I push any number or -, replace the whole field with the key I pressed; else ignore it.

  • If I pushed a number, enter it.
  • If I pushed a decimal point, and i’m not currently in a decimal, enter it and set the decimal flag.
  • If I pushed a minus sign, enter it. Set the decimal flag to false.
  • If I pushed a symbol other than minus, check the last character of the calculation string.
    • If the last character of the string is another symbol, erase it, and repeat this check until the last character is not a symbol.
    • Special Case: If this would erase the entire calculation field, the input was invalid. Restore the previous calculated value.
    • Enter the value. Set the decimal flag to false.
2 Likes

Still feels ’hacked’ which is what I was originally concerned about, but I just got this to pass all 16 out of 16… So I guess I can go back to finishing my pomodoro tomorrow then I’ll finally have the certificate… but I’d still like to understand the ‘right’ way to do it. Like you said a proper negator would be nice to distinguish.

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Button, Container, Row, Col } from 'react-bootstrap';
import './Calculator.css';
import { evaluate } from 'mathjs';

export class Calculator extends Component {
  constructor(props) {
    super(props);
    // Initial State
    this.state = {
      // equation: 0,
      // operators: ['+', '-', 'x', '÷'],
      operatorFlag: false,
      decimalFlag: false,
      displayNumber: 0,
    };

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
    // this.handleClear = this.handleClear.bind(this);

  }

  static propTypes = {
    operators: PropTypes.array.isRequired,
    decimalAdded: PropTypes.bool.isRequired,
  };

  handleClick = (buttonName) => {
    let { displayNumber, operatorFlag, decimalFlag } = this.state;
    // let lastChar = displayNumber[displayNumber.length - 1];
    let scndLastChar = displayNumber[displayNumber.length - 2];
    switch (true) {
      // 24:00
      case buttonName === "0" ||
        buttonName === "1" ||
        buttonName === "2" ||
        buttonName === "3" ||
        buttonName === "4" ||
        buttonName === "5" ||
        buttonName === "6" ||
        buttonName === "7" ||
        buttonName === "8" ||
        buttonName === "9":
        if (displayNumber !== "0") {
          displayNumber += buttonName;
          operatorFlag = false;
        } else {
          displayNumber = buttonName;
        }
        break;
      case buttonName === "-":
        if (!operatorFlag) {
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        } else {
          // const newNumber = displayNumber.slice(0, displayNumber.length - 1);
          // displayNumber = newNumber;
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        }
        break;
      //25:00
      case buttonName === "+" ||
        // buttonName === "-" ||
        buttonName === "*" ||
        buttonName === "/":
        if (scndLastChar === "*") {
          let newNumber = displayNumber.slice(0, displayNumber.length - 1);
          // console.log(newNumber);
          displayNumber = newNumber;
        }
        if (!operatorFlag) {
          displayNumber += buttonName;
          operatorFlag = true;
          this.setState({ decimalFlag: false });
        } else {
          let newNumber = displayNumber.slice(0, displayNumber.length - 1);
          displayNumber = newNumber;
          displayNumber += buttonName;
        }
        break;
      //33:00 clear
      case buttonName === "C":
        displayNumber = "0";
        operatorFlag = false;
        this.setState({ decimalFlag: false });
        break;
      //35:00
      case buttonName === "=":
        displayNumber = evaluate(displayNumber);
        operatorFlag = false;
        this.setState({ decimalFlag: true });
        break;
      //38:00
      case buttonName === ".":
        if (!decimalFlag) {
          displayNumber += ".";
          this.setState({ decimalFlag: true });
        }
        break;
      default:
    }
    this.setState({ operatorFlag });
    this.setState({ displayNumber });
  };

  render() {
    return (
      <Container
        id="calculator">
        <h4>
          <a
            className="App-link"
            href="https://www.freecodecamp.org/learn/front-end-libraries/front-end-libraries-projects/build-a-javascript-calculator"
            target="_blank"
            rel="noopener noreferrer"
            title="JavaScript Calculator"
          >
            <i className="fas fa-calculator"></i> Calculator <i className="fas fa-calculator"></i>
          </a>
        </h4>
        {/* <br></br> */}
        <Row className="justify-content-center">
          {/* 789 */}
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="warning"
            id="divide"
            value="/"
            onClick={() => this.handleClick("/")}
          >
            <h2>
              <i class="fas fa-divide"></i>
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="seven"
            value="7"
            onClick={() => this.handleClick("7")}
          >
            <h2>
              7
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="eight"
            value="8"
            onClick={() => this.handleClick("8")}
          >
            <h2>
              8
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="nine"
            value="9"
            onClick={() => this.handleClick("9")}
          >
            <h2>
              9
            </h2>
          </Col>
        </Row>
        <Row className="justify-content-center">
          {/* 456 */}
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="warning"
            id="multiply"
            value="*"
            onClick={() => this.handleClick("*")}
          >
            <h2>
              <i class="fas fa-times"></i>
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="four"
            value="4"
            onClick={() => this.handleClick("4")}
          >
            <h2>
              4
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="five"
            value="5"
            onClick={() => this.handleClick("5")}
          >
            <h2>
              5
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="six"
            value="6"
            onClick={() => this.handleClick("6")}
          >
            <h2>
              6
            </h2>
          </Col>
        </Row>
        <Row className="justify-content-center">
          {/* 123 */}
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="warning"
            id="subtract"
            value="-"
            onClick={() => this.handleClick("-")}
          >
            <h2>
              <i class="fas fa-minus"></i>
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="one"
            value="1"
            onClick={() => this.handleClick("1")}
          >
            <h2>
              1
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="two"
            value="2"
            onClick={() => this.handleClick("2")}
          >
            <h2>
              2
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="three"
            value="3"
            onClick={() => this.handleClick("3")}
          >
            <h2>
              3
            </h2>
          </Col>
        </Row>
        <Row className="justify-content-center">
          {/* +.0 = */}
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="warning"
            id="add"
            value="+"
            onClick={() => this.handleClick("+")}
          >
            <h2>
              <i class="fas fa-plus"></i>
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="dark"
            id="decimal"
            value="."
            onClick={() => this.handleClick(".")}
          >
            <h1 className="decim">
              .
            </h1>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="success"
            id="zero"
            value="0"
            onClick={() => this.handleClick("0")}
          >
            <h2>
              0
            </h2>
          </Col>
          <Col as={Button}
            className="key-pad" xs={2} sm={2} md={1} lg={1}
            variant="warning"
            id="equals"
            value="="
            onClick={() => this.handleClick("=")}
          >
            <h2>
              <strong>
                =
              </strong>
              {/* <i class="fas fa-equals"></i> */}
            </h2>
          </Col>
        </Row>
        <Row className="justify-content-center">
          {/* equation, display & clear */}
          <Col as={"h3"}
            className="key-pad" xs={4} sm={2} md={2} lg={2}
            id="equation">
            {this.state.equation}
          </Col>
          <Col as={"h3"}
            className="key-pad" xs={4} sm={2} md={2} lg={2}
            id="display">
            {this.state.displayNumber}
          </Col>
          <Col as={Button}
            className="key-pad" xs={4} sm={3} md={2} lg={2}
            variant="info"
            id="clear"
            value="c"
            onClick={() => this.handleClick("C")}
          >
            <h2>
              clear
            </h2>
          </Col>
        </Row>
        <br></br>
        <h5>
          <a
            className="App-link"
            href="https://www.twitch.tv/collections/1DAbboLDVBZxxA"
            target="_blank"
            rel="noopener noreferrer"
            title="These Episodes on Twitch I work on the JavaScript Calculator for my freeCodeCamp certificate using React-BootStrap"
          >
            <i className="fab fa-twitch"></i> These Episodes on Twitch <i className="fab fa-twitch"></i>
          </a>
        </h5>
      </Container>
    );
  }
}
export default Calculator;

When doing test-driven development, you almost always do something that feels hacked or too simple for other plans to work. Just getting the tests to pass is not the end.

The next stage from there is to keep the tests passing while you refactor the code, to make improvements so that it doesn’t feel hacked.

1 Like

Thank you for all the replies, I’ll reread them for clarity in the morning and maybe it’ll click better… I’m more right brained visual, than left brained analytical. I’d rather make the front end pretty than hack equations. It feels like instead of using math to solve a word problem, I’ve used regular expressions to brute force the equation

The proper way is indeed a separate negator symbol; otherwise the logic gets ambiguous (or at least, must make a declaritive assumption/restriction).

In effect: You can turn any existing operator into a *, / or +, but you cannot turn it into a -.

Looking at a few things TurtleWolf, and just throwing out a few ideas.

You could consider isNaN (is not a number) instead

If we negate that we can check for a positive match to a number

console.log(!isNaN('9')) // true
console.log(!isNaN('=')) // false

For testing if it is a ‘+*/’ operator we could use a regEx character set

/[+*/]/.test(input)

An example test

function check(input) {
  switch(true) {
    case (!isNaN(input)):
      console.log(`Input is Number ${input}`)
      break;
    case (input === '-'):
      console.log('Input is Minus')
      break;
    case (/[+*/]/.test(input)):
      console.log(`Input is operator ${input}`)
      break;
    default:
  }
}

check('+') // Input is operator +
check('9') // Input is Number 9
check('-') // Input is Minus
check('3') // Input is Number 3
check('*') // Input is operator *

Second thing that stands out

Is there a need to set the operatorFlag? You are already ascertaining whether the flag is false or true, why set what already is?

if (!operatorFlag) { // if operatorFlag is false
  operatorFlag = false // why do this?
} else {
  opertatorFlag = true // we already know that :)

excluding the commented out script I don’t see a need for the if/else block, just

displayNumber += buttonName
this.setState({ decimalFlag: false })

Or if that commented out script is integral, possibly

case buttonName === "-":
  if (operatorFlag === true) {
    displayNumber = displayNumber.slice(0, displayNumber.length - 1)
  }
  displayNumber += buttonName
  this.setState({ decimalFlag: false })
  break;

Just a few thoughts :slight_smile:

edit: Just throwing this out there as well, as an alternative to using switch statements possibly, you could make the operators methods on an object

const calcMethods = {
  '.': function() { return 'Do Decimal point stuff here' },
  '+': function() { return 'Do Addition stuff here' },
  '-': function() { return 'Do Subtraction here' },
  '*': function() { return 'Do Multiplication here' },
  'C': function() { return 'Cancel here' },
  '=': function() { return 'Equals to' },
}

function calculation(input) {
  // is it a number
  if (!isNaN(input)) {
    return 'Do Number stuff here'
  }

  // does calcMethods have that property e.g. calcMethods['+']
  if (calcMethods[input]) {
    return calcMethods[input]()
  }
}

console.log(calculation('+')) // Do Addition stuff here
console.log(calculation('.')) // Do Decimal point stuff here
console.log(calculation('3')) // Do Number stuff here
console.log(calculation('*')) // Do Multiplication here
console.log(calculation('C')) // Cancel here
console.log(calculation('4')) // Do Number stuff here
console.log(calculation('=')) // Equals to

I think the advantage to this approach is that it keeps your handler short and simple. Shorter functions are generally easier to work with and debug.

Edit2: The above calcMethods (rough around the edges) could be a module of their own to import, and given you are passing the values ‘C’, ‘=’ etc could they possibly be real names instead? ‘cancel’, ‘equals’ -> calcMethods.cancel or calcMethods.equals

As I say just thoughts, feel free to ignore.

1 Like

Very Good, thank you. On the FCC forum they’d suggested using a Shunting Method (named after a 3-way split in the Rail Road yards, but I wasn’t sure how to implement it until I saw your comments. I think between the 2 I can cobble together something more algorithmic than my word salad on the first pass. All very good and the way you explained it makes sense to me. Thank you again

1 Like