'Code Study/사칙연산'에 해당되는 글 2건

  1. 2015.02.11 사칙연산 - 완성판. (1)
  2. 2015.02.01 사칙연산 - 수식 입력 및 해석
2015.02.11 00:11

각설하고, 완성된 사칙연산 계산기 프로그램입니다.



FourArithExprParser.java


FourArithExprProcessor.java



import java.util.Scanner;

/**
 * 사칙 연산 계산기.
 * 
 * - 사칙 연산 수식을 입력 받고,
 * - 해석한 후,
 * - 계산 결과를 출력한다.
 * 
 * @author sunnyk
 */
public class FourArithExprProcessor {

	public static void main(String args[]) {
		// 스캐너 열기
		Scanner scanner = new Scanner(System.in);

		// 사칙연산 수식 입력
		System.out.print("Input expression : ");
		String mathExpression = scanner.nextLine();

		// 빈 문장을 입력한 경우, 오류 메시지 출력
		if (mathExpression.trim().isEmpty()) {
			System.out
					.println("You entered empty expression, so do nothing and quit!");
		} 
		// "test"를 입력하면, 사칙연산 테스트 실시...
		else if ("test".equals(mathExpression)) {
			runParser("A + B becomes A B +", "10 + 2");
			runParser("A / B becomes A B /", "15 / 2");
			runParser("A * B + C becomes A B * C +", "76 * 32 + 2");
			runParser("A + B * C becomes A B C * +", "64 + 39 * 4");
			runParser("A * (B + C) becomes A B C + *", "31 * (61 + 2)");
			runParser("A * (B + C * D) + E becomes A B C D * + * E +",
					"1 * (2 + 3 * 4) + 2");
		}
		// 수식이 입력된 경우 해석...
		else {
			System.out.format(
					"Entered four arithmetic operation (expression) is '%s'\n",
					mathExpression);
			runParser("", mathExpression);
		}

		// 스캐너 닫기
		scanner.close();
	}

	/*
	 * 사칙연산 수식을 계산하고, 결과를 출력한다.
	 * 
	 * @param msg 안내 메시지
	 * @param expr 입력 수식
	 */
	private static void runParser(String msg, String expr) {
		
		if(msg == null || !msg.isEmpty()) {
			System.out.println("Run : " + msg);
		}
		
		FourArithExprParser parser = new FourArithExprParser(expr);

		// parse four arithmetic expression as infix.
		System.out.print("Infix expression : ");
		parser.parseSymbols(); 
		parser.print();

		// convert infix expression to postfix.
		System.out.print("Postfix expression : ");
		parser.toPostfix(); 
		parser.print();
		
		// calculate expression
		int result = parser.calcalate();
		System.out.println("Caculcation result = " + result);
		System.out.println();
	}

}
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 사칙 연산 수식 해석 (parse four arithmetic expression)
 * 
 * @author sunnyk
 *
 */
public class FourArithExprParser {

	// 사칙연산자 목록
	private static char[] OPERATORS = { '+', '-', '*', '/' };
	// 좌/우 괄호
	private static char[] PARENTHESISES = { '(', ')' };
	private String numberBuffer;
	private String inputExpr;
	private List<String> symbols = new ArrayList<String>();
	private Stack<String> aStack = new Stack<String>();
	
	/**
     * 생성자에 중위식 사칙연산 수식을 입력.
	 */
	public FourArithExprParser(String expression) {
		this.inputExpr = expression;
	}

	/**
	 * 중위식 사칙연산 수식에서 항목(symbol)들을 추출한다.
	 */
	public void parseSymbols() {

		// 문자(character) 단위로 분석..
		for (int idx = 0; idx < inputExpr.length(); idx++) {
			char charInExpr = inputExpr.charAt(idx);

			// 공백(whitespace) 문자 - linefeed, space, tab 등-이면, 이전 버퍼의 내용을 비움.
			if (Character.isWhitespace(charInExpr)) {
				handleTempBuff();
			}

			// 사칙연산 연산자 혹은 괄호인 경우, 항목 추가.
			else if (isMathOperatorOrParenthesis(charInExpr)) {
				handleTempBuff();
				symbols.add(String.valueOf(charInExpr));
			}

			// 숫자 문자인 경우, 숫자 버퍼에 추가..
			else if (Character.isDigit(charInExpr)) {
				if (numberBuffer == null) {
					numberBuffer = String.valueOf(charInExpr);
				} else {
					numberBuffer += charInExpr;
				}
			}
		}
		
		handleTempBuff();
	}

