私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「そのeval、本当に安全ですか?」(CodeIQ)を参照してください。
簡単に言うと、eval()に渡す式の安全性を確保してください、と言う問題です。
主なポイントは次の通りです。
| 種類 | 名前 | 戻り値 |
|---|---|---|
| 変数 | two | 2 |
| 関数 | mod(x,y) | 第一引数(x)を第二引数(y)で割ったときの余り |
| 関数 | sumsq(x, ...) | 可変個の引数すべての平方根(2乗)の和 ← 問題文がおかしいけれど2乗の和を求める |
JavaScript(Node.js)で解答しています。
process.stdin.resume();
process.stdin.setEncoding('utf8');
var List = Array();
var DefVals = {
"two": "2",
}
var DefFuncs = {
"mod":true,
"sumsq":true,
}
var DefNames = {};
function addDefNames(arg){
for(var key in arg){
DefNames[key] = arg[key];
}
}
function checkDefName(str){
// 数字を全て取り除く
var l = str.replace(/\d/g, "");
// +-*/(),を全てスペースに置換する(関数名だけがスペース区切りで残る)
l = l.replace(/[\+\-\*\/\(\),]/g, " ").trim();
l = l.replace(/\s+/g, " ");
var n = l.split(" ");
for(var i=0; i<n.length; i++){
n[i] = n[i].trim();
if(!n[i]){continue;}
if(!DefNames[n[i]]){
console.log("Unknown name");
return false;
}
}
return true;
}
function execFomula(str){
try{
var ret = eval(str);
if(typeof ret === "number" &apm;& !isNaN(ret)){
console.log(ret);
}
else if(isNaN(ret)){
console.log("Unknown syntax");
}
else{
console.log("Unknown syntax");
}
}
catch(e){
//console.log(e);
console.log("Unknown syntax");
}
}
function mod(a,b){
var ra = eval(a);
var rb = eval(b);
return ra % rb;
}
function sumsq(){
var sum = 0;
for(var i=0; i<arguments.length; i++){
var a = eval(arguments[i]);
sum += a*a;
}
return sum;
}
function replaceDefVals(str){
var syntax = str;
for(var key in DefVals){
var regex = new RegExp(key , "g");
syntax = syntax.replace(regex, DefVals[key]);
}
return syntax;
}
process.stdin.on('data', function (chunk) {
var lines = chunk.toString().split('\n');
for(var i=0; i<lines.length; i++) {
var input = lines[i].trim();
if(!input){
continue;
}
List.push(input);
}
});
process.stdin.on('end', function () {
addDefNames(DefVals);
addDefNames(DefFuncs);
for(var i=0; i<List.length; i++){
var l = List[i];
var ok = checkDefName(l);
if(ok){l = replaceDefVals(l);}
else {continue;}
execFomula(l);
}
});
私の解答はおそらく出題者の意図している正攻法ではありません。
まともにやるならば入力値から構文木を作って解析するのでしょうが、違う手段を用いています。
大きな処理の手順は次の通りです。
次の3つの大域変数がチェック用の変数です。
checkDefName()で構文をチェックしています。
手順は次の通りです。具体例をあげながら説明します。
例えば、次のような入力があったとします。
replaceDefVals()で定数を数値に変換しています。
今回は問題ありませんでしたがこの処理は問題があります。
もし、「divide_by_two」のような置換対象が別の文字列の部分文字列になっているパターンがあったら正しい結果にはなりません。
演算子とカッコで分割してから置換して再度式を構成するなどの処理にする必要があります。
execFomula()で引数を実行しています。
ここに至るまでに、使用できない関数名、変数名が含まれる場合はエラーになっているので組み込み関数のようなものが含まれる式が実行されることはありません。
そして、式自体が正しければきちんと実行されることになります。
逆に言うと式自体がおかしい(ex:カッコが正しく対応していない、など)の場合は47行目のeval()がエラーになるのでeval()がエラーになった=「unknown syntax」ということになります。
以上が私のプログラムの説明です。
今回の問題のように限定的な入力なら私の方法でも大丈夫でしょうが、拡張性などを考えるとこの方法はあまり良くないでしょう。
特に、使用可能な関数を全て私の実装のように実装しなければならないのが問題です。
きちんと構文木を作って処理すれば単なる関数呼び出しにできるはずなので、やはり正攻法でやるのが一番いいような気がします。