상냥한 세상 :: [백준]2588번: 곱셈

본문으로 바로가기

[백준]2588번: 곱셈

category Computer Science/BAEKJOON JAVA Practice 2021. 12. 25. 20:23
  • 문제

이번엔 '곱셈'이 아니라 '곱셈과정'을 전개하는거와 같다. 

472와 385를 곱했을때 바로 181720이 나오는게 아닌, 그 중간중간에 어떻게 곱셈이 이루어지고 있는지를 코드로 보여주어야한다. 

(1)의 위치는 첫번째 세자리수, 그리고 이어서 (2)는 (1)에 곱해줄 두번째 세자리수가 등장한다. 

(3)의 위치에는 (1)와 (2)의 일의자리수를 곱한 결과값

(4)의 위치에는 (1)와 (2)의 십의자리수를 곱한 결과값

(5)의 위치에는 (1)와 (2)의 백의자리수를 곱한 결과값

(6)은 (3)+(4)+(5)을 합한, 즉 (1)X(2)의 결과값이 나와야한다. 

어쩌면, 이역시 줄바꿈을 써야할수도 있다. 좀더 빠른 연산을 위해 Buffered와 StringTokenizer을 사용해야할수도 있는점을 인지하자.


  • 코드1
import java.util.Scanner;
 
public class Main {
 
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
 
        int A = in.nextInt();
        String B = in.next();
        
        in.close();
 
        System.out.println(A * (B.charAt(2) - '0'));
        System.out.println(A * (B.charAt(1) - '0'));
        System.out.println(A * (B.charAt(0) - '0'));
        System.out.println(A * Integer.parseInt(B));
 
    }
}

이번에도 역시 charAt이라는 용어가 나온다. 그뿐만인가 -'0'도 나왔다. 어김없이 이 용어들을 짚고 넘어가겠다. 

 

Character Encoding(문자 인코딩):문자를 컴퓨터가 이해하기 쉽게 변환하는 과정,복잡한 신호를 0과1의 디지털신호(2진수)로 변환

Character Decoding(문자 디코딩):일반적으로 암호화되어 있거나 컴퓨터가 이해할 수 있는 값들을 알아보기 쉽게 변환하는 과정

 

ASCII Code(아스키 코드)영문 알파벳을 사용하는 대표적인 Character Encoding. ※컴퓨터와 통신장비를 비롯한 문자를 사용하는 장치들에 사용되고 있음. ※33개의 출력불가능한 제어문자들(대부분 더이상 사용되지않음)+공백포함 95개의 출력가능 문자들로 구성된 000(0x00)~127(0x7F)까지의 총 128개의 문자들. ※영문 키보드로 입력할 수 있는 모든 기호들이 할당되어 있는 부호체계※매우 단순, 간단하여 어느 시스템에도 적용가능

제어용 코드/출력가능한 코드/확장ASCII

Unicode(유니코드): 전세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준, 즉 모든 문자를 표현하려면 2바이트 이상을 사용하게 되는데, 이때 각 언어의 표준 인코딩을 정의해놓은 것. 

※유니코드의 1바이트는 아스키 코드 값과 호환되고, 그 밖의 문자를 2바이트나 그 이상의 조합으로 표현

※핸드폰에서 문자 텍스트를 칠때 한글자당 2바이트로 처리되는 이유가 유니코드를 사용하기 때문. 

 

http://www.unicode.org/charts/PDF/

 

Index of /charts/PDF

 

www.unicode.org

