JavaScriptでの数式としての文字列の評価


82

数値を生成するために'1+1'呼び出すことなく、文字列(たとえば)の数式を解析および評価するにはどうすればよいeval(string)ですか?

その例では、関数がを受け入れ'1+1'て返すようにし2ます。


5
非常に似ていますが、おそらくあなたが求めているものではありません:(Function("return 1+1;"))()
ガンボ2010

回答:



22

+または-を簡単に実行できます。

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)
)

より複雑な数学はevalをより魅力的にし、そして確かに書くのをより簡単にします。


2
+ 1-おそらく私が行ったものよりも少し一般的ですが、1 + -2のようなものがある可能性があるため、私の状況では機能しません。正規表現でも無効なステートメントを除外したいと思います(あなたが許可すると思います) 「+3 + 4 +」のようなもの)
wheresrhys 2010年

正規表現を短くし、演算子間のスペースを考慮した更新された回答を以下に投稿しました
Stefan Gabos 2017年

17

誰かがその文字列を解析する必要があります。それが(を介してeval)インタプリタでない場合は、数値、演算子、および数式でサポートしたいその他のものを抽出するための解析ルーチンを作成する、あなたである必要があります。

したがって、いいえ、eval。なしの(単純な)方法はありません。セキュリティが心配な場合(解析している入力が制御しているソースからのものではないため)、入力を渡す前に(ホワイトリストの正規表現フィルターを介して)入力の形式を確認できevalますか?


1
私を悩ませているのはセキュリティではありません(私はすでにジョブの正規表現を持っています)、このような多くの文字列を処理する必要があるため、ブラウザの負荷が高くなります。カスタムパーサーはeval()よりも実行可能に高速でしょうか?
wheresrhys 2010

11
@wheresrhys:JSで記述されたパーサーが、システムが提供するパーサー(最適化され、おそらくCまたはC ++で記述されている)よりも高速になると思うのはなぜですか?
Mehrdad Afshari 2010

4
evalは、これを行うための最速の方法です。ただし、通常、正規表現はセキュリティを確保するのに十分ではありません。
levik 2010

1
@wheresrhys:なぜこのような文字列がたくさんあるのですか?それらはプログラムによって生成されていますか?その場合、最も簡単な方法は、文字列に変換される前に結果を計算することです。それ以外の場合は、独自のパーサーを書き込む時間です。
Phil H

12

@kennebecによる優れた回答の代わりに、より短い正規表現を使用し、演算子間にスペースを入れる

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;
}

次のように使用します

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

更新

これがより最適化されたバージョンです

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

1
足し算と引き算だけが必要な限り、これは完璧です。非常に少ないコード、非常に多くの製品!安心して、それは
永久

10

同じ目的でBigEvalを作成しました。
式を解く際には、Eval()%、^、&、**(累乗)、!などの演算子とまったく同じように実行され、サポートされます。(階乗)。式内で関数と定数(または変数など)を使用することもできます。式は、JavaScriptを含むプログラミング言語で一般的なPEMDASの順序で解決されます。

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

任意の精度で数値を処理する場合は、これらのBigNumberライブラリを算術演算に使用することもできます。


8

数式を評価するためのJavaScriptライブラリを探しに行ったところ、次の2つの有望な候補が見つかりました。

  • JavaScript式エバリュエーター:より小さく、できればより軽量になります。代数式、置換、およびいくつかの関数を許可します。

  • mathjs:複素数、行列、単位も使用できます。ブラウザ内のJavaScriptとNode.jsの両方で使用するように構築されています。


JavaScript式エバリュエーターをテストしましたが、うまくいかないようです。(mathjsもおそらく揺れますが、私の目的には少し大きすぎるようで、JSEEの置換機能も気に入っています。)
Itangalo 2014

7

私は最近Eval()逆ポーランド記法で式を評価することにより、C#でこれを実行しました(これは簡単なことです)。難しいのは、実際に文字列を解析して逆ポーランド記法に変換することです。ウィキペディアと擬似コードに素晴らしい例があるので、私は操車場アルゴリズムを使用しました。両方を実装するのは本当に簡単だと思いました。まだ解決策が見つからない場合や代替案を探している場合は、これをお勧めします。


ウィキペディアの例やリンクを教えてください。
letynSOFT 2016年

@LetynSOFT擬似コードを見つけることができ、ここで
Mayonnaise2124

6

これは、この問題を解決するために今一緒に作成した小さな関数です。文字列を一度に1文字ずつ分析して式を作成します(実際にはかなり高速です)。これは任意の数式(+、-、*、/演算子のみに限定)を取り、結果を返します。負の値や無制限の数の演算も処理できます。

残っている唯一の「やるべきこと」は、+&-の前に*&/を計算することを確認することです。後でその機能を追加しますが、今のところこれは私が必要なことをします...

