Bir dizeyi JavaScript'te matematiksel bir ifade olarak değerlendirme


84

Bir dizedeki matematiksel bir ifadeyi sayısal değerini elde etmek için '1+1'çağırmadan nasıl ayrıştırabilir ve değerlendirebilirim (örneğin ) eval(string)?

Bu örnekle, işlevin kabul etmesini '1+1've geri dönmesini istiyorum 2.


5
Çok benzer ama sizin için ne soruyorsun muhtemelen değil: (Function("return 1+1;"))().
Gumbo

Yanıtlar:



23

+ Veya - işlemini kolayca yapabilirsiniz:

function addbits(s) {
  var total = 0,
      s = s.match(/[+\-]*(\.\d+|\d+(\.\d+)?)/g) || [];
      
  while (s.length) {
    total += parseFloat(s.shift());
  }
  return total;
}

var string = '1+23+4+5-30';
console.log(
  addbits(string)
)

Daha karmaşık matematik, değerlendirmeyi daha çekici ve kesinlikle yazmayı daha kolay hale getirir.


2
+1 - Muhtemelen benim kullandığımdan biraz daha genel, ancak 1 + -2 gibi bir şeye sahip olabileceğimden durumum için işe yaramayacak ve normal ifadenin geçersiz ifadeleri de hariç tutmasını istiyorum (sanırım seninki izin verir gibi bir şey "+ 3 + 4 +")
wheresrhys

Aşağıda, daha kısa bir normal ifade ile ve operatörler arasındaki boşluklara izin veren güncellenmiş bir cevabı yayınladım
Stefan Gabos

17

Birinin bu dizeyi çözümlemesi gerekiyor. Eğer yorumlayıcı (aracılığıyla eval) değilse, o zaman sizin olmanız, sayıları, operatörleri ve matematiksel bir ifadede desteklemek istediğiniz her şeyi ayıklamak için bir ayrıştırma rutini yazmanız gerekecektir.

Yani hayır, olmadan (basit) bir yol yoktur eval. (Giriş Sen ayrıştırma kontrol bir kaynaktan değil çünkü) Güvenlik konusunda endişe ediyorsanız, belki geçirmeden önce (beyaz liste regex filtresi aracılığıyla) girişin formatını kontrol edebilirsiniz eval?


1
Beni rahatsız eden güvenlik değil (iş için zaten bir regexp'im var), bunun gibi birçok dizeyi işlemem gerektiğinden tarayıcıya daha çok yük bindiriyor. Özel bir ayrıştırıcı, eval () 'den daha hızlı olabilir mi?
wheresrhys

11
@wheresrhys: Neden JS ile yazılmış ayrıştırıcınızın sistemden daha hızlı olacağını düşünüyorsunuz (optimize edilmiş, muhtemelen C veya C ++ ile yazılmış)?
mmx

4
eval bunu yapmanın açık arayla en hızlı yoludur. Ancak, bir normal ifade genellikle güvenliği sağlamak için yeterli değildir.
levik

1
@wheresrhys: Neden böyle bir sürü dizginiz var? Bir program tarafından mı üretiliyorlar? Öyleyse, en basit yol, sonucu dizelere dönüştürülmeden önce hesaplamaktır. Aksi takdirde, kendi ayrıştırıcınızı yazma zamanıdır.
Phil H

13

Daha kısa bir düzenli ifade kullanarak ve operatörler arasında boşluk bırakarak @kennebec'in mükemmel cevabına bir alternatif

function addbits(s) {
    var total = 0;
    s = s.replace(/\s/g, '').match(/[+\-]?([0-9\.\s]+)/g) || [];
    while(s.length) total += parseFloat(s.shift());
    return total;
}

Gibi kullan

addbits('5 + 30 - 25.1 + 11');

Güncelleme

İşte daha optimize bir sürüm

function addbits(s) {
    return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || [])
        .reduce(function(sum, value) {
            return parseFloat(sum) + parseFloat(value);
        });
}

