質問するログイン新規登録

回答編集履歴

1

コメントの回答追加

2016/10/20 17:17

投稿

naomi3
naomi3

スコア1105

answer CHANGED
@@ -1,138 +1,415 @@
1
- 演算子の優先順位が同じで、( )を使わない、電卓のような場合は比較的単純に修正できます。
2
1
  ```Java
3
2
  import java.io.BufferedReader;
4
3
  import java.io.InputStreamReader;
4
+ import java.util.ArrayDeque;
5
+ import java.util.EmptyStackException;
6
+ import java.util.HashMap;
7
+ import java.util.Stack;
5
8
  import java.util.StringTokenizer;
6
9
 
7
10
  // Calcクラス
8
- public class Calc {
11
+ public class Calc
9
-
12
+ {
10
13
  // メインメソッド
11
- public static void main(String[] args) {
14
+ public static void main(String[] args)
12
-
15
+ {
13
- try {
16
+ try
14
- int ans = 0;
17
+ {
15
-
16
18
  InputStreamReader isr = new InputStreamReader(System.in);
19
+ BufferedReader bReader = new BufferedReader(isr);
17
20
 
18
- BufferedReader br = new BufferedReader(isr);
19
-
20
21
  Calc calc = new Calc();
21
22
 
23
+ for (; ;)
24
+ {
22
- for (String expr = br.readLine();
25
+ // プロンプトの出力
23
- expr != null && ! expr.equals("");
26
+ System.out.print(">");
24
- expr = br.readLine()) {
25
27
 
28
+ String expr = bReader.readLine();
29
+ if (expr == null || expr.equals(""))
30
+ {
31
+ break;
32
+ }
33
+
26
- try {
34
+ try
35
+ {
27
- ans = calc.calc(expr);
36
+ int ans = calc.calc(expr);
28
37
  System.out.println("=" + ans);
29
38
  }
30
- catch (UsrExprException e) {
39
+ catch (UsrExprException e)
31
-
40
+ {
32
41
  System.out.println("式に誤りがあります : " + e.getMessage());
33
42
  }
34
43
  }
35
- }
44
+ }
36
- catch (Exception e) {
45
+ catch (Exception e)
46
+ {
47
+ System.out.println("例外 : " + e);
48
+ }
37
49
 
38
- System.out.println("例外:"+ e);
50
+ System.out.println("終了");
39
- }
40
51
  }
41
52
 
42
- public int calc(String expr) throws Exception {
43
53
 
54
+ public int calc(String expr) throws Exception
55
+ {
44
- int ans = 0;
56
+ // 全ての空白文字を取り除く
57
+ expr = expr.replaceAll("\\s+", "");
45
58
 
59
+ boolean printRevPolishExpr = false;
60
+ // p で始まっていたら、逆ポーランド記法表示モード
46
- StringTokenizer tokenizer = new StringTokenizer(expr, "+-*/", true);
61
+ if (expr.startsWith("p"))
62
+ {
63
+ printRevPolishExpr = true;
64
+ expr = expr.substring(1);
65
+ }
47
66
 
48
- String sign = "+";
67
+ // 字句解析
49
- String operator = "+";
68
+ StringTokenizer tokenizer = new StringTokenizer(expr, "+-/*", true);
50
69
 
51
- while (tokenizer.hasMoreTokens()) {
70
+ ArrayDeque<String> tokenQueue = new ArrayDeque<>();
52
71
 
72
+ while (tokenizer.hasMoreTokens())
73
+ {
53
- String token = tokenizer.nextToken().trim();
74
+ String token = tokenizer.nextToken();
54
75
 
55
- if (token.length() == 0) {
76
+ if (! token.isEmpty())
77
+ {
78
+ tokenQueue.add(token);
79
+ }
80
+ }
56
81
 
82
+ tokenQueue = replaceAsUnaryOps(tokenQueue);
83
+
84
+ ArrayDeque<String> revPolishQueue = toRevPolish(tokenQueue);
85
+
86
+ if (printRevPolishExpr)
87
+ {
88
+ ArrayDeque<String> revPolishExpr = revPolishQueue.clone();
89
+
90
+ while (! revPolishExpr.isEmpty())
91
+ {
92
+ System.out.print(" " + revPolishExpr.remove());
93
+ }
94
+ System.out.println();
95
+ }
96
+
97
+ return evaluate(revPolishQueue);
98
+ }
99
+
100
+
101
+ // 単項演算子を見つけて置換する
102
+ protected static ArrayDeque<String> replaceAsUnaryOps(ArrayDeque<String> tokenQueue)
103
+ {
104
+ ArrayDeque<String> outQueue = new ArrayDeque<>();
105
+
106
+ // 前回の演算子を"+"に初期化
107
+ String lastOperator = "+";
108
+
109
+ while (! tokenQueue.isEmpty())
110
+ {
111
+ String token = tokenQueue.remove();
112
+ if (isNumber(token))
113
+ {
114
+ lastOperator = null;
115
+ outQueue.add(token);
57
116
  continue;
58
117
  }
59
118
 
60
- int num;
119
+ // tokenは演算子
61
120
 
62
- if (! token.matches("[-+*/]")) {
121
+ if (lastOperator == null)
122
+ {
123
+ lastOperator = token;
124
+ outQueue.add(token);
125
+ continue;
126
+ }
63
127
 
64
- // トークンが演算子でなければ、整数と解釈し、演算を行う
128
+ // 前回も演算子
65
- try {
66
- num = Integer.parseInt(token);
67
- }
68
- catch (NumberFormatException e) {
69
129
 
130
+ switch (token)
131
+ {
70
- throw new UsrExprException("整数ではない");
132
+ case "+":
133
+ // 何もしないので詰めない
71
- }
134
+ break;
72
135
 
136
+ case "-":
73
- if (sign.equals("-")) {
137
+ if (lastOperator.equals("u-"))
74
- num = - num;
75
- }
138
+ {
139
+ // 2回連続"u-"である場合
76
140
 
77
- if (operator.equals("+")) {
141
+ // 前回詰めた"u-"を捨てる
142
+ outQueue.removeLast();
78
143
 
144
+ // 前々回詰めた演算子を復元する
145
+ lastOperator = outQueue.peekLast();
79
- ans += num;
146
+ if (lastOperator == null)
147
+ {
148
+ // 前回の演算子を"+"に再初期化
149
+ lastOperator = "+";
150
+ }
80
151
  }
152
+ else
153
+ {
81
- else if (operator.equals("-")) {
154
+ lastOperator = "u-";
82
-
83
- ans -= num;
155
+ outQueue.add("u-");
84
156
  }
85
- else if (operator.equals("*")) {
157
+ break;
86
158
 
159
+ default:
87
- ans *= num;
160
+ lastOperator = token;
161
+ outQueue.add(token);
88
- }
162
+ }
89
- else if (operator.equals("/")) {
163
+ }
90
164
 
91
- ans /= num;
165
+ return outQueue;
92
- }
166
+ }
93
167
 
168
+
169
+ // 式を逆ポーランド記法に変換する
170
+ protected static ArrayDeque<String> toRevPolish(ArrayDeque<String> tokenQueue)
171
+ throws UsrExprException
172
+ {
173
+ ArrayDeque<String> outQueue = new ArrayDeque<>();
174
+ Stack<String> stack = new Stack<>();
175
+
176
+ while (! tokenQueue.isEmpty())
177
+ {
178
+ String token = tokenQueue.remove();
179
+
180
+ if (isNumber(token))
181
+ {
182
+ // 数値ならば無条件にキューに書き出す
94
- operator = null;
183
+ outQueue.add(token);
184
+ continue;
95
185
  }
96
- else {
97
- // トークンが演算子の場合
98
186
 
99
- if (operator == null) {
187
+ // tokenは演算子
100
188
 
189
+ if (stack.isEmpty())
190
+ {
101
- // 前回演算子でけれ、演算子を保存
191
+ // スタック無条件にスタックに積む
102
- operator = token;
192
+ stack.push(token);
193
+ continue;
194
+ }
103
195
 
104
- // 符号に + をセ
196
+ // スタクは空ではない
105
- sign = "+";
106
- }
107
- else {
108
197
 
109
- // 前回が演算子場合
198
+ // token優先順位を得る
199
+ int priorityOfToken = getPriorityOf(token);
110
200
 
201
+ // スタックの先頭を得る(ただし取り出さない)
202
+ String topOfStack = stack.peek();
111
- if (token.equals("-") || token.equals("+")) {
203
+ if (priorityOfToken > getPriorityOf(topOfStack))
204
+ {
205
+ // スタックの先頭より優先順位が高ければスタックに積む
206
+ stack.push(token);
207
+ continue;
208
+ }
112
209
 
210
+ do
211
+ {
113
- // トーンが符号であれば、演算子はそまま
212
+ // スタックの先頭を取り出してキューに書き出す
114
- // 符号にトークンをセット
213
+ topOfStack = stack.pop();
115
- sign = token;
214
+ outQueue.add(topOfStack);
116
- }
117
- else {
118
215
 
216
+ // スタックの先頭より優先順位が低くなるまで繰り返す
119
- throw new UsrExprException("演算子の次が符号でない");
217
+ if (priorityOfToken > getPriorityOf(topOfStack))
120
- }
218
+ {
219
+ break;
121
220
  }
122
221
  }
222
+ while (! stack.isEmpty());
223
+
224
+ // スタックに積む
225
+ stack.push(token);
123
226
  }
124
227
 
125
- // 式の最後整数がな場合
228
+ // スタック残ってる全てを取り出してキューに書き出す
126
- if (operator != null) {
229
+ while (! stack.isEmpty())
230
+ {
231
+ String topOfStack = stack.pop();
232
+ outQueue.add(topOfStack);
233
+ }
127
234
 
235
+ return outQueue;
236
+ }
237
+
128
- throw new UsrExprException("式の最後に整数がない");
238
+ protected static boolean isNumber(String token)
239
+ {
240
+ try
241
+ {
242
+ Integer.parseInt(token);
243
+ return true;
129
244
  }
245
+ catch (NumberFormatException e)
246
+ {
247
+ return false;
248
+ }
249
+ }
130
250
 
251
+
252
+ // 演算子の優先順位を定義
253
+ protected static final HashMap<String, Integer> priorityMap =
254
+
255
+ new HashMap<String, Integer>() {
256
+ {
257
+ // ※数値が高いほど、優先順位が高いとする
258
+ put("u+", 3); // 単項演算子
259
+ put("u-", 3); // 単項演算子
260
+ put("*", 2);
261
+ put("/", 2);
262
+ put("+", 1);
263
+ put("-", 1);
264
+ }
265
+ };
266
+
267
+
268
+ // 演算子の優先順位を得る
269
+ protected static int getPriorityOf(String operator)
270
+ throws UsrExprException
271
+ {
272
+ Integer priority = priorityMap.get(operator);
273
+ // 見つからない場合はnullを返す
274
+
275
+ if (priority != null)
276
+ {
277
+ return priority;
278
+ }
279
+
280
+ throw new UsrExprException("'" + operator + "':未知の演算子");
281
+ }
282
+
283
+
284
+ // 逆ポーランド記法に変換された式を評価する
285
+ protected static int evaluate(ArrayDeque<String> revPolishQueue)
286
+ throws UsrExprException
287
+ {
288
+ int ans;
289
+ ValueStack stack = new ValueStack();
290
+
291
+ try
292
+ {
293
+ while (! revPolishQueue.isEmpty())
294
+ {
295
+ String token = revPolishQueue.remove();
296
+
297
+ if (isNumber(token))
298
+ {
299
+ // 数値ならば無条件にスタックに積む
300
+ stack.push(token);
301
+ continue;
302
+ }
303
+
304
+ int operand1;
305
+ int operand2;
306
+
307
+ switch (token)
308
+ {
309
+ case "u+": // 単項 + 演算子
310
+ // スタックはそのまま
311
+ break;
312
+
313
+ case "u-": // 単項 - 演算子
314
+ operand1 = stack.pop();
315
+ // 演算結果をスタックに積む
316
+ stack.push(- operand1);
317
+ break;
318
+
319
+ case "+":
320
+ operand2 = stack.pop();
321
+ operand1 = stack.pop();
322
+ // 演算結果をスタックに積む
323
+ stack.push(operand1 + operand2);
324
+ break;
325
+
326
+ case "-":
327
+ operand2 = stack.pop();
328
+ operand1 = stack.pop();
329
+ // 演算結果をスタックに積む
330
+ stack.push(operand1 - operand2);
331
+ break;
332
+
333
+ case "*":
334
+ operand2 = stack.pop();
335
+ operand1 = stack.pop();
336
+ // 演算結果をスタックに積む
337
+ stack.push(operand1 * operand2);
338
+ break;
339
+
340
+ case "/":
341
+ operand2 = stack.pop();
342
+ operand1 = stack.pop();
343
+ // 演算結果をスタックに積む
344
+ stack.push(operand1 / operand2);
345
+ break;
346
+ }
347
+ }
348
+
349
+ // 最終結果をスタックから取り出す
350
+ ans = stack.pop();
351
+ }
352
+ catch (EmptyStackException e)
353
+ {
354
+ throw new UsrExprException("数値が足りない");
355
+ }
356
+
357
+ if (! stack.empty())
358
+ {
359
+ throw new UsrExprException("演算子が足りない");
360
+ }
361
+
131
- //ansを返す
362
+ // ansを返す
132
363
  return ans;
133
364
  }
134
365
  }
366
+
367
+
368
+ import java.util.EmptyStackException;
369
+ import java.util.Stack;
370
+
371
+
372
+ public class ValueStack {
373
+
374
+ private Stack<Integer> stack = new Stack<>();
375
+
376
+
377
+ public ValueStack() {
378
+ }
379
+
380
+
381
+ public int push(int item) {
382
+ return stack.push(item);
383
+ }
384
+
385
+
386
+ public int push(String item) throws NumberFormatException {
387
+
388
+ Integer num = Integer.parseInt(item);
389
+
390
+ if (num == null) {
391
+ throw new NullPointerException();
392
+ }
393
+
394
+ return stack.push(num);
395
+ }
396
+
397
+
398
+ public int pop() throws EmptyStackException {
399
+ return stack.pop();
400
+ }
401
+
402
+
403
+ public boolean empty() {
404
+ return stack.empty();
405
+ }
406
+ }
135
407
  ```
408
+
136
- 優先順位や( )を考慮する場合は根本的な修正が必要です。
409
+ キュースタックはデータ構造、よく使われます。
410
+ case 文字列: … break; はJavaのバージョンによって使えない場合があります。
137
- その場合、式を逆ポーランド記法へ換しから、演算を行うと簡単でしょう
411
+ else if (token.equals(文字列)) { … } にください
412
+
138
- 逆ポーランド記法への変換アルゴリズムも知られていので、調べてくさい
413
+ 逆ポーランド記法変換メソッドを少し変えるけで( )も扱えるようになります
414
+ 優先順位を正しく設定すると、Javaのほかの演算子、%, &, |, ^, ~などにも対応できます。
415
+ 字句解析を正規表現によるマッチングに変えると、浮動小数点、複数文字の演算子、>>, >>>, <<やJavaにはない演算子、例えばべき乗 ** も扱えるようになりますね。