[Resolvido] Interpretar fórmula matemática que está como String

Bom dia galera, tudo certo ?? Bem, na minha aplicação tenho um xhtml com a forma de avaliação. Nela, o usuário pode escolher qual a forma que vai avaliar a sua turma naquela escola. Bem, daí que tenho um campo para o usuário digitar a fórmula que ele quer que seja avaliada a turma dele. Exemplo: Formula = (n1 + n2 + n3 + n4)/4; Até aí tudo bem…
No meu cadastro de notas, quando vou dar a nota para cada aluno em cada perído, por exemplo n1 = 1º bimestre, n2 = 2º bimestre… ele vai substituir a string ( no caso n1) pela nota informada no primeiro bimestre. Consegui fazer isso…
Só que agora estou com dificuldade em fazer com que seja interpretada a formula para um possível cálculo. Pesquisei e tals, e algumas pessoas já usaram o seguinte:

Binding binding = new Binding();
GroovyShell shell = new GroovyShell(binding);
System.out.println(shell.evaluate(formula))

Só que nao estou sabendo qual importação utilizar…Abaixo, como estou fazendo no meu bean:

    public void atualizarMediaFinal() {
        try {
            Integer codEscola = crudObj.getEscola().getCodigo();
            Integer codTurma = crudObj.getTurma().getCodigo();
            Integer codDisciplina = crudObj.getDisciplinas().getCodigo();
            Integer codAluno = crudObj.getAluno().getCodigo();
            Integer codMatricula = crudObj.getCodMatricula();
            String formula = turma.getEtapa().getFormaAvaliacao().getFormulaAvaliacao();

            if (formula.contains("np1")) {
                formula = formula.replace("np1", String.valueOf(1));
            }

            if (formula.contains("np2")) {
                formula = formula.replace("np2", String.valueOf(2));
            }

            if (formula.contains("np3")) {
                formula = formula.replace("np3", String.valueOf(3));
            }

            if (formula.contains("np4")) {
                formula = formula.replace("np4", String.valueOf(4));
            }
 } catch (Exception exception) {
            fatal("Problema no método atualizarMediaFinal");
            fatal(exception.getCause().getMessage());
        }
    }

Se alguém puder ajudar, agradeceria mto…Vlww

Isso eh bem facil usando engine de alguma linguagem dinamica, segue:

ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
		// pode usar javascript (nativo no java6+) ou 
		// pode usar groovy (precisa importar a lib groovy-all no projeto)
		ScriptEngine scriptEngine = scriptEngineManager
				.getEngineByName("javascript");

		String formula = "a = b; b = 10; a/b";

		// passa as variaveis para o codigo
		Bindings b = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
		// nome da variavel, valor da variavel
		b.put("a", 10);
		b.put("b", 20);

		Object resultado = scriptEngine.eval(formula);
		System.out.println(resultado);

Bom, sei que já postei isso aqui antes, mas posto de novo hehehe.

Eu fiz um projetinho justamente pra isso, avaliar uma expressão matemática dentro de uma String. Ele é bem simples, não interpreta funções nem nada, mas para o que você precisa ele resolve bem.

Segue o link: https://github.com/rodrigopsasaki/math-engine/

[quote=mauricioadl]Isso eh bem facil usando engine de alguma linguagem dinamica, segue:

[code]
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
// pode usar javascript (nativo no java6+) ou
// pode usar groovy (precisa importar a lib groovy-all no projeto)
ScriptEngine scriptEngine = scriptEngineManager
.getEngineByName(“javascript”);

	String formula = "a = b; b = 10; a/b";

	// passa as variaveis para o codigo
	Bindings b = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
	// nome da variavel, valor da variavel
	b.put("a", 10);
	b.put("b", 20);

	Object resultado = scriptEngine.eval(formula);
	System.out.println(resultado);

[/code][/quote]
Vou testar aki e depois posto o resultado…

[quote=Rodrigo Sasaki]Bom, sei que já postei isso aqui antes, mas posto de novo hehehe.

Eu fiz um projetinho justamente pra isso, avaliar uma expressão matemática dentro de uma String. Ele é bem simples, não interpreta funções nem nada, mas para o que você precisa ele resolve bem.