	/**
	 * 중위식을 후위식으로 변환.
	 */
	public void toPostfix() {
		List postfixExpr = new ArrayList();
		
		// 중위식 표현식의 각 항목에 대해서...
		for(String symbol : symbols) {
			
			char firstCharOfColumn = symbol.charAt(0);
			
			// 피연산자이면, 피연산자를 후위식에 추가
			if(!isMathOperatorOrParenthesis(firstCharOfColumn)) {
				postfixExpr.add(symbol);
			}
			// 좌괄호이면 스택에 추가
			else if('(' == firstCharOfColumn) {
				aStack.push(symbol);
			}
			// 우괄호이면, 좌괄호 '('가 나올 때까지 스택에서 꺼냄.
			else if(')' == firstCharOfColumn) {
				
				// 스택의 최상위 값이 좌괄호 '('가 아니면..
				while (!"(".equals(aStack.peek())) {
					postfixExpr.add(aStack.pop());
				} // while의 끝
				
				// 좌괄호'(' 제거
				aStack.pop();
			}
			// 연산자이면, 스택에 있는 더 높은 우선순위 연산자 처리
			else {
				// 만약, 스택이 비어 있거나, 스택의 최상위에 좌괄호가 들어 있으면, 연산자를 스택에 추가
				if(aStack.isEmpty() || "(".equals(aStack.peek())) {
					aStack.push(symbol);
				}
				// 연산자의 우선순위가 스택의 최상위에 있는 연산자보다 높으면 스택에 추가
				else if(priority(symbol) > priority(aStack.peek())) {
					aStack.push(symbol);
				}
				// 연산자의 우선순위가 스택의 최상위에 있는 연산자와 같으면, 연관에 따라 처리..
				// (좌측에서 우측으로 연산하는 경우, 스택의 최상위 연산자를 꺼내고, 연산자를 스택에 추가)
				else if(priority(symbol) == priority(aStack.peek())) {
					postfixExpr.add(aStack.pop());
					aStack.push(symbol);
				}
				// 새로운 연산자의 우선순위가 스택의 최상위에 있는 연산자보다 낮으면,
				// 스택의 최상위 연산자를 꺼내고, 새로운 연산자를 스택에 추가
				else {
					while(!aStack.isEmpty() && priority(symbol) <= priority(aStack.peek())) {
						postfixExpr.add(aStack.pop());
					}
					aStack.push(symbol);
				}
			}
		}

		// 후위식에 스택에 남은 연산자들을 추가
		while(!aStack.isEmpty()) {
			postfixExpr.add(aStack.pop());
		} // while의 끝
		
		// 중위식을 후위식으로 덮어쓰기...
		symbols = postfixExpr;
	}

	/**
	 * 후위식 계산을 수행하고 결과 값을 반환한다.
	 */
	public int calcalate() {
		
		// 후위식 기호(symbol)들을 순차적으로 처리한다.
		for(String symbol : symbols) {
			char firstChOfSymbol = symbol.charAt(0);
			
			// 사칙연산 연산자인 경우...
			if(isOperator(firstChOfSymbol)) {

				// 피연산자(operand)를 스택에서 꺼낸다.
				// (순서가 반대로 저장되어 있음을 주의할 것)
				int secondOperand = Integer.valueOf(aStack.pop());
				int firstOperand = Integer.valueOf(aStack.pop());

				// 연산자의 유형에 따라 계산을 수행한다.
				int result = 0;
				switch(firstChOfSymbol) {
					case '+':
						result = firstOperand + secondOperand;
						break;
					case '-':
						result = firstOperand - secondOperand;
						break;
					case '*':
						result = firstOperand * secondOperand;
						break;
					case '/':
						result = firstOperand / secondOperand;
						break;
				}
				// 연산 결과를 다시 스택에 담는다.
				aStack.push(String.valueOf(result));
			} 
			// 피연산자인 경우에는 스택에 무조건 담는다.
			else {
				aStack.push(symbol);
			}
		}
		
		// 모든 처리가 끝나면, 결과 값이 스택에 남아 있다.
		return Integer.valueOf(aStack.pop());
	}

	/**
     * 수식 분석 결과를 화면에 출력.
	 */
	public void print() {
		for (String symbol : symbols) {
			System.out.print(symbol + " ");
		}
		System.out.println();
	}

	/* 
	 * 새로운 항목(symbol)을 찾은 경우, 임시 버퍼의 내용을 항목 목록에 추가
	 */
	private void handleTempBuff() {
		if (numberBuffer != null) {
			symbols.add(numberBuffer);
			numberBuffer = null;
		}
	}

