Equation solving

Asked

Viewed 1,855 times

6

I created a Cartesian plan and I get an equation from the user. My goal is to draw this equation in the plane. I’m doing this using canvas. Only here comes a problem, I can only do this if I separate each type of equation (circle, straight...). What I want is for the user to put any kind of equation, of 2 variables (x and y). Then I take and put in the 'x' values from -100 to 100. That is, at the end of the day, I’m going to have an equation with only one variable (y). Except I can’t generalize these equations.

As I make an algorithm so that regardless of the way the user inserts, I isolate the 'y' from the equation?

Example 1: 3²/4 + y²/25 = 1
Example 2: 4² - 6 = (y-1)²

It must return the value of y.

Anyone who can help, thank you!
//JAVASCRIPT, but I accept PHP or C++ to have as a basis for me to do in javascript (without using framework in any of these)

  • 2

    There is no generalized way of solving equations from third degree upward. His examples are both second degree, so the Bhaskara’s formula is sufficient. In this case, it is only a matter of interpreting and manipulating the user input. In what format will this input be made? Can you give some examples? (type, how the user would enter with "a raised to b"?)

  • 1

    it will enter as follows: x 2 + 3*y 2 = 12. Where x I will fill in the program with values from -100 to 100. There’s a website that does exactly what I want: http://www.wolframalpha.com/

  • 1

    Dude, comparing wolframalpha to anything we poor mortals are capable of is bullshit... P

  • 2

    Very relevant to the problem: http://www.cs.cornell.edu/w8/~Andru/relplot/ I contacted you requesting the source code. If accepted, I will post an answer explaining how the same thing can be done with general equations and not just polynomials.

1 answer

6


If after the replacement of x the result is an equation up to 2 degrees in y, then it can be solved by Bhaskara’s formula:

a*y^2 + b*y + c = 0

y = -b +- raiz(b^2 - 4*a*c)
    -----------------------
              2*a

The trickiest thing is interpret the entrance to... You need to convert it from the text format to an abstract format, which allows you to manipulate the elements (multiplication of terms, playing terms from right to left with reversed sign, etc.) until isolating a, b and c.

The ideal is to use some parser able to interpret its desired format. In this answer, I will give a very simple suggestion, without error treatment or anything, but still able to interpret simple equations as your example.

Lexical analysis

Basically, a sequence of digits is a number (which I will call c - "constant"), x is x and y is y. Spaces are ignored, and operators are =, + e -, * e / and ^, in that order of precedence. Parentheses are a special case...

  function token() {
    var numero = /^-?\d+/.exec(s);
    if ( numero ) {
      s = s.substring(numero[0].length);
      return variavel(0,0,parseInt(numero[0], 10)); // x^0 * y^0 * c
    }

    var simbolo = s[0];
    s = s.substring(1);

    if ( simbolo == "x" )
      return variavel(1); // x^1 * y^0 * 1
    if ( simbolo == "y" )
      return variavel(0,1); // x^0 * y^1 * 1
    if ( operadores[simbolo] )
        return operadores[simbolo]();
    if ( simbolo == " " )
      return token(); // Ignora o espaço em branco

    throw "Formato inválido";
  }

Separate text in tokens is simply the case to go withdrawing tokens from the beginning of the string until it ends:

function lexica(str) {
  var s = str;
  function token() { ... }

  var tokens = [];
  while ( s.length > 0 )
    tokens.push(token());
  return tokens;
}

Syntactic analysis

Assuming that variables and constants take precedence 0, exponentiation 1, multiplication 2, etc (i.e. a*b^c is a*(b^c) and not (a*b)^c), if we read the tokens from left to right, and we decide who goes in who, let’s end with a tree of expressions and sub-expressions:

function sintatica(tokens, i) {
  var ret = undefined;
  i = i || 0;
  for ( ; i < tokens.length ; i++ ) {
    if ( !ret || tokens[i].p >= ret.p ) { // Se o próximo da lista tem precedência maior
      tokens[i].esq = ret;                // a antiga raiz entra na esquerda dele
      ret = tokens[i];                    // e ele vira a raiz
    }
    else { // Senão, ele é inserido na direita da raiz

      // mas se alguém na direita tem precedência menor que ele
      for (var pai = ret ; pai.dir && pai.dir.p > tokens[i].p ; pai = pai.dir) { }
      tokens[i].esq = tokens[i].esq || pai.dir; // então ele o coloca à sua esquerda
      pai.dir = tokens[i];                      // e toma o lugar dele
    }
  }
  return [ret,i]; // No fim todos os tokens vão formar uma árvore, na precedência certa
}

This is a method well simple to interpret an expression. It associates to the right always, while not every operation does it (power, for example, associates to the left). Again, I suggest looking for a parser more complete to make your system more robust.

That said, let’s finally get to the point of the question:

Replacing the x

In the code above, I defined a type variavel which is implemented as follows:

