質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

3回答

7564閲覧

Javaで文字列を数式に変換したい

tmp-user

総合スコア44

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

1クリップ

投稿2020/03/09 05:31

編集2020/04/02 00:09


お世話になっております。

またしても学習サイトで分からない問題があったため質問させていただきたいです。

「String形式で書かれた数式の答えを求める関数を作成せよ
条件 引数、答えはすべてint型とする
例 引数 "(1 + 2 ) * 3"
答え 9                       」

文字を列をCharacterに変換し、さらにisDigit()で数値かどうかを
判断すれば答えが求められるのでは、と考えたのですが
正しい答えは導き出せず、元の数式に戻っただけでした。

こちらはどのようにすれば数式として扱えるのでしょう。
(Characterの1をint型に変換するとUnicodeの関係で49となるところを
強引に変換している所も正直イマイチだと思ってはいます・・・。)

java

1import java.util.*; 2 3public class Main { 4 public static void main(String[] args) throws Exception { 5 6 strConversion("(1 + 2) * 3"); 7 8 } 9 10 11 public static void strConversion(String str){ 12 String ans =""; 13 14 for(int i = 0; i <= str.length() -1; i++){ 15 char X = str.charAt(i); 16 if(Character.isDigit(X)){ 17 //数値 18 ans += Integer.valueOf(X) -48; //UniCode 19 System.out.println("数値:" + X); 20 }else{ 21 //文字列 22 ans += String.valueOf(X); 23 System.out.println("文字:" + X); 24 } 25 26 } 27 System.out.println(ans); 28 } 29} 30

追記
みなさま回答ありがとうございます。
私の想定より難易度が高かったようでせっかくの助言がなかなか理解出来ていません…。
頂いた回答をもとにもう少し理解を深めてからレスポンスさせていただきます。

※レスポンス、及び回答受付は一旦保留させていただきます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dodox86

2020/03/09 06:04

> こちらはどのようにすれば数式として扱えるのでしょう。 質問者さんはもしかすると課題の意図を取り違えているのではないか、と思うので、回答ではなくコメントのみです: "(1 + 2 ) * 3"、この数式を文字列から自分で分解して カッコ、数値、+、*、などの加減乗除などの記号に分け、計算できるようにせよ、と言う問題なのではないでしょうか。
tmp-user

2020/03/09 06:09

[文字列で書かれた演算式を計算する処理(関数)を作成してください ・文字列は 正の整数、半角スペース、半角括弧、演算子(+,-,*)のみで構成。 ・計算結果はintの範囲を想定。] という問題だったのでてっきり文字列から数値を取り出す、という事を考えたのですが、おっしゃるように演算子を発見する問題なのではという気もしてきました…。 この観点でも調べてみます。ありがとうございます。
dodox86

2020/03/09 06:17

数式を使う課題の場合、コンピュータで扱いやすい「逆ポーランド記法」と言うものを使うことも多いのですが、そうではない一般的な数式だと少し難しいかもしれませんね。カッコが複数存在したりネストしていたりすることも想定すると結構面倒です。ネストされたカッコを考える場合、「再帰」を使うことを検討しても良いかもしれません。
dodox86

2020/03/09 06:23

(私のコメント) > ネストされたカッコを考える場合 あ、いえ、間違えました。一般的な加減乗除の計算式なのですから、中カッコも無く、カッコ閉じカッコ'()'がネストすることはないのでした。この点、大変失礼しました。
jimbe

2020/03/09 18:53

実際に計算するようですね. 数式文字列→逆ポーランド化→計算と言う段階を経る感じでしょうか.
jimbe

2020/03/14 05:13

> 私の想定より難易度が高かったようで 学習サイトがどのようなレベルの方を対象にこの問題を出しているのかが気になる所です. 「数値の桁数の制限も無く(負数もあり?)演算子の優先順位を考慮しかつ括弧ありでどれだけ長い式かも分からない」ということであれば, 少なくとも"文字列→数値変換をどうやるか考える"レベルの方向けでは無いと思います. これが, 「式中の数字は正数1桁のみ(結果は負数や複数桁になる可能性あり?), 優先順位は考えず左から順に計算, 括弧無し, 演算子数は2個まで」...つまり "1 + 2 * 3" で 9 になるような問題であれば, まだ簡単かとは思いますが...
guest

回答3

0

ベストアンサー

再帰下降構文解析でやってみました。

Java

1import java.io.*; 2 3class Expr { 4 char c; int pos = 0, val = 0; String str, o = "+-*/"; 5 6 Expr(String s) { str = s; } 7 8 char get() { 9 while (pos < str.length()) 10 if ((c = str.charAt(pos++)) != ' ' ) { 11 if (!Character.isDigit(c)) return c; 12 for (val = c - '0'; ; ) { 13 if (pos == str.length()) return c ='0'; 14 c = str.charAt(pos++); 15 if (!Character.isDigit(c)) { pos--; return c = '0'; } 16 val = val * 10 + (c - '0'); 17 } 18 } 19 return c = 0; 20 } 21 22 int expr(int i) { 23 int v; 24 if (i < o.length()) 25 for (v = expr(i + 2); c == o.charAt(i) || c == o.charAt(i + 1); ) 26 if (c == '+') v += expr(i + 2); 27 else if (c == '-') v -= expr(i + 2); 28 else if (c == '*') v *= expr(i + 2); 29 else v /= expr(i + 2); 30 else 31 if (get() == '0') { v = val; get(); } 32 else if (c == '(') { 33 v = expr(0); 34 if (c == ')') get(); 35 else c = 2; 36 } 37 else if (c == '+') v = expr(i); 38 else if (c == '-') v = -expr(i); 39 else { v = 0; c = 1; } 40 return v; 41 } 42 43 public static void main(String[] args) throws Exception { 44 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 45 for (;;) { 46 System.out.print(">> "); 47 String s = br.readLine(); 48 if (s.equals(".")) break; 49 Expr e = new Expr(s); 50 int v = e.expr(0); 51 System.out.println(e.c == 0 ? " " + v : " Error"); 52 } 53 } 54}

バグのご指摘歓迎します。

追記
get の中の return '0'; を return c = '0'; に修正しました。

追記2
逆ポーランド記法に変換してからそれを評価するようにしてみました。

Java

1import java.util.*; 2 3class Rpn { 4 char c; int pos = 0, len, val; String str, o = "+-*/"; 5 StringBuilder sb = new StringBuilder(); 6 7 Rpn(String s) { str = s; len = s.length(); } 8 9 char get() { 10 while (pos < len) { 11 c = str.charAt(pos++); 12 if (c != ' ' ) { 13 if (!Character.isDigit(c)) return c; 14 for (val = c - '0'; ; ) { 15 if (pos == len) return c = '0'; 16 c = str.charAt(pos++); 17 if (!Character.isDigit(c)) { pos--; return c = '0'; } 18 val = val * 10 + (c - '0'); 19 } 20 } 21 } 22 return c = 0; 23 } 24 25 void put(Object o) { sb.append(' ').append(o); } 26 27 void expr(int i) { 28 if (i < 4) 29 for (expr(i + 2); c == o.charAt(i) || c == o.charAt(i + 1); ) { 30 char d = c; expr(i + 2); put(d); 31 } 32 else if (get() == '0') { put(val); get(); } 33 else if (c == '(') { expr(0); if (c == ')') get(); else c = 2; } 34 else if (c == '+') expr(i); 35 else if (c == '-') { expr(i); put('_'); } 36 else c = 1; 37 } 38 39 int eval(String rpn) { 40 Scanner scn = new Scanner(rpn); 41 Stack<Integer> s = new Stack<>(); 42 int v; 43 for (;;) 44 if (scn.hasNextInt()) 45 s.push(scn.nextInt()); 46 else if (scn.hasNext()) { 47 String op = scn.next(); 48 if (op.equals("+")) s.push(s.pop() + s.pop()); 49 else if (op.equals("-")) s.push(-s.pop() + s.pop()); 50 else if (op.equals("*")) s.push(s.pop() * s.pop()); 51 else if (op.equals("/")) { v = s.pop(); s.push(s.pop() / v); } 52 else if (op.equals("_")) s.push(-s.pop()); 53 else return 0; 54 } 55 else return s.pop(); 56 } 57 58 public static void main(String[] args) throws Exception { 59 Scanner scn = new Scanner(System.in); 60 for (;;) { 61 System.out.print(">> "); 62 if (!scn.hasNextLine()) break; 63 String s = scn.nextLine(); 64 if (s.equals(".")) break; 65 Rpn rpn = new Rpn(s); 66 rpn.expr(0); 67 if (rpn.c == 0) { 68 s = rpn.sb.toString(); 69 System.out.println(s + " ==> " + rpn.eval(s)); 70 } 71 else System.out.println(" Error"); 72 } 73 } 74}

new しているクラスは Scanner, Stack, StringBuilder だけです。
ラムダ式なんか使っていません。
理解できなくてもかまいませんが、どこが分からないかを教えていただけませんか?

追記3
計算を double で行うようにしてみました。

Java

1import java.util.Scanner; 2import java.util.regex.*; 3 4class Expr { 5 String str; int len, pos; double val; 6 char c, op[] = { '+', '-', '*', '/', '^', '^' }; 7 Pattern p = Pattern.compile("\d+\.?\d*([eE][+-]?\d+)?"); 8 9 Expr(String s) { str = s; len = s.length(); pos = 0; } 10 11 char get() { 12 while (pos < len) 13 if ((c = str.charAt(pos++)) != ' ' ) { 14 if (!Character.isDigit(c)) return c; 15 String s = str.substring(--pos); 16 Matcher m = p.matcher(s); 17 m.find(); 18 s = m.group(); 19 val = Double.parseDouble(s); 20 pos += s.length(); 21 return c = '0'; 22 } 23 return c = 0; 24 } 25 26 double expr(int i) { 27 double v; 28 if (i < 6) 29 for (v = expr(i+2); c == op[i] || c == op[i+1]; ) 30 if (c == '+') v += expr(i+2); 31 else if (c == '-') v -= expr(i+2); 32 else if (c == '*') v *= expr(i+2); 33 else if (c == '/') v /= expr(i+2); 34 else v = Math.pow(v, expr(i)); 35 else if (get() == '0') { v = val; get(); } 36 else if (c == '(') { v = expr(0); if (c == ')') get(); else c = 2; } 37 else if (c == '+') v = expr(i); 38 else if (c == '-') v = -expr(i); 39 else if (str.startsWith("sqrt(", pos-1)) { pos += 3; v = Math.sqrt(expr(i)); } 40 else v = c = 1; 41 return v; 42 } 43 44 public static void main(String[] args) { 45 for (Scanner scn = new Scanner(System.in); ; ) { 46 System.out.print(">> "); 47 if (!scn.hasNextLine()) break; 48 String s = scn.nextLine(); 49 if (s.equals(".")) break; 50 Expr e = new Expr(s); 51 double v = e.expr(0); 52 System.out.println(e.c == 0 ? " " + v : " Error"); 53 } 54 } 55}

べき乗演算子 ^ や関数 sqrt を追加しています。

実行例

>> 1+2*(3-4)/5 0.6 >> 355/113 3.1415929203539825 >> sqrt(2) 1.4142135623730951 >> (1 + sqrt(5)) / 2 1.618033988749895 >> sqrt(3^2 + 4^2) 5.0 >> 2^3^2 512.0 >> .

^ は右結合なので 2^(3^2) となります。

投稿2020/03/11 14:26

編集2020/03/16 00:49
kazuma-s

総合スコア8224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

xebme

2020/03/15 10:16

字句解析しているし、再帰によって優先度もうまく処理されていて、良いコードだと思います。
guest

0

逆ポーランド記法を利用した計算

逆ポーランド記法を参考に、解析と計算を一度に実行するコードを書いてみました。

Java

1 static int eval(String expression) { 2 Stack<Integer> operands = new Stack<>(); 3 Stack<Character> operators = new Stack<>(); 4 for (char c : expression.toCharArray()) { 5 switch (c) { 6 case ' ': 7 break; 8 case '(': 9 operators.push(c); 10 break; 11 case ')': 12 while (!operators.isEmpty()) { 13 char operator = operators.pop(); 14 if (operator == '(') { 15 break; 16 } 17 operands.push(calculate(operands.pop(), operands.pop(), operator)); 18 } 19 break; 20 case '0': 21 case '1': 22 case '2': 23 case '3': 24 case '4': 25 case '5': 26 case '6': 27 case '7': 28 case '8': 29 case '9': 30 operands.push(c - '0'); 31 break; 32 case '+': 33 case '-': 34 case '*': 35 if (!operators.isEmpty() && precede(operators.peek(), c)) { 36 operands.push(calculate(operands.pop(), operands.pop(), operators.pop())); 37 } 38 operators.push(c); 39 break; 40 default: 41 throw new RuntimeException("Invalid token : '" + c + "'"); 42 } 43 } 44 while (!operators.isEmpty()) { 45 operands.push(calculate(operands.pop(), operands.pop(), operators.pop())); 46 } 47 return operands.pop(); 48 } 49 50 static int calculate(int y, int x, char operator) { 51 int result = 0; 52 switch (operator) { 53 case '+': 54 result = x + y; 55 break; 56 case '-': 57 result = x - y; 58 break; 59 case '*': 60 result = x * y; 61 break; 62 } 63 return result; 64 } 65 66 static int priority(char operator) { 67 return (operator == '*') ? 2 : (operator == '(') ? 0 : 1; 68 } 69 70 static boolean precede(char op1, char op2) { 71 return priority(op1) >= priority(op2); 72 }

バグがあれば遠慮なく指摘してください。

参考

逆ポーランド記法(追記)

上のコードからcalculate()メソッドを削除しオペランドスタックをリストにかえてリストへ演算子を追加すると、逆ポーランド記法を生成できます。せっかくだから、逆ポーランド記法を生成して評価する関数型コードを書いてみました。

演算に除算/を追加して優先度を以下のように決定。

Java

1static Function<Character,Integer> priority = 2 operator -> (operator == '*' || operator == '/') ? 2 3 : (operator == '+' || operator == '-') ? 1 : 0; 4 5static BiFunction<Character,Character,Boolean> preceding = 6 (op1,op2) -> priority.apply(op1) >= priority.apply(op2);

逆ポーランド記法の生成器(関数)を定義する。逆ポーランド記法として出力されるのはList<Character>

Java

1static Function<String,List<Character>> toRpn = expression -> { 2 Deque<Character> operators = new LinkedList<>(); // キャプチャされる。 3 List<Character> rpnList = expression.chars() 4 .mapToObj(c -> Character.valueOf((char) c)) 5 .collect( 6 ArrayList::new, 7 (accList, token) -> { 8 switch (token) { 9 case ' ': 10 break; 11 case '(': 12 operators.push(token); 13 break; 14 case ')': 15 while (!operators.isEmpty()) { 16 char operator = operators.pop(); 17 if (operator == '(') { 18 break; 19 } 20 accList.add(operator); 21 } 22 break; 23 case '0': 24 case '1': 25 case '2': 26 case '3': 27 case '4': 28 case '5': 29 case '6': 30 case '7': 31 case '8': 32 case '9': 33 accList.add(token); 34 break; 35 case '+': 36 case '-': 37 case '*' 38 case '/': 39 if (!operators.isEmpty() && preceding.apply(operators.peek(), token)) { 40 accList.add(operators.pop()); 41 } 42 operators.push(token); 43 break; 44 default: // 45 throw new RuntimeException("Invalid token : '" + token + "' in '" + expression + "'"); 46 } 47 }, 48 List::addAll); 49 while (!operators.isEmpty()) { 50 rpnList.add(operators.pop()); 51 } 52 return rpnList; 53}; 54

逆ポーランド記法の評価

上で作成したList<Character>を入力する逆ポーランド記法の評価器(関数)を定義。

Java

1static Function<List<Character>,Integer> evalRpn = rpn -> { 2 Deque<Integer> result = 3 rpn.stream() 4 .collect( 5 LinkedList::new, 6 (operandStack, token) -> { 7 switch (token) { 8 case '0': 9 case '1': 10 case '2': 11 case '3': 12 case '4': 13 case '5': 14 case '6': 15 case '7': 16 case '8': 17 case '9': 18 operandStack.push(token - '0'); 19 break; 20 case '+': 21 operandStack.push(operandStack.pop() + operandStack.pop()); 22 break; 23 case '-': 24 operandStack.push(-operandStack.pop() + operandStack.pop()); 25 break; 26 case '*': 27 operandStack.push(operandStack.pop() * operandStack.pop()); 28 break; 29 case '/': 30 operandStack.push((int)(1d / operandStack.pop() * operandStack.pop())); 31 break; 32 } 33 }, 34 Deque::addAll); 35 return result.pop(); 36 };

逆ポーランド記法の生成と評価を合成。

Java

1static Function<String,Integer> eval = toRpn.andThen(evalRpn);

利用方法。

Java

1String expression = "2 * (1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9)"; 2int result = eval.apply(expression); 3System.out.println(expression + " = " + result);

投稿2020/03/09 23:34

編集2020/03/11 10:55
xebme

総合スコア1090

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

投稿2020/03/09 13:36

cateye

総合スコア6851

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問