	/*
     * 연산자 혹은 괄호인지 검사.
	 */
	private boolean isMathOperatorOrParenthesis(char inputChar) {
		for (char op : OPERATORS) {
			if (inputChar == op) {
				return true;
			}
		}
		for(char brace : PARENTHESISES) {
			if (inputChar == brace) {
				return true;
			}
		}
		return false;
	}
	
	/*
	 * 연산자 인지 검사. 
	 */
	private boolean isOperator(char inputChar) {
		for (char op : OPERATORS) {
			if (inputChar == op) {
				return true;
			}
		}
		return false;
	}


	/*
	 * 연산자의 우선순위(priority)를 반환한다.
	 */
	private int priority(String symbol) {
		int priority = 0;
		switch(symbol){
		case "*":
		case "/":
			priority = 2;
			break;
		case "+":
		case "-":
			priority = 1;
		}
		
		return priority;
	}

}


Posted by 善 곽중선
2015.02.01 13:37

수식 입력 처리는 간단한 문제 같지만, 기초적인 원리와 기법을 이해하는데 있어서 중요한 과정입니다. 사칙연산 수식을 콘솔(console)에서 입력 받아 연산자와 피연산자로 분리하는 문제를 아래와 같이 자바로 코딩한 사례를 보여드리고 무엇이 문제인지 설명해보겠습니다.
import java.util.Scanner;

public class Operation {

	public static void main(String args[]) {
		// 스캐너 열기
		Scanner sc = new Scanner(System.in);
		String imsi = new String();
		int countOp = 0;
		char imsiOp = 0;

		do {
			System.out.print("Enter expression : ");
			imsi = sc.nextLine();
		} while (imsi.charAt(0) == '0');

		int size = imsi.length() + 1;
		char expr[] = new char[size];
		int num[] = new int[size];
		char op[] = new char[size];

		for (int i = 0; i < imsi.length(); i++) {
			expr[i] = imsi.charAt(i);
		}

		for (int i = 0; i < imsi.length(); i++) {
			for (int j = 0; j < imsi.length(); j++) {
				if (expr[j] >= 48 && expr[j] <= 57) {
					num[i] = (int) expr[j] - 48;
					if (expr[j + 1] > 48 && expr[j + 1] <= 57)
						num[i] = num[i] * 10 + ((int) expr[j] - 48);
				} else if (expr[j] == '*' && expr[j] == '/' && expr[j] == '+'
						&& expr[j] == '-') {
					op[i] = expr[j];
					countOp++;
				} else if (expr[j] == 0)
					break;
			}
		}

		for (int i = 0; i <= countOp; i++) {
			if (op[i] == '*' || op[i] == '/')
				System.out.printf("%d %c %d", num[i], op[i], num[i + 1]);
			else if (op[i] == '+' || op[i] == '-')
				if (op[i + 1] == '*' || op[i + 1] == '/')
					imsiOp = op[i];
			op[i] = op[i + 1];
			op[i + 1] = imsiOp;
		}

		// 스캐너 닫기
		sc.close();
	}

}


자바의 Scanner 클래스는 입력 스트림(input stream)에서 텍스트를 읽어들이는 기능들을 제공합니다. sc.nextLine() 메소드를 호출하면, 사용자가 콘솔에서 텍스트를 입력하고 엔터(enter key)를 누를 때까지 기다린 다음... 줄바꿈 문자(line feed)를 제외한 문자열을 반환합니다. 아래 코드에서는 안내 문구를 출력한 후에 사용자의 입력을 받아들이고, 입력 받은 문자열의 첫번째 문자가 '0' 가 아니면 계속 반복 입력 받게 됩니다. 안내 문구인 'Enter expressiion : ' 을 봐서는 첫번째 문자를 '0'으로 입력해야 한다는 것을 알 수 없으므로, 부정확한 - 혹은 잘못된 UX (User eXperience, 사용자 경험)입니다.


		do {
			System.out.print("Enter expression : ");
			imsi = sc.nextLine();
		} while (imsi.charAt(0) == '0');


더욱 더 잘못된 것은 입력을 받은 후의 수식 해석입니다.  ASCII 코드 테이블에서 48 부터 57 코드 값은 '숫자'에 해당하는 문자입니다만, 자바의 문자(character) 타입은 2 byte 크기이며, UNICODE-16 코드 체계를 사용합니다. 즉, ASCII 코드 값으로 비교하면 안되는 것입니다.

		if (expr[j] >= 48 && expr[j] <= 57) {
			num[i] = (int) expr[j] - 48;
			if (expr[j + 1] > 48 && expr[j + 1] <= 57)
				num[i] = num[i] * 10 + ((int) expr[j] - 48);
		}