Segue o link: https://github.com/rodrigopsasaki/math-engine/[/quote]
Dae rodrigo, tdo certo ?? Cara, nao tem jeito de eu conseguir fazer com que interprete… Vou testar ae…Depois posto o resultado… Vlw…

[quote=mauricioadl]Isso eh bem facil usando engine de alguma linguagem dinamica, segue:

[code]
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
// pode usar javascript (nativo no java6+) ou
// pode usar groovy (precisa importar a lib groovy-all no projeto)
ScriptEngine scriptEngine = scriptEngineManager
.getEngineByName(“javascript”);

	String formula = "a = b; b = 10; a/b";

	// passa as variaveis para o codigo
	Bindings b = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
	// nome da variavel, valor da variavel
	b.put("a", 10);
	b.put("b", 20);

	Object resultado = scriptEngine.eval(formula);
	System.out.println(resultado);

[/code][/quote]
Cara, fiz da seguinte forma:

            Integer codEscola = crudObj.getEscola().getCodigo();
            Integer codTurma = crudObj.getTurma().getCodigo();
            Integer codDisciplina = crudObj.getDisciplinas().getCodigo();
            Integer codAluno = crudObj.getAluno().getCodigo();
            Integer codMatricula = crudObj.getCodMatricula();
            String formula = turma.getEtapa().getFormaAvaliacao().getFormulaAvaliacao();

            if (formula.contains("np1")) {
                formula = formula.replace("np1", String.valueOf(1));
            }

            if (formula.contains("np2")) {
                formula = formula.replace("np2", String.valueOf(2));
            }

            if (formula.contains("np3")) {
                formula = formula.replace("np3", String.valueOf(3));
            }

            if (formula.contains("np4")) {
                formula = formula.replace("np4", String.valueOf(4));
            }

            ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
            // pode usar javascript (nativo no java6+) ou
            // pode usar groovy (precisa importar a lib groovy-all no projeto)
            ScriptEngine scriptEngine = scriptEngineManager
                    .getEngineByName("javascript");

            // passa as variaveis para o codigo
            Bindings b = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);
            // nome da variavel, valor da variavel
            b.put("n1", 1);
            b.put("n2", 2);
            b.put("n3", 3);
            b.put("n4", 4);

            Object resultado = scriptEngine.eval(formula);
            System.out.println(resultado);

Até entao blz, passou os 2.5 msm, show de bola… Porém, eu já seto o valor na fórmula, certo ? Como eu poderia passar sem o nome da variável e o valor dela ( no caso o b.put)? Ou devo setar aí?? Vlw…

Cara, não quero ser insistente nem nada, mas sei lá, eu acho que seria muito mais fácil do jeito que sugeri, exemplo:[code]double nota1 = 8.0;
double nota2 = 7.5;
double nota3 = 5.0;
double nota4 = 9.5;

String expressao = “(” + nota1 + " + " + nota2 + " + " + nota3 + " + " + nota4 + “) / 4”;
double resultado = Engine.evaluate(expressao);[/code]

[quote=Rodrigo Sasaki]Cara, não quero ser insistente nem nada, mas sei lá, eu acho que seria muito mais fácil do jeito que sugeri, exemplo:[code]double nota1 = 8.0;
double nota2 = 7.5;
double nota3 = 5.0;
double nota4 = 9.5;

String expressao = “(” + nota1 + " + " + nota2 + " + " + nota3 + " + " + nota4 + “) / 4”;
double resultado = Engine.evaluate(expressao);[/code][/quote]
Vamos lá entao Rodrigo… Sempre responde minhas dúvidas…suhauhas
Seguinte: tipo assim, o usuário pode digitar ( fazer) a fórmula para sua propria turma, entendeu ? Daí que eu pego a fórmula conforme a turma que estou trabalhando…No cadastro da forma de avaliação tem a explicação pro cristão digitar np1 para nota do bimestre 1 , np2 para nota do periodo 2 e assim sucessivamente…
Nas minhas notas, mais expecificamente no método para calcular a médiaFinal, devo pegar a fórmula conforme a turma que estou trabalhando e começar a substituir…
No caso, ali estou passando valores fixos, mas vou ter que buscar os valores por sql no meu BD…