아스키코드의 출력가능한 문자들은 52개의 알파벳 대소문자(a~Z)와 10개의 숫자(0~9), 32개의 특수문자(@,#) 공백문자(Null)로 구성되어있다.

 

char형: Character의 약자로 문자 1개를 저장하는 '자료형'

public class Main {

public static void main(String[] args) {
    System.out.println('c');
  } 
} 

결과: c

public class Main { 
  public static void main(String[] args) {
    char character = 'c';
    System.out.println(character);
  } 
} 

결과: c

 

char의 자료형의 크기는 16비트=2바이트이다. 코드를 실행시키면 컴퓨터가 char character;을 선언한 부분을 실행시키고, 컴퓨터는 메모리 영역에 16비트(2바이트)공간을 확보한다. 이후 'c'를 선언해주면 c값을 16비트가 할당된 공간에 c의 바이너리값(0000000001000011)으로 메모리 공간을 덮어 쓴다. 

public class Main {
    public static void main(String[] args) {
        System.out.println(Character.SIZE);
    }
}

문자를 변수에 대입하면 문자 그대로 저장되는게 아니라 그 문자에 해당되는 ASCII 값(정수값)이 저장된다. 

각각의 알파벳에 따른 각각의 다른 ASCII값이 나온다. 그리고, 2번째 예시를 보면, \uD55C는 '한'에 해당하는 글자의 유니코드 값이고 16진수로 나타내고 있다. 16진수 숫자중 하나가 4비트를 사용하므로, 한글의 '한'이라는 글자를 표현하려면 4비트 4개 즉 2바이트가 필요하단 것을 알 수 있다. 

//알파벳 예시
public class Main {
	public static void main(String[] args) {
		char ch1 = 'A';
		System.out.println(ch1);
		System.out.println((int)ch1);
		
		char ch2 = 'B';
		System.out.println(ch2);
		System.out.println((int)ch2);

		char ch3 = 'C';
		System.out.println(ch3);
		System.out.println((int)ch3);

	}
}

결과
A
65
B
66
C
67


//유니코드 예시
public class Main {
	public static void main(String[] args) {
		char ch1 = '한';
		char ch2 = '\uD55C'; //유니코드
		
		System.out.println(ch1);
		System.out.println(ch2);
	}
}

결과
한
한

간단히 다시한번 얘기하면, 아래 str에 문자열 abcdef가 저장되어있을때, 이 배열에서 인덱스를 참조하여 해당 문자를 반환하는데, 이때 반환하는 값은 ASCII값인 문자 (char)이다. 

 

charAt(): String타입으로 받은 문자열을 char타입으로 한글자만 받는 함수, 주로 Scanner과 같이 응용되어 사용됨

  • charAt()의 응용
    1. 문자열 받기

    "Hello World"를 통해 문자열 선언했지만, charAt(0)을 통해 첫번째 문자열"H"만 받아오게 된다. 이후 "H"만 출력된다. 
    String str = "Hello World"; 
    System.out.print(str.charAt(0));​


    2. 숫자 받기

    문자열 변수 a에 "0"을 선언후, 정수형 변수 b에서 문자열로 선언된 0을 숫자 0으로 받는다
    -48을 하는 이유는 숫자 0~9까지는 Unicode상 48-57이기 때문이다. 그래서 48을 빼서 b에 숫자 0을 넣는다.
    String a = "0"; 
    int b = a.charAt(0) - 48; 
    System.out.println(b);​
    Unicode의 48~57은 숫자 0~9와 같다.

    3. 홀수/짝수 번째 문자열 받기

    Hello World의 홀수번째 문자열은 HloWrd, 짝수번째 문자열은 el ol 이다. 이를 출력하기 위한 코드는 아래와 같다. 
    public class Main{
        public static void main(String args[]){
            String str="Hello World";
            String odd="";
            String even="";
        
           int length=str.length(); //Hello World문자열의 길이를 가져온다 총길이 11, 인덱스 10
        
            for(int i=0; i<length; i++){
                if(i%2==0){
                    odd+=str.charAt(i);
                }
                else{
                    even+=str.charAt(i);
                }
            }
            System.out.println(odd);
            System.out.println(even);
        }
        
    }​

    예시

    여기서 나오는 결과값인 3은 숫자 3이아니라 문자3이다. 이에 해당하는 아스키 코드값은 51이고 실제론 51이 저장되는것이다. 
    String num="345";
    int result=num.charAt(0);

이번에도 멀~리 개념설명과 함께 둘랫길로 왔는데 이제 다시 코드를 보면 charAt()을 해준뒤 -'0'을 해준것이 보일것이다. -'0'을 써준 이유는 우리가 문자로 저장된 숫자가 아니라 보는 숫자 그대로의 값을 쓰기 위한것이다. 

System.out.println(A * (B.charAt(2) - '0'));
System.out.println(A * (B.charAt(1) - '0'));
System.out.println(A * (B.charAt(0) - '0'));


그런데, 여기서 궁금한건 왜 B(두번째 세자리 숫자)만 String으로 선언해주었냐다. 그 이유는 간단하다. 문제에서 세자리x세자리 통째로 계산을 요구하는게 아니라  A(첫번째 세자리 숫자)에 B(두번째 세자리 숫자)의 일의자리, 십의자리, 백의자리를 각각 곱한 값을 위에 코드대로 차례로 출력해주어야 하기 때문이다. 물론 정수형 입력을 받는게 nextInt()인만큼 문자열 입력은 next()로 받는다. 

int A = in.nextInt();
String B = in.next();


아래와같이 B(두번째 세자리 숫자)를 요소별로 쪼개준뒤..

B="385"
(B.charAt(2) - '0'));//5추출
(B.charAt(1) - '0'));//8추출
(B.charAt(0) - '0'));//3추출


 각각 A(첫번째 세자리 숫자)와 각각 곱해주고 출력해준다.