1
Bu, sadece toplama ve çıkarma işlemine ihtiyacınız olduğu sürece mükemmeldir. Çok az kod, çok fazla ürün!
İçiniz

10

Oluşturduğum BigEval aynı amaçla.
İfadeleri çözerken, Eval()%, ^, &, ** (güç) ve! Gibi operatörlerle tam olarak aynı performansı gösterir ve bunları destekler. (faktöryel). İfadenin içinde fonksiyonları ve sabitleri (veya değişkenleri) kullanmanıza da izin verilir. İfade, JavaScript dahil programlama dillerinde yaygın olan PEMDAS sırasına göre çözülür .

var Obj = new BigEval();
var result = Obj.exec("5! + 6.6e3 * (PI + E)"); // 38795.17158152233
var result2 = Obj.exec("sin(45 * deg)**2 + cos(pi / 4)**2"); // 1
var result3 = Obj.exec("0 & -7 ^ -7 - 0%1 + 6%2"); //-7

Rastgele hassasiyetle sayılarla uğraşmanız durumunda, aritmetik için bu Büyük Sayı kitaplıklarını kullanmak da yapılabilir.


8

Matematiksel ifadeleri değerlendirmek için JavaScript kitaplıkları aramaya gittim ve şu iki umut verici adayı buldum:

  • JavaScript İfade Değerlendiricisi : Daha küçük ve umarız daha hafiftir. Cebirsel ifadelere, ikamelere ve bir dizi fonksiyona izin verir.

  • mathjs : Karmaşık sayılara, matrislere ve birimlere de izin verir. Hem tarayıcı içi JavaScript hem de Node.js tarafından kullanılmak üzere oluşturulmuştur.