String formula = turma.getEtapa().getFormaAvaliacao().getFormulaAvaliacao();

            if (formula.contains("np1")) {
                formula = formula.replace("np1", String.valueOf(1));
            }

            if (formula.contains("np2")) {
                formula = formula.replace("np2", String.valueOf(2));
            }

            if (formula.contains("np3")) {
                formula = formula.replace("np3", String.valueOf(3));
            }

            if (formula.contains("np4")) {
                formula = formula.replace("np4", String.valueOf(4));
            }

Deu pra entender +/- ? ( nunca fui bom em explicar as coisas :frowning: ) Poderia dar uma ajuda pra ver como ficaria do seu jeito entao?? Vlww cara…

Entendi, o problema não é processar a expressão então. A expressão varia… então acho que você está em um caminho bom já :slight_smile:

Na vdd, da maneira que estou fazendo já está funcionando… agora to vendo como setar o resultado em um campo BigDecimal…

            Integer codEscola = crudObj.getEscola().getCodigo();
            Integer codTurma = crudObj.getTurma().getCodigo();
            Integer codDisciplina = crudObj.getDisciplinas().getCodigo();
            Integer codAluno = crudObj.getAluno().getCodigo();
            Integer codMatricula = crudObj.getCodMatricula();
            String formula = turma.getEtapa().getFormaAvaliacao().getFormulaAvaliacao();

            if (formula.contains("np1")) {
                formula = formula.replace("np1", String.valueOf(getCrudService().executeNativeQuery("select (NOT_NOTA) AS NotaPeriodo FROM EDU_NOTAS n"
                        + " WHERE n.not_codescola=?1 and n.NOT_CODALUNO =?2 AND n.NOT_CODDISCIPLINA =?3"
                        + " AND n.NOT_ANO =?4 AND n.NOT_PERIODO =1 and n.not_codturma =?5", codEscola, codAluno, codDisciplina, ano, codTurma)));
            }

            if (formula.contains("np2")) {
                formula = formula.replace("np2", String.valueOf(getCrudService().executeNativeQuery("select (NOT_NOTA) AS NotaPeriodo FROM EDU_NOTAS n"
                        + " WHERE n.not_codescola=?1 and n.NOT_CODALUNO =?2 AND n.NOT_CODDISCIPLINA =?3"
                        + " AND n.NOT_ANO =?4 AND n.NOT_PERIODO =2 and n.not_codturma =?5", codEscola, codAluno, codDisciplina, ano, codTurma)));
            }

            if (formula.contains("np3")) {
                formula = formula.replace("np3", String.valueOf(getCrudService().executeNativeQuery("select (NOT_NOTA) AS NotaPeriodo FROM EDU_NOTAS n"
                        + " WHERE n.not_codescola=?1 and n.NOT_CODALUNO =?2 AND n.NOT_CODDISCIPLINA =?3"
                        + " AND n.NOT_ANO =?4 AND n.NOT_PERIODO =3 and n.not_codturma =?5", codEscola, codAluno, codDisciplina, ano, codTurma)));
            }

            if (formula.contains("np4")) {
                formula = formula.replace("np4", String.valueOf(getCrudService().executeNativeQuery("select (NOT_NOTA) AS NotaPeriodo FROM EDU_NOTAS n"
                        + " WHERE n.not_codescola=?1 and n.NOT_CODALUNO =?2 AND n.NOT_CODDISCIPLINA =?3"
                        + " AND n.NOT_ANO =?4 AND n.NOT_PERIODO =4 and n.not_codturma =?5", codEscola, codAluno, codDisciplina, ano, codTurma)));
            }

            ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
            ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript");
            Object resultado = scriptEngine.eval(formula);
            System.err.println("Resultado da Média : " + resultado);
            BigDecimal resultadoConvertido = new BigDecimal((BigInteger) resultado);
            crudObj.setNota(resultadoConvertido);

Obs: o setNota é um campo BIGDECIMAL…
Está dando erro de conversão…Poderia dar uma mão aí?? Vlww