2진수 기반의 컴퓨터 동작원리를 익혀야 하는 이유가 바로 이 점입니다. 하나의 문자를 표현하는 방법도 컴퓨터 내에서는 다양한 방법이 사용됩니다.


제가 작성한 수식 해석 프로그램 예시는 아래와 같습니다.


import java.util.Scanner;

/**
 * 
 * 사칙 연산 계산기.
 * 
 * - 사칙 연산 수식을 입력 받고,
 * - 해석한 후,
 * - 계산 결과를 출력한다.
 * 
* * @author sunnyk */ public class FourArithExprProcessor { public static void main(String args[]) { // 스캐너 열기 Scanner scanner = new Scanner(System.in); // 사칙연산 수식 입력 System.out.print("Input expression : "); String mathExpression = scanner.nextLine(); // 빈 문장을 입력한 경우, 오류 메시지 출력 if (mathExpression.trim().isEmpty()) { System.out .println("You entered empty expression, so do nothing and quit!"); } // 수식이 입력된 경우 해석... else { System.out.format( "Entered four arithmetic operation (expression) is '%s'\n", mathExpression); // 입력된 수식을 해석하는 파서(parser) 생성 FourArithExprParser parser = new FourArithExprParser(mathExpression); // 입력된 사칙연산 수식 해석 parser.parse(); // 수식 해석 결과 출력 parser.print(); } // 스캐너 닫기 scanner.close(); } }
import java.util.ArrayList;
import java.util.List;

/**
 * 사칙 연산 수식 해석 (parse four arithmetic expression)
 * 
 * @author sunnyk
 *
 */
public class FourArithExprParser {

	// 연산자 종류
	private static char[] OPERATORS = { '+', '-', '*', '/' };

	// 입력 수식
	private String inputExpr;

	// 수치 입력을 위한 임시 버퍼 (temporary buffer for input number)
	private String numberBuffer;

	// 입력된 연산자와 비연산자 목록 (분석 결과 리스트)
	private List <string> columns = new ArrayList<string>();

	/**
	 * 생성자, 사칙연산 문자열을 입력 받는다.
	 * 
	 * @param expression
	 *            사칙연산 문자열
	 */
	public FourArithExprParser(String expression) {
		this.inputExpr = expression;
	}

	/**
	 * 사칙 연산 문자열을 해석해, 연산자(operator)와 피연산자(operand)를 구분한다.
	 */
	public void parse() {

		// 입력 수식 문자열의 처음부터 마지막 문자까지 scan 한다.
		for (int idx = 0; idx < inputExpr.length(); idx++) {
			char charInExpr = inputExpr.charAt(idx);

			// 공백 문자이면, 무시(skip)
			// 공백 문자(white space char)는 space, tab, enter character 등 다양함.
			if (Character.isWhitespace(charInExpr)) {
				
				// 공백 이전에 입력된 수치 문자열을 분석 결과 리스트에 추가..
				handleTempBuff();
			}
			// 사칙 연산자이면, 연산자 항목에 추가
			else if (isMathOperator(charInExpr)) {
				columns.add(String.valueOf(charInExpr));
			}
			// 숫자이면, 임시 버퍼에 추가.
			else if (Character.isDigit(charInExpr)) {
				if(numberBuffer == null) {
					numberBuffer = String.valueOf(charInExpr);
				} else {
					numberBuffer += charInExpr;
				}
			}
		}

		// 마지막으로 버퍼에 남은 수치 값이 있으면, 분석 결과 리스트에 추가
		handleTempBuff();
	}

	/**
	 * 입력 문자가 사칙 연산자인지 검사
	 * 
	 * @param inputChar
	 *            입력 문자
	 * @return 사칙연산자 중에 하나이면 true, 아니면 false 반환
	 */
	private boolean isMathOperator(char inputChar) {
		for (char op : OPERATORS) {
			if (inputChar == op) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 수식 해석 중, 연산자 혹은 공백이 발견된 경우, 
	 * 이전에 임시 버퍼에 남겨둔 숫자 값을 입력 항목 목록에 추가.
	 */
	private void handleTempBuff() {
		if (numberBuffer != null) {
			columns.add(numberBuffer);
			numberBuffer = null;
		}
	}

	/**
	 * 수식 해석 결과를 출력한다.
	 */
	public void print() {
		// 입력된 수식의 전체 항(column)의 갯수를 출력한다.
		System.out.println("Number of columns in expression : " + columns.size());
		
		for(int idx=0; idx < columns.size(); idx++) {
			System.out.format( (idx+1) + "'th columns is '" + columns.get(idx) + "'\n");
		}
	}

}


Posted by 善 곽중선