/**
* 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;
}

3

シンプルでエレガント Function()

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

parse("1+2+3"); 


それがどのように機能するか説明できますか?私はこの構文に
不慣れ

Function( "return(1 + 2 + 3)")(); -その匿名関数。引数(関数本体)を実行しているだけです。Function( "{return(1 + 2 + 3)}")();
アニケットクデール

文字列はどのように解析されますか?&それは何) -----ですか($ {str})() `ついにこの括弧?
pageNotfoUnd

これがevalよりも優れているかどうかはわかりません。このサーバー側を実行する前に、に注意してくださいparse('process.exit()')
バスティ

3

forループを使用して文字列に無効な文字が含まれているかどうかを確認してから、try ... catchとevalを使用して、計算でエラーがスローされるかどうかを確認できますeval("2++")

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'))

または関数の代わりに、 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

私は最終的にこのソリューションを選びました。これは正と負の整数を合計するために機能します(正規表現を少し変更するだけで、小数に対しても機能します):

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()よりも高速かどうかはわかりませんが、操作を何度も実行する必要があるため、javascriptコンパイラのインスタンスを大量に作成するよりもこのスクリプトを実行する方がはるかに快適です。


1
return表現内で使用することができない、sum("+1")返しNaNが
ガンボ2010年

戻り値が三項式の中に入る必要があるかどうかを常に忘れてください。「+1」は数値として評価する必要がありますが、日常的な意味での数学的な合計の例ではないため、除外したいと思います。私のコードは、許容される文字列の評価とフィルタリングの両方を行うように設計されています。
wheresrhys 2010年

2

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

私はparseIntES6がこの状況で役立つと信じています

==>このように:

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

ここでparseInt重要なのは、演算子を使用して数値を解析することです。コードは、対応するニーズに合わせて変更できます。


2

Nodejsとブラウザの両方 で動作するGithubからこの手入れの行き届いたライブラリを使用できますは、ここで提供されている他の代替ライブラリよりも高速です。

使用法

 mexp = require('math-expression-evaluator')
 var value = mexp.eval(exp);  

完全なドキュメント


2
所属を図書館に開示してください。これは、ここでのユーザー名である「bugwheels94」という名前のユーザーによって管理されています。
GalaxyCat105

2
ここにリンクされているGitHubリポジトリに所属しているようです。あなたが提携しているものにリンクするとき、あなたはあなたの投稿でその提携を開示しなければなりません。所属の開示がなければ、技術的にはスパムと見なされます。参照してください:「良い」自己宣伝を意味するものは何ですか?ヘルプセンターは自己宣伝です。開示は明示的である必要がありますが、正式である必要はありません。このような場合、開示は「…、私が寄稿するリポジトリ」などのようになります。開示を含めるように投稿を編集しました。
Makyen

@Makyenルールとしてあなたの意見を押し付けないでください。あなたが与えた2つのリンクのどれにも無料のパッケージを開示しなければならないと書かれているところはどこにも見つかりません。私は礼儀からそれをしただろうが、今は不必要な編集の後、私はそれをするのを嫌がっている。このパッケージはOPの問題を解決し、その方法を説明しました。残りは重要ではありません
bugwheels94

1
@ bugwheels94リンク先が無料か商用かは関係ありません。提携しているものをリンクまたは宣伝する場合は、この回答には絶対に当てはまらない非常に限られた状況を除いて、開示が必要です。所属を開示するための要件は、非常に長い間ポリシーでした。これは、MSOとMSEの両方のMetaで何度も議論されています。投稿の最小限の編集が気に入らなかったのが残念です。その編集を行うことは、最も侵襲性の低いオプションでした。これが開示されていないことを除けば、それは合理的な答えであるため、私はそれを選びました。
Makyen

1
追加情報が必要な場合は、次を参照してください:StackOverflowの「スパム」の正確な定義は何ですか。、および何が何かをスパムするのか、そしてヘルプは行動に集中します。
Makyen

1

AutoCalculatorを試してください https://github.com/JavscriptLab/autocalculate セレクター式を使用して入力値と出力を計算します

data-ac = "(#firstinput +#secondinput)"のような出力入力の属性を追加するだけです

初期化の必要はありません。data-ac属性のみを追加するだけです。動的に追加された要素を自動的に検出します

または、出力で「Rs」を追加するだけで、中括弧内にデータを追加します-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
減算、乗算、除算はどうですか?なぜnum1を掛けるのですか?
nathanfranke

@nathanfrankeを指摘していただきありがとうございます。より一般的なものにするために回答を更新しました。現在、4つの操作すべてをサポートしています。そして、1の倍数は、それを文字列から数値に変換することでした。+ numを実行することでもこれを達成できます。
Rushikesh Bharad

0

これは、jMichaelと同様のアルゴリズムソリューションであり、式を1文字ずつループし、左/演算子/右を段階的に追跡します。この関数は、各ターン後にオペレーター文字を見つけた後に結果を累積します。このバージョンは「+」および「-」演算子のみをサポートしますが、他の演算子で拡張するように記述されています。注:式は正のfloatで始まると想定しているため、ループする前に「currOp」を「+」に設定します。実際、全体として、入力は電卓からの入力と似ていると想定しています。

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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.