O que esse avaliador retorna? Você tem que ver o tipo real do objeto, e ver como instanciar o BigDecimal.

Se retornar um double, int, long ou String é só jogar no construtor

Depois que o Ricardo me mostrou o problema dele, acabei pensando numa forma de fazê-lo ainda mais simples e conciso, pelo menos a parte de substituir as notas. Daí lembrei do uso de Expressões Regulares. Elas vão acabar com aqueles montes de IFs e ainda possibilitar que ele reutilize o programa para um número genérico de NOTAS. Abaixo vai um código de exemplo comentado.

[code] // Sua formula original, contendo os "NPx"
String formula = “(np1+np2+np3+np4)/4”;

  // A expressão regular que você quer encontrar ("NPx")
  String REGEX = "(np\\d)";
  
  // As notas que você vai querer inserir na fórmula. Pode ser qualquer uma Collection qualquer. Aqui usei o Array básico pra exemplo
  Double[] notas = {9.5, 9.3, 8.0, 8.8};
  
  //A nova string (no caso StringBuffer) que vai receber a substituição
  StringBuffer novaFormula = new StringBuffer();

  // Um buscador de expressão regular que posteriormente vai fazer a troca
  Matcher buscador = Pattern.compile(REGEX).matcher(formula);
  
  int i = 0;
  //Iterativamente encontra o próximo "NPx"
  while (buscador.find()) {
     // Substitui o "NPx" por notas[i]
     buscador.appendReplacement(novaFormula, String.valueOf(notas[i++]));
     //i++;
  }
  //Copia o resto que aparece depois do último "NPx"
  buscador.appendTail(novaFormula);

[/code]

No caso desse exemplo, a saída da novaFormula é “(9.5+9.3+8.0+8.8)/4”

Depois disso parte pra avaliação normal da fórmula que o pessoal já sugeriu.
Um abraço.

[quote=aadario]Depois que o Ricardo me mostrou o problema dele, acabei pensando numa forma de fazê-lo ainda mais simples e conciso, pelo menos a parte de substituir as notas. Daí lembrei do uso de Expressões Regulares. Elas vão acabar com aqueles montes de IFs e ainda possibilitar que ele reutilize o programa para um número genérico de NOTAS. Abaixo vai um código de exemplo comentado.

[code] // Sua formula original, contendo os "NPx"
String formula = “(np1+np2+np3+np4)/4”;

  // A expressão regular que você quer encontrar ("NPx")
  String REGEX = "(np\\d)";
  
  // As notas que você vai querer inserir na fórmula. Pode ser qualquer uma Collection qualquer. Aqui usei o Array básico pra exemplo
  Double[] notas = {9.5, 9.3, 8.0, 8.8};
  
  //A nova string (no caso StringBuffer) que vai receber a substituição
  StringBuffer novaFormula = new StringBuffer();

  // Um buscador de expressão regular que posteriormente vai fazer a troca
  Matcher buscador = Pattern.compile(REGEX).matcher(formula);
  
  int i = 0;
  //Iterativamente encontra o próximo "NPx"
  while (buscador.find()) {
     // Substitui o "NPx" por notas[i]
     buscador.appendReplacement(novaFormula, String.valueOf(notas[i++]));
     //i++;
  }
  //Copia o resto que aparece depois do último "NPx"
  buscador.appendTail(novaFormula);

[/code]

No caso desse exemplo, a saída da novaFormula é “(9.5+9.3+8.0+8.8)/4”

Depois disso parte pra avaliação normal da fórmula que o pessoal já sugeriu.
Um abraço.
[/quote]
Eu pensei nisso também, o único problema é se a fórmula colocar n4 antes de n2 por exemplo, por qualquer que seja o motivo

[quote=aadario]Depois que o Ricardo me mostrou o problema dele, acabei pensando numa forma de fazê-lo ainda mais simples e conciso, pelo menos a parte de substituir as notas. Daí lembrei do uso de Expressões Regulares. Elas vão acabar com aqueles montes de IFs e ainda possibilitar que ele reutilize o programa para um número genérico de NOTAS. Abaixo vai um código de exemplo comentado.