System.out.println(A * (B.charAt(2) - '0'));
System.out.println(A * (B.charAt(1) - '0'));
System.out.println(A * (B.charAt(0) - '0'));


이거로 끝이 아니라, 입력받은 문자열을 숫자로 바꿔주는 작업도 필요하므로, Integer.parseInt()를 통해 문자열을 int로 바꿔주고 A와 곱한 값을 출력하면 끝난다.

System.out.println(A * Integer.parseInt(B));

  • 결과


  • 코드2
import java.util.Scanner;

public class Main{
    public static void main(String[] args){
        Scanner in=new Scanner(System.in);
        int A=in.nextInt();
        int B=in.nextInt();
        
        in.close();
        
        System.out.println(A*(B%10));
        //385%10=5
        System.out.println(A*((B%100)/10));
        //385%100=85 85/10=8(정수형이므로 8)
        System.out.println(A*(B/100));
        //385/100=3
        System.out.println(A*B);
        //(6)의 결과값 통째로추출                   
    }
}

위와 같이 Scanner을 사용하긴 했지만, 이번엔 char이 아닌 바로 출력문에서 수학적으로 식을 구성하여 출력하는 방식이다. 

전형적으로 몇자리 숫자든 상관없이 1의자리, 10의자리, 100의자리 등등으로 잘라내줄때 사용하는 방법이 10, 100,1000... 10의 배수로 나눈 나머지를 보는것이다. 1의자리:10으로 나눈나머지, 10의자리:100으로 나눈 나머지/10, 100의자리: 1000으로 나눈나머지/100


  • 결과

Scanner+수학연산

 


  • 코드3
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.io.IOException;
    
    public class Main{
        public static void main(String[] args) throws IOException{
            BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
            
            int A=Integer.parseInt(br.readLine());
            int B=Integer.parseInt(br.readLine());
            
            br.close();
            
            StringBuilder sb=new StringBuilder();
            sb.append(A * (B%10));
            sb.append('\n');
    		
            sb.append(A * ((B%100)/10));
            sb.append('\n');
            
            sb.append(A * (B/100));
            sb.append('\n');
            
            sb.append(A * B);
            
            System.out.print(sb);
        }
    }

Builder을 사용했다. 다시 복습하면, StringBuilder은 동기화가 되지않는 단일 스레드환경에서 사용하는게 처리속도면에서 유리하다. 