Şimdi JavaScript İfade Değerlendiricisini test ettim ve harika görünüyor. (Mathjs de muhtemelen harika, ancak benim amaçlarım için biraz fazla büyük görünüyor ve ayrıca JSEE'deki ikame işlevselliğini de seviyorum.)
Itangalo

7

Bunu yakın zamanda C # 'da ( Eval()bizim için hayır ...) İfadeyi Reverse Polish Notation'da (bu kolay bit) değerlendirerek yaptım . İşin zor kısmı aslında dizgiyi ayrıştırıp Reverse Polish Notation'a çevirmektir. Wikipedia ve sözde kodda harika bir örnek olduğu için Shunting Yard algoritmasını kullandım . Her ikisini de uygulamayı gerçekten kolay buldum ve henüz bir çözüm bulamadıysanız veya alternatifler arıyorsanız bunu tavsiye ederim.


Wikipedia'ya bir örnek veya bağlantı verebilir misiniz?
LetynSOFT

@LetynSOFT Sözde kod burada
Mayonnaise2124

6

Bu, şu anda bu sorunu çözmek için bir araya getirdiğim küçük bir işlev - dizgeyi bir seferde bir karakter analiz ederek ifadeyi oluşturur (aslında oldukça hızlıdır). Bu, herhangi bir matematiksel ifadeyi alır (yalnızca +, -, *, / operatörlerle sınırlıdır) ve sonucu döndürür. Negatif değerleri ve sınırsız sayıda işlemi de halledebilir.

Yapılması gereken tek şey, + & --'dan önce * & / hesapladığından emin olmaktır. Bu işlevi daha sonra ekleyeceğim, ancak şimdilik ihtiyacım olan şeyi yapıyor ...

/**
* Evaluate a mathematical expression (as a string) and return the result
* @param {String} expr A mathematical expression
* @returns {Decimal} Result of the mathematical expression
* @example
*    // Returns -81.4600
*    expr("10.04+9.5-1+-100");
*/ 
function expr (expr) {

    var chars = expr.split("");
    var n = [], op = [], index = 0, oplast = true;

    n[index] = "";

    // Parse the expression
    for (var c = 0; c < chars.length; c++) {

        if (isNaN(parseInt(chars[c])) && chars[c] !== "." && !oplast) {
            op[index] = chars[c];
            index++;
            n[index] = "";
            oplast = true;
        } else {
            n[index] += chars[c];
            oplast = false;
        }
    }

    // Calculate the expression
    expr = parseFloat(n[0]);
    for (var o = 0; o < op.length; o++) {
        var num = parseFloat(n[o + 1]);
        switch (op[o]) {
            case "+":
                expr = expr + num;
                break;
            case "-":
                expr = expr - num;
                break;
            case "*":
                expr = expr * num;
                break;
            case "/":
                expr = expr / num;
                break;
        }
    }

    return expr;
}

4

İle sade ve zarif Function()

function parse(str) {
  return Function(`'use strict'; return (${str})`)()
}

parse("1+2+3"); 


lütfen nasıl çalıştığını açıklar mısınız? Bu sözdiziminde
yeniyim

İşlev ("dönüş (1 + 2 + 3)") (); - anonim bir işlevdir. Biz sadece argümanı (fonksiyon gövdesi) çalıştırıyoruz. İşlev ("{dönüş (1 + 2 + 3)}") ();
Aniket Kudale

tamam dize nasıl ayrıştırılır? & bu ($ {str}) ) -----() `bu köşeli parantez nedir?
pageNotfoUnd

Bunun nasıl değerlendirmekten daha iyi olduğunu anlamıyorum. Bu sunucu tarafını çalıştırmadan önce dikkatli olun parse('process.exit()').
Basti

3

Dizenin geçersiz karakter içerip içermediğini kontrol etmek için bir for döngüsü kullanabilir ve daha sonra, hesaplamanın eval("2++")yapacağı gibi bir hata verip vermediğini kontrol etmek için bir try ... catch kullanabilirsiniz .

function evaluateMath(str) {
  for (var i = 0; i < str.length; i++) {
    if (isNaN(str[i]) && !['+', '-', '/', '*', '%', '**'].includes(str[i])) {
      return NaN;
    }
  }
  
  
  try {
    return eval(str)
  } catch (e) {
    if (e.name !== 'SyntaxError') throw e
    return NaN;
  }
}

console.log(evaluateMath('2 + 6'))

veya bir işlev yerine, Math.eval

Math.eval = function(str) {
  for (var i = 0; i < str.length; i++) {
    if (isNaN(str[i]) && !['+', '-', '/', '*', '%', '**'].includes(str[i])) {
      return NaN;
    }
  }
  
  
  try {
    return eval(str)
  } catch (e) {
    if (e.name !== 'SyntaxError') throw e
    return NaN;
  }
}

console.log(Math.eval('2 + 6'))


2

Sonunda pozitif ve negatif tam sayıları toplamak için çalışan bu çözüme gittim (ve normal ifadede küçük bir değişiklik ile ondalık sayılar için de işe yarayacak):

function sum(string) {
  return (string.match(/^(-?\d+)(\+-?\d+)*$/)) ? string.split('+').stringSum() : NaN;
}   

Array.prototype.stringSum = function() {
    var sum = 0;
    for(var k=0, kl=this.length;k<kl;k++)
    {
        sum += +this[k];
    }
    return sum;
}

Eval () 'den daha hızlı mı emin değilim, ancak işlemi birçok kez gerçekleştirmem gerektiğinden, bu betiği çalıştırırken çok sayıda javascript derleyicisi oluşturmaktan çok daha rahatım


1
Her ne kadar returnbir ifadenin içine kullanılamaz, sum("+1")döner NaN .
Gumbo

Geri dönüşün üçlü bir ifadenin içine girip giremeyeceğini her zaman önceden tahmin edin. "+1" i hariç tutmak istiyorum çünkü bir sayı olarak "değerlendirilmesi" gerekse de, günlük anlamda matematiksel bir toplamın gerçekten bir örneği değil. Kodum, izin verilen dizeleri hem değerlendirmek hem de filtrelemek için tasarlanmıştır.
wheresrhys

2

deneyin nerdamer

var result = nerdamer('12+2+PI').evaluate();
document.getElementById('text').innerHTML = result.text();
<script src="http://nerdamer.com/js/nerdamer.core.js"></script>
<div id="text"></div>


2

Buna inanıyorum parseIntve ES6 bu durumda yardımcı olabilir

let func = (str) => {
  let arr = str.split("");
  return `${Number(arr[0]) + parseInt(arr[1] + Number(arr[2]))}`
};

console.log(func("1+1"));

Buradaki en önemli şey parseInt, numarayı operatörle ayrıştırmasıdır. Kod, ilgili ihtiyaçlara göre değiştirilebilir.


1

AutoCalculator'ı deneyin https://github.com/JavscriptLab/autocalculate değerini ve Çıkışı Hesapla Seçici ifadelerini kullanarak

Çıktı girdiniz için data-ac = "(# firstinput + # secondinput)" gibi bir özellik eklemeniz yeterlidir.

Herhangi bir başlatmaya gerek yok, sadece data-ac özelliğini ekleyin. Dinamik olarak eklenen öğeleri otomatik olarak bulacaktır

Veya Çıktı ile 'Rs' ekleyin, sadece küme parantez içine ekleyin data-ac = "{Rs} (# firstinput + # secondinput)"


1
const operatorToFunction = {
    "+": (num1, num2) => +num1 + +num2,
    "-": (num1, num2) => +num1 - +num2,
    "*": (num1, num2) => +num1 * +num2,
    "/": (num1, num2) => +num1 / +num2
}

const findOperator = (str) => {
    const [operator] = str.split("").filter((ch) => ["+", "-", "*", "/"].includes(ch))
    return operator;
}

const executeOperation = (str) => {
    const operationStr = str.replace(/[ ]/g, "");
    const operator = findOperator(operationStr);
    const [num1, num2] = operationStr.split(operator)
    return operatorToFunction[operator](num1, num2);
};

const addition = executeOperation('1 + 1'); // ans is 2
const subtraction = executeOperation('4 - 1'); // ans is 3
const multiplication = executeOperation('2 * 5'); // ans is 10
const division = executeOperation('16 / 4'); // ans is 4

1
Çıkarma, çarpma ve bölme ne olacak? Neden num1 ile çarpılır ?
nathanfranke

@Nathanfranke'yi işaret ettiğiniz için teşekkür ederiz. Daha genel hale getirmek için cevabı güncelledim. Şimdi 4 işlemi de destekliyor. Ve 1'e çarpmak onu dizeden sayıya dönüştürmekti. + Num yaparak da başarabiliriz.
Rushikesh Bharad

0

Burada jMichael'inkine benzer, karakter karakter ifade boyunca döngü yapan ve aşamalı olarak sol / operatör / sağı izleyen algoritmik bir çözüm var. İşlev, bir operatör karakteri bulduğu her dönüşten sonra sonucu toplar. Bu sürüm yalnızca '+' ve '-' operatörlerini destekler ancak diğer operatörlerle genişletilmek üzere yazılmıştır. Not: İfadenin pozitif bir float ile başladığını varsaydığımız için döngü yapmadan önce 'currOp'u' + 'olarak ayarlıyoruz. Aslında, genel olarak girdinin bir hesap makinesinden gelene benzer olduğunu varsayıyorum.

function calculate(exp) {
  const opMap = {
    '+': (a, b) => { return parseFloat(a) + parseFloat(b) },
    '-': (a, b) => { return parseFloat(a) - parseFloat(b) },
  };
  const opList = Object.keys(opMap);

  let acc = 0;
  let next = '';
  let currOp = '+';

  for (let char of exp) {
    if (opList.includes(char)) {
      acc = opMap[currOp](acc, next);
      currOp = char;
      next = '';
    } else {
      next += char;
    } 
  }

  return currOp === '+' ? acc + parseFloat(next) : acc - parseFloat(next);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.