[code] // Sua formula original, contendo os "NPx"
String formula = “(np1+np2+np3+np4)/4”;

  // A expressão regular que você quer encontrar ("NPx")
  String REGEX = "(np\\d)";
  
  // As notas que você vai querer inserir na fórmula. Pode ser qualquer uma Collection qualquer. Aqui usei o Array básico pra exemplo
  Double[] notas = {9.5, 9.3, 8.0, 8.8};
  
  //A nova string (no caso StringBuffer) que vai receber a substituição
  StringBuffer novaFormula = new StringBuffer();

  // Um buscador de expressão regular que posteriormente vai fazer a troca
  Matcher buscador = Pattern.compile(REGEX).matcher(formula);
  
  int i = 0;
  //Iterativamente encontra o próximo "NPx"
  while (buscador.find()) {
     // Substitui o "NPx" por notas[i]
     buscador.appendReplacement(novaFormula, String.valueOf(notas[i++]));
     //i++;
  }
  //Copia o resto que aparece depois do último "NPx"
  buscador.appendTail(novaFormula);

[/code]

No caso desse exemplo, a saída da novaFormula é “(9.5+9.3+8.0+8.8)/4”

Depois disso parte pra avaliação normal da fórmula que o pessoal já sugeriu.
Um abraço.
[/quote]
Daí professor? Tdo certo?? Agora modifiquei isso… A minha solução ficou da seguinte maneira:

  public boolean verificaNumeroPeriodos() {
        int n = -1;
        int nroVezes = 0;
        String formula = turma.getEtapa().getFormaAvaliacao().getFormulaAvaliacao().toLowerCase();
        for (;; ++nroVezes) {
            n = formula.indexOf("np", n + 1);
            if (n == -1) {
                break;
            }
        }
        if (turma.getEtapa().getFormaAvaliacao().getNumeroPeriodos() == nroVezes) {
            return true;
        } else {
            return false;
        }
    }

.......

        String formula = turma.getEtapa().getFormaAvaliacao().getFormulaAvaliacao();

        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
        ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript");
        Bindings bindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE);

        for (int i = 1; i <= turma.getEtapa().getFormaAvaliacao().getNumeroPeriodos(); i++) {
            Object nota = getCrudService().executeNativeQuerySingleResult("select (NOT_NOTA) AS NotaPeriodo FROM EDU_NOTAS n"
                    + " WHERE n.not_codescola=?1 and n.NOT_CODALUNO =?2 AND n.NOT_CODDISCIPLINA =?3"
                    + " AND n.NOT_ANO =?4 AND n.NOT_PERIODO =?6 and n.not_codturma =?5", codEscola, codAluno, codDisciplina, ano, codTurma, i);
            bindings.put("np" + i, nota);
        }

        Object resultado = scriptEngine.eval(formula);
        BigDecimal resultadoMedia = new BigDecimal(((Number) resultado).doubleValue());

Oque acontece: ele vai pegar a qtde de períodos informados… Por exemplo, se eu tenho 4 Bimestres e o cara colocar n1+n2+n3+n4+n5…Daí n vai conseguir…Mas funcinou e está funcionando bem aki com os testes… Vlww abc

[quote=Rodrigo Sasaki]
Eu pensei nisso também, o único problema é se a fórmula colocar n4 antes de n2 por exemplo, por qualquer que seja o motivo[/quote]

Mas daí, resolvemos assim: como ele sempre vai ter as notas indicadas por um número, posso pegar a string que representa o número como um grupo na expressão regular:

[code] String formula = “(np4+np2+np3+np1)/4”;
String REGEX = “np(\d)”;
Double[] notas = {9.5, 9.3, 8.0, 8.8};

  StringBuffer novaFormula = new StringBuffer();

  Matcher buscador = Pattern.compile(REGEX).matcher(formula);
  
  while (buscador.find()) {
     int indice = Integer.parseInt(buscador.group(1)) - 1;
     buscador.appendReplacement(novaFormula, String.valueOf(notas[indice]));
  }
  buscador.appendTail(novaFormula);

[/code]

A vantagem de usar o Matcher é que você utiliza um processador de expressões regulares nativo do Java e não do processador de scripts do Javascript.