Single-Thread(단일 스레드): 우리가 통상 써왔던 main메소드만을 이용해서 프로그램을 실행시키는 스레드환경
※main메소드가 종료될시, 프로그램종료

  • Single Thread 생성(예시)
    public class Main{
    	public static void main(String[] args){
        	System.out.println("main스레드 시작");
    		System.out.println("싱글스레드");
        }
    }
    
    //순차적으로 출력한다.​

Multi-thread(멀티 스레드): main쓰레드와 별개로 실행되는 스레드를 만들어 명령이 동시에 실행되도록 만든 프로그램
※증권거래 프로그램이나 채팅, 게임등에 많이 쓰임
※main메소드가 종료되어도 프로그램이 종료되지 않는다. 단, 모든 스레드가 종료되면 프로그램이 종료된다.

  • Multi-Thread 생성법1: Extends Thread
    // java.lang.Thread 클래스를 상속받아 사용자 정의 Thread 클래스를 생성할 수 있다. 
    class Hamburger extends Thread {
    
      // 이 클래스에서 상위 클래스인 Thread의 run() 메소드를 재정의를 해야 Thread의 실행부분을 작성할 수 있다.
        @Override
        public void run() {
            super.run();
            System.out.println("Hamburger 나왔습니다.");
        }
    }
    
    public class ThreadExample{
    
        public static void main(String[] args) {
            Hamburger hamburger = new Hamburger(); // Thread 객체를 생성한 후
            hamburger.start(); // start() 메소드를 호출하면 thread가 실행된다. 여기서 start() 메소드는 쓰레드 객체의 run() 메소드를 호출한다.
        }
    }
    
    결과: Hamburger 나왔습니다.
    (상속과 오버라이드는 길이관계상 추후에 다루겠다. 지금은 그냥 이렇구나 정도로만 보자. 아직 백준에서 하고있는건 싱글스레드다)
  • Multi-Thread 생성법2: implements Runnable interface
    // Runnable 인터페이스를 implements 키워드를 이용하여 구현하면 된다.
    class Hamburger implements Runnable {
    
        @Override
        public void run() {
            System.out.println("Hamburger 나왔습니다.");
        }
    }
    
    
    public class MultiThreadExample{
    
        public static void main(String[] args) {
            // Runnable 인터페이스를 구현해 Thread를 만들 경우
            // 객체 생성을 Thread 클래스 타입으로 만들어 줘야 한다.
            // run() 메소드의 트리거 역할을 하는 start()는 Thread 클래스에 정의되어있기 때문에 반드시 객체 생성시 Thread 타입으로 만들어야 한다.
            Thread hamburger = new Thread(new Hamburger());
            hamburger.start(); 
        }
    }
    
    결과: Hamburger 나왔습니다.​
    자세한건 https://velog.io/@ljs0429777/10주차-과제-멀티쓰레드-프로그래밍 참조


Single Thread환경인 만큼 이전포스트에서 문자열이 많은 환경에선 String보다 StringBuffer/StringBuilder이 압도적으로 성능이 좋고 StringBuffer보다 StringBuilder가 처리속도가 좀 더 좋은것으로 확인되었다. 또한 Scanner으로 입력받는것과 BufferedReader로 입력받는것중에선 BufferedReader의 처리속도가 Scanner보다 월등히 좋다. 

그러므로 BufferedReader+StringBuilder가 문자열사용빈도가 높은환경에서 어지간하면 좋다

그런데, 사실 이런 연산문제의 경우 문자열 참조보단 직접 연산을 시켜주는게 더빠르므로 코드1보다 코드2,3이 처리속도가 더 높다.


  • 결과

BufferedReader+StringBuilder+수학연산


  • 코드1,2,3 속도비교

Scanner+문자열 참조
Scanner+수학연산
BufferedReader+StringBuilder+수학연산