数式を入力する際に演算子の周囲にスペースを入れておくなら、
"123 + 456 * 789".split
正規表現でトークンに分解するなら以下のように(ソースはここ)。
require('readline').createInterface({
input: process.stdin
}).on('line',(line)=>{
console.log(line.match(/\d+|[-+\/*%]/g))
}).on('close',()=>{
process.exit(0)
})
トークン列ができたとして、これを前から順々に取り出して、3つ揃ったら計算を実行させる。
Array.prototype.shift() は Array#shift と違って、複数要素を一度に取り出すための引数がない。
3つまとめて取り出すためには、一回ずつ shift を呼ぶという手もあるが、
以下のような(慣れれば)強力な記法もある(スプレッド構文についてはすでに概要を解説してあるので類推で意味は理解できるだろう)。
[l,ope,r,...a]=a
計算を実行するための関数も用意しておこう
function calc1(ope,r,l){
switch(ope){
case '+': return r+l
case '-': return r-l
case '*': return r*l
case '/': return r/l
case '%': return r%l
}
}
関数的に用いるcase構文(多くの言語にあるがC Java JSにはない)が使えないので 少し古くさいが switch構文で書いた。
それぞれのcase毎に return を書く必要があるのが結構面倒(returnでなければ break が必要になり、いずれにしても古い構文は記述量が増える傾向がある。
これまでに紹介したコードを取りまとめたものが このソース(nodeコマンドで実行してみて下さい)。
calc1を、以下のようにオブジェクト(連想配列に近い用法)を使って書くことも、 できなくはない。
function calc1(ope,l,r){
return {'+':l+r,'-':l-r,'*':l*r,'/':l/r,'%':l%r}[ope]
}
記述的にはコンパクトになるが、 オブジェクトはすべての値を生成してから全体が作られるので、 その中から1つの値だけを取り出すだけのために すべての計算(ここでは5つ)を実行するという無駄が発生するのと、 関係ない計算のためにエラーが発生するリスクがあり、 あまり推奨されないやり方。
ただし、l/r を r==0 で呼び出しても、現在のJSではエラーは発生しない (ゼロ除算をエラーとする言語も多いが JSでは 代わりに Infinity
を返す)。
ついでながら、上記を ダブルアローで定義する際には、1つ落し穴があるので解説しておく。
// 単純に考える と
var calc1=(ope,l,r)=>
{'+':l+r,'-':l-r,'*':l*r,'/':l/r,'%':l%r}[ope]
// NG (Unexpected token ':')
// 左ブレースは複数式を囲むブレースと解釈されるようだ
var calc1=(ope,l,r)=>{
{'+':l+r,'-':l-r,'*':l*r,'/':l/r,'%':l%r}[ope]
} // これもなぜかNG
// 回避策としては 1. 明示的に return を使う
var calc1=(ope,l,r)=>{
return {'+':l+r,'-':l-r,'*':l*r,'/':l/r,'%':l%r}[ope]
}
// あるいは 2. ブレースでなく丸い括弧 ( ) で式を囲む
var calc1=(ope,l,r)=>({'+':l+r,'-':l-r,'*':l*r,'/':l/r,'%':l%r}[ope])
丁寧に探すとドキュメントにも書いてあるのだが、見逃しがちなTIPSです。
CoffeeScript だと少し軽快に書けます。例を示しておきます。
calc1=(ope,r,l)->
switch ope
when '+' then r+l
when '-' then r-l
when '*' then r*l
when '/' then r/l
when '%' then r%l
一度にまとめてマッチさせる機能(Rubyだと String#scan
、JSだと String.prototype.match() に渡す正規表現の g オプション)がなかったら:
まず、後方参照の書き方(マッチ終了後にキャプチャした文字列を参照する)
var m
m="1+2+3".match(/([-+*\/]|\d+)/) && m[1]
Rubyでは $1, $2 などで引用していたが、JSにこの記法の変数はない。
正規表現の中にキャプチャのための ( )
が含まれる場合、JSでは match が返す値(配列)の、添え字1以降にキャプチャされた文字列が 配列の要素として挟まれる。
括弧が1つの時は m[1] で後方参照できる(matchの値を m に代入したとして)。
マッチした部分と、残りの部分とを後方参照で取り出し、前者を結果列に push し、 後者に対して繰り返しマッチを試みる、というアプローチで、Array#scan 的なものが 実現できる。
var r=[], m, s="1+2+3"
while(m=s.match(/([-+*\/]|\d+)(.*)/)) {
r.push(m[1]) ; s=m[2]
}
console.log(r) // に、トークンの配列が作られている
(ソースはここ)
JS には グローバル関数として parseInt() があるが、もしこれがなかったとしたら:
まず準備として、1文字を数値に変換させる方法:
'7'.charCodeAt())-'0'.charCodeAt())
// または
'0123456789'.indexOf('7')
これを全桁について適用する。
"1234567".split('').
reduce((r,v)=>r*10+'0123456789'.indexOf(v))
数字以外の文字が含まれた時の動作については(おそらく誤動作をする) ここでは考慮していない。
上のプログラムは、下のように手続き的な感覚で書いても同じ動きをする。
var s="1234567"
var c, r=0
var a=s.split('')
while(c=a.shift()) {
r=r*10+'0123456789'.indexOf(c)
}
console.log(r)
前節と逆の変換になる。
const n2s=n=>n<=0?"": n2s(n/10<<0)+"0123456789"[n%10]