2015. 2. 1. 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 곽중선