function variavel(x,y,c) {
  return {
    x:x || 0, // O expoente do x, padrão 0
    y:y || 0, // O expoente do y, padrão 0
    c:c || 1, // Uma constante, padrão 1
    p:0,      // A precedência é zero (não é um operador)

That is to say, x flipped {x:1,y:0,c:1}, y^2 flipped {x:0,y:2,c:1} and 42 flipped {x:0,y:0,c:42}. You can mix, too, like, 42*x*y^2 flipped {x:1,y:2,c:42 }, since the x is nothing more than 1 (in this representation; in the user input, can). Representing the variables in this way, replace the x by a value is simply a matter of multiplying the c at the value of x, and pass the xof 1 for 0 (if there was no x, the value does not change).

    substitui:function(x) { this.c *= this.x ? x : 1; this.x = 0; },

In operators, simply replace the left and right side independently...

function substitui(x) {
  this.esq.substitui(x);
  this.dir.substitui(x);
}

Finding the plots

After replacing the x, we want to find the plots and pass them all to the left. In some cases it is easy:

x^2 + 3*y^2 = 12 [x = 2]
==>
4 + 3*y^2 + (-12) = 0

In others it’s harder:

(y + 1)^2
==>
(y + 1)*(y + 1)
(y*y) + (y*1) + (1*y) + (1*1)

The solution is to make each operator have a function to turn their arguments into a list of plots. A variable/constant is a single parcel:

function variavel(x,y,c) {
    soma:function() { return [this]; },

A sum is simply the set of plots on the left plus the set of plots on the right:

var operadores = {
  "+":function(){
    return {
      p:3,
      substitui:substitui,
      soma:function() { return this.esq.soma().concat(this.dir.soma()); }
    };
  },

Multiplication has to multiply each portion of the left with each portion of the right:

  "*":function(){
    return {
      p:2,
      substitui:substitui,
      soma:function() {
        var esq = this.esq.soma(); // Acha todas as parcelas da esquerda
        var dir = this.dir.soma(); // e todas da direita

        var ret = [];
        for ( var i = 0 ; i < esq.length ; i++ )   // Cada termo da esquerda
          for ( var t = 0 ; t < dir.length ; t++ ) // Vezes cada termo da direita
            ret.push(esq[i].vezes(dir[t]));
        return ret;
      }
    };
  },

etc. Finally, the operator = have to pass everything that is right to left, with the reversed signal:

  "=":function(){
    return {
      p:4,
      substitui:substitui,
      soma:function() {
        var ret = this.esq.soma();
        var dir = this.dir.soma();
        for ( var i = 0 ; i < dir.length ; i++ ) {
          dir[i].c = -dir[i].c; // Passa pra esquerda, trocando o sinal
          ret.push(dir[i]);
        }
        return ret;
      }
    };
  },

Bhaskara

Finally, only apply the formula mentioned at the beginning of the reply:

function avaliar(equacao, x) {
  var raiz = sintatica(lexica(equacao))[0]; // Interpreta
  raiz.substitui(x); // Substitui o x
  var termos = raiz.soma(); // Transforma numa soma de termos

  // Bhaskara
  var a = 0, b = 0, c = 0;
  for ( var i = 0 ; i < termos.length ; i++ ) {
    if ( termos[i].y == 0 )
      c += termos[i].c;
    else if ( termos[i].y == 1 )
      b += termos[i].c;
    else if ( termos[i].y == 2 )
      a += termos[i].c;
    else
      throw "Equação maior que segundo grau!";
  }

  if ( a == 0 )
    return [-c/b] // Equação de primeiro grau

  var rdelta = Math.sqrt(b*b - 4*a*c);
  return [(-b + rdelta)/(2*a), (-b - rdelta)/(2*a)];
}

Full example:

Joining the above code, with one or two extra things (the missing operators, and the parentheses - which have zero precedence, but "capture" everything within them), we have a complete example:

function substitui(x) {
  this.esq.substitui(x);
  this.dir.substitui(x);
}

var operadores = {
  "=":function(){
    return {
      p:4,
      substitui:substitui,
      soma:function() {
        var ret = this.esq.soma();
        var dir = this.dir.soma();
        for ( var i = 0 ; i < dir.length ; i++ ) {
          dir[i].c = -dir[i].c; // Passa pra esquerda, trocando o sinal
          ret.push(dir[i]);
        }
        return ret;
      }
    };
  },
  "+":function(){
    return {
      p:3,
      substitui:substitui,
      soma:function() { return this.esq.soma().concat(this.dir.soma()); }
    };
  },
  "-":function(){
    return {
      p:3,
      substitui:substitui,
      soma:function() {
        var ret = this.esq.soma();
        var dir = this.dir.soma();
        for ( var i = 0 ; i < dir.length ; i++ ) {
          dir[i].c = -dir[i].c; // troca o sinal
          ret.push(dir[i]);
        }
        return ret;
      }
    };
  },
  "*":function(){
    return {
      p:2,
      substitui:substitui,
      soma:function() {
        var esq = this.esq.soma();
        var dir = this.dir.soma();
        var ret = [];
        for ( var i = 0 ; i < esq.length ; i++ )   // Cada termo da esquerda
          for ( var t = 0 ; t < dir.length ; t++ ) // Vezes cada termo da direita
            ret.push(esq[i].vezes(dir[t]));
        return ret;
      }
    };
  },
  "/":function(){
    return {
      p:1,
      substitui:substitui,
      soma:function() {
        var ret = this.esq.soma();
        var divisor = this.dir.c;
        for ( var i = 0 ; i < ret.length ; i++ )
            ret[i].c /= divisor;
        return ret;
      }
    };
  },
  "^":function(){
    return {
      p:1,
      substitui:substitui,
      soma:function() {
        var esq = this.esq.soma();
        var potencia = this.dir.c;
        var ret = esq;
        while ( potencia-- > 1 ) { // Cada termo da esquerda multiplica com
          var tmp = ret;           // cada termo da esquerda n vezes
          var ret = [];
          for ( var i = 0 ; i < tmp.length ; i++ )
            for ( var t = 0 ; t < esq.length ; t++ )
              ret.push(tmp[i].vezes(esq[t]));
        }
        return ret;
      }
    };
  },
  "(":function(){
    return { par:"(" };
  },
  ")":function(){
    return { par:")" };
  }
};

function variavel(x,y,c) {
  return {
    x:x || 0,
    y:y || 0,
    c:c || 1,
    p:0,
    substitui:function(x) { this.c *= this.x ? x : 1; this.x = 0; },
    soma:function() { return [this]; },
    vezes:function(v) {
      return variavel(this.x + v.x, this.y + v.y, this.c * v.c);
    }
  }
}

function lexica(str) {
  var s = str;
  
  function token() {
    var numero = /^\d+/.exec(s);
    if ( numero ) {
      s = s.substring(numero[0].length);
      return variavel(0,0,parseInt(numero[0], 10));
    }
    
    var simbolo = s[0];
    s = s.substring(1);
    
    if ( simbolo == "x" )
      return variavel(1);
    if ( simbolo == "y" )
      return variavel(0,1);
    if ( operadores[simbolo] )
        return operadores[simbolo]();
    if ( simbolo == " " )
      return token(); // Ignora o espaço em branco
      
    throw "Formato inválido";
  }
  
  var tokens = [];
  while ( s.length > 0 )
    tokens.push(token());
  return tokens;
}

function sintatica(tokens, i) {
  var ret = undefined;
  i = i || 0;
  for ( ; i < tokens.length ; i++ ) {
    // Parênteses "quebram" a lógica da precedência...
    if ( tokens[i].par == ')' )
        break;
    if ( tokens[i].par == '(' ) {
        var conteudo = sintatica(tokens, i+1);
        i = conteudo[1];
        tokens[i] = conteudo[0];
        tokens[i].p = 0;
    }
    //
      
    if ( !ret || tokens[i].p >= ret.p ) {
      tokens[i].esq = ret;
      ret = tokens[i];
    }
    else {
      for (var pai = ret ; pai.dir && pai.dir.p > tokens[i].p ; pai = pai.dir) { }
      tokens[i].esq = tokens[i].esq || pai.dir;
      pai.dir = tokens[i];
    }
  }
  return [ret,i];
}

function avaliar(equacao, x) {
  var raiz = sintatica(lexica(equacao))[0]; // Interpreta
  raiz.substitui(x); // Substitui o x
  var termos = raiz.soma(); // Transforma numa soma de termos
    
  // Bhaskara
  var a = 0, b = 0, c = 0;
  for ( var i = 0 ; i < termos.length ; i++ ) {
    if ( termos[i].y == 0 )
      c += termos[i].c;
    else if ( termos[i].y == 1 )
      b += termos[i].c;
    else if ( termos[i].y == 2 )
      a += termos[i].c;
    else
      throw "Equação maior que segundo grau!";
  }
  
  if ( a == 0 )
    return [-c/b] // Equação de primeiro grau
  
  var rdelta = Math.sqrt(b*b - 4*a*c);
  return [(-b + rdelta)/(2*a), (-b - rdelta)/(2*a)];
}

/***** Exemplos *****/

var exemplos = ["x^2 + 3*y^2 = 12", 
                "x^2/4 + y^2/25 = 1",
                "x^2 - 6 = (y-1)^2",
                "4*x + 5*y = 15"];

for ( var e = 0 ; e < exemplos.length ; e++ ) {

  var entrada = exemplos[e]; 
  
  document.body.innerHTML += "<p><strong>" + entrada + "</strong></p>";

  for ( var i = 0 ; i <= 5 ; i++ )
    document.body.innerHTML += "<p>x = " + i + ", y = " +
                                   JSON.stringify(avaliar(entrada,i)) + 
                               "</p>";
}

As already said, this parser handmade is not very good, but it should be enough to demonstrate the logic necessary to deal with the equations, which is the focus of the question.

I leave the bug fix when x == 0 as an exercise for the reader... P

  • I had started sketching something like this, but I saw that it would take a lot of work to do everything I wanted.. When I thought about the problem I thought it would be simpler, so much so that I already have the plotter and graph ready (just need to interpret the equation) rsrs. Plus, thanks for the help!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.