자바의 정석 3판 (9) 연습문제 : java.lang. 패키지편

NtoZ·2023년 3월 24일
0

Java

목록 보기
18/23
post-thumbnail

자바의 정석 3판 Chapter9 - java.lang. 패키지


💡9-3. 문자열 조작하기

  • 예제:
//[9-3] 다음과 같은 실행결과가 나오도록 코드를 완성하시오.
class Exercise9_3 {
    public static void main(String[] args) {
        String fullPath = "c:\\jdk1.8\\work\\PathSeparateTest.java";
        String path = "";
        String fileName = "";

        /*
        (1) 알맞은 코드를 넣어 완성하시오.
        */
//        path = "c:\\jdk1.8\\work";
//        fileName = "PathSeparateTest.java";   🤪이렇게 풀라는 문제가 아니었다.

        path = fullPath.sp
        System.out.println("fullPath:"+fullPath);
        System.out.println("path:"+path);
        System.out.println("fileName:"+fileName);

    }
}

/*
<실행결과>
fullPath:c:\jdk1.8\work\PathSeparateTest.java
path:c:\jdk1.8\work
fileName:PathSeparateTest.java
 */
  • <🤪문제접근1>
    해당 문제는 이미 참조변수에 빈 문자열로 초기화된 String 값을 바꾸는 문제이다.
    ⭐그러나 String 클래스는 immutable 클래스이므로 값이 바뀔 수 없다.
    대신, 문자열이 다른 객체를 참조하게 할 수 있는데, 간단히 참조변수 값을 재지정해주면 된다.
    이렇게 되면 기존 객체는 참조하고 있던 변수와의 연결이 사라지므로, GC에 의해 메모리에서 제거당한다. 그리고 새로 할당한 String 객체가 Heap의 Metaplace 라는 공간의 상수 풀에 저장되게 된다.
    참조변수와 연결도 이어져 있으므로 해당 String 객체는 상수 풀에 잔존하게 되며,
    추후 큰따옴표"" 연산자로 String 객체를 생성하면 해당 객체가 String pool에 존재하는지 여부를 살피고,
    존재한다면 객체 생성 없이 해당 값을 참조하도록 참조변수의 연결을 조작한다.
    존재하지 않는다면 상수 풀에 또다른 객체를 생성한다.

  • <문제접근2>
    하지만 🤪문제접근1은 문제가 의도하는 바가 아니었다.
    ❗자세히 살펴보니, fullpath가 path와 fileName의 결합으로 이루어진 문자열이었다.
    이를 분할하여 저장하기 위해서 다음과 같은 방식을 생각했다.

  1. 구분자로 나누는 split메서드를 사용한다.
    ➡️ \를 기준으로 나눠야 한다. 문자열 배열을 만들어 path와 fileName으로 적절하게 나눠야 한다.
  2. subString()으로 일부 문자열을 발췌한다.
    ➡️ 어느 지점에서 자를 것인지 index를 알아야 한다.

- 🤪방법1 : 구분자로 나누는 split메서드를 사용했을 때

class Exercise9_3 {
    public static void main(String[] args) {
        String fullPath = "c:\\jdk1.8\\work\\PathSeparateTest.java";
        String path = "";
        String fileName = "";

        /*
        (1) 알맞은 코드를 넣어 완성하시오.
        */
//        path = "c:\\jdk1.8\\work";
//        fileName = "PathSeparateTest.java";   🤪이렇게 풀라는 문제가 아니었다.

        String[] strings = fullPath.split("\\\\");  //💡💡\\로 쓰면 오류가 발생한다. 자바에서 \는 escape문자열이기 때문에 \로 나누는 것이 된다.
        path = strings[0]+"\\"+strings[1]+"\\"+strings[2];
        fileName = strings[3];

        System.out.println("fullPath:"+fullPath);
        System.out.println("path:"+path);
        System.out.println("fileName:"+fileName);

    }
}

<결과>
fullPath:c:\jdk1.8\work\PathSeparateTest.java
path:c:\jdk1.8\work
fileName:PathSeparateTest.java
  • 주의 사항이 있다. 💡💡\\로 쓰면 오류가 발생한다.
    자바에서 \는 escape문자열이기 때문에 구분자로 \\을 쓰게된다면,
    \로 나누는 것이 된다.

  • 😊for문을 사용하면 일반화된 구문을 만들 수도 있다. 마지막 \\로 구분된 배열요소만 fileName에 담아주고 path로는 나머지 배열을 \\로 결합해주면 된다. 단, 마지막 \\는 붙지 않아야 한다.

 String[] strings = fullPath.split("\\\\");  //💡💡\\로 쓰면 오류가 발생한다. 자바에서 \는 escape문자열이기 때문에 \로 나누는 것이 된다.
        //path = strings[0]+"\\"+strings[1]+"\\"+strings[2];
        //fileName = strings[3];

        for(int i = 0; i<strings.length; i++) {
           if(i != strings.length-1) {
               path += strings[i] +"\\";
           } else { // i가 strings.length-1이면
               fileName = strings[i];
           }
        }
        path= path.substring(0, path.length()-1);  // 마지막 `\`는 빼주어야 한다.

        System.out.println("fullPath:"+fullPath);
        System.out.println("path:"+path);
        System.out.println("fileName:"+fileName);
<결과>
fullPath:c:\jdk1.8\work\PathSeparateTest.java
path:c:\jdk1.8\work
fileName:PathSeparateTest.java
  • bing AI 리팩토링 버전
리팩토링을 하기 위해서는 어떤 부분을 개선하면 좋을지 생각해보아야 합니다. 일반적으로 코드를 개선하는 방법은 다음과 같습니다:

1. 가독성을 높이기 위해 변수명을 명확하게 지정합니다.
2. 중복 코드를 제거합니다.
3. 메소드를 분리하여 코드의 재사용성을 높입니다.
따라서 다음과 같이 코드를 개선할 수 있습니다:

public static void main(String[] args) {
    String fullPath = "c:\\jdk1.8\\work\\PathSeparateTest.java";
    String path = "";
    String fileName = "";

    String[] strings = fullPath.split("\\\\");
    int lastIndex = strings.length - 1;	//⭐⭐lastIndex라는 변수를 만들었더니 가독성이 훨씬 좋아졌다.

    for (int i = 0; i < lastIndex; i++) {
        path += strings[i] + "\\";
    }
    fileName = strings[lastIndex];

    System.out.println("fullPath:" + fullPath);
    System.out.println("path:" + path);
    System.out.println("fileName:" + fileName);
}
  • 💡 코드 리팩토링 관점도 생각해보자!
    마지막 인덱스에 대해 lastIndex라는 변수를 만들어서 그 값을 활용했더니 코드가 훨씬 깔끔해졌다.

  • <문제접근3> lastIndexOf 메소드와 subString 메소드 활용하기
public static void main(String[] args) {
        String fullPath = "c:\\jdk1.8\\work\\PathSeparateTest.java";
        String path = "";
        String fileName = "";

        /*
        (1) 알맞은 코드를 넣어 완성하시오.
        */

        int LastIndexOfRegex = fullPath.lastIndexOf("\\");
        path = fullPath.substring(0, LastIndexOfRegex);
        //✔️⭐ 시작인덱스는 포함이기 때문에 시작인덱스+1을 해주어야함!
        fileName = fullPath.substring(LastIndexOfRegex+1); 

        System.out.println("fullPath:"+fullPath);
        System.out.println("path:"+path);
        System.out.println("fileName:"+fileName);

<실행결과>
fullPath:c:\jdk1.8\work\PathSeparateTest.java
path:c:\jdk1.8\work
fileName:PathSeparateTest.java

  • 💡모범답안 : lastIndexOf와 subString 메소드 활용하기
class Exercise9_3 {
    public static void main(String[] args) {
        String fullPath = "c:\\jdk1.8\\work\\PathSeparateTest.java";
        String path = "";
        String fileName = "";
        int pos = fullPath.lastIndexOf("\\");
        if (pos != -1) {	//💡💡 lastIndexOf를 사용할 때는
            path = fullPath.substring(0, pos);
            fileName = fullPath.substring(pos + 1);
        }

        System.out.println("fullPath:" + fullPath);
        System.out.println("path:" + path);
        System.out.println("fileName:" + fileName);
    }
}
  • 💡💡 lastIndexOf를 사용할 때 역시 유효성 검사가 필요하다. 왜냐하면 인자로 들어오는 fullPath 값에 \이 하나라도 포함되어 있지 않다면, -1을 반환하기 때문이다.

9-4. 숫자에서 문자변환

  • 위의 클린코드 관점을 적용하여 int curElement에 현재 요소값을 담아서 활용해보았다.
class Exercise9_4 {
    static void printGraph(int[] dataArr, char ch) {
    /*
    (1) printGraph메서드를 작성하시오.
    */
        for(int i=0; i<dataArr.length; i++) {
            int curElement = dataArr[i];
            for(int j=0; j<curElement; j++) {
                System.out.print(ch);
            }
            System.out.println(String.valueOf(curElement));
        }
    }

    public static void main(String[] args) { printGraph(new int[]{3,7,1,4},'*');
    }
}

<결과>
***3
*******7
*1
****4


9-5. 특정 문자열 개수 확인하기

  • 예제
class Exercise9_5 {
    public static int count(String src, String target) { 
        int count = 0; // 찾은 횟수
        int pos = 0; // 찾기 시작할 위치

        /*
        (1) 반복문을 사용해서 아래의 과정을 반복한다.
        1.	src에서 target을 pos의 위치부터 찾는다.
        2.	찾으면 count의 값을 1 증가 시키고,
        pos의 값을 target.length만큼 증가시킨다.
        3.	indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
        */
    }

    public static void main(String[] args) {
        System.out.println(count("12345AB12AB345AB","AB"));
        System.out.println(count("12345","AB"));
    }
}

/*
<실행결과>
3
0
 */
  • <문제접근1>
    어떤 문자열에 특정 문자열이 몇 번 반복되는지 확인하는 메서드이다.
    src안의 target의 개수를 찾아야한다.
    🤔아이디어가 떠올랐는데, for문을 사용할 필요 없이 String 클래스의 split 메서드를 활용하여 분할된 요소의 개수-1만큼 return해주면 되지 않을까?
    System.out.println(Arrays.toString(src.split(target)));
    return src.split(target).length-1 ; //target으로 분할된 String 배열의 요소개수-1
    //⭐아, 이렇게 하면 target이 가장 마지막에 존재하는 경우 src.split(target)의 요소로 반영되지 않는다... 테스트 코드의 중요성!
    //그런데 이럴경우 if(src.split(target).endsWith(target)일 시, 리턴 값을 -1하지 않아도 된다. 마찬가지로 startsWith(target)할 때는 -1할 필요가 없음.
class Sol_Exercise9_5 {
    public static int count(String src, String target) {
        int count = 0; // 찾은 횟수
        int pos = 0; // 찾기 시작할 위치

    /*
        (1) 반복문을 사용해서 아래의 과정을 반복한다.
            1.	src에서 target을 pos의 위치부터 찾는다.
            2.	찾으면 count의 값을 1 증가 시키고,
            pos의 값을 target.length만큼 증가시킨다.
            3.	indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
    */	🔥🔥🔥
        String[] div_src = src.split(target);   //src를 target으로 분할한 문자열배열을 div_src에 저장
            if(src.endsWith(target)) return div_src.length; //끝문자로 target을 가지고 있으면 나눠진 요소 개수 모두 반환
            else return div_src.length-1;    //끝 문자로 target을 가지고 있지 않으면 나눠진  요소개수에서 -1해야한다.
            //🔥🔥🔥

    }

    public static void main(String[] args) {
        System.out.println(count("12345AB12AB345AB","AB"));
        System.out.println(count("12345","AB"));
    }
}

<결과>
3
0
  • <문제접근2>
class Sol_Exercise9_5 {
    public static int count(String src, String target) {
        int count = 0; // 찾은 횟수
        int pos = 0; // 찾기 시작할 위치

    /*
        (1) 반복문을 사용해서 아래의 과정을 반복한다.
            1.	src에서 target을 pos의 위치부터 찾는다.
            2.	찾으면 count의 값을 1 증가 시키고, pos의 값을 target.length만큼 증가시킨다.
            3.	indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
    */
        for(pos=src.indexOf(target); pos<src.length(); ) {    //⭐굳이 pos에 증감식을 붙이지 않는다. pos는 for문 안에서 계속 증가재할당 할 것이다.
            if(src.indexOf(target, pos)!=-1) { // src안에 target이 존재하면
                pos += target.length(); //⭐for문에서 조건문의 기준이 되는 변수에 =을 붙이지말자. 원래 실수로 =을 붙였는데 무한루프문에 빠졌다.
                count++;
            } else return count;
        }
        return count;

    }

    public static void main(String[] args) {
        System.out.println(count("12345AB12AB345AB","AB"));
        System.out.println(count("12345","AB"));
    }
}
<결과>
5
0
  • ❓ pos의 위치를 src.indexOf(target)에서 시작하여 target이 발견될 때마다 pos에 target.length()의 값만큼 누계하고, count를 ++ 했는데 값이 3이 아닌 5가 나왔다. 어떤 것이 잘못된 걸까?
    ➡️ debug를 찍어보니 이렇게 하면 pos가 5에서 시작하여 2만큼 증가한다. 이 때, count+1;
    그런데 pos가 7일 때도 남은 부분에 target이 있으니 count++; pos+2가 된다.pos가 9, 11, 13까지 count가 ++되며 15가 되는째에 for문을 탈출하고 count가 반환된다. 그래서 5가 나오는 것이다.
    ✔️한마디로, pos의 포인터(가리키는 지점)이 +2씩 등차수열로 커지게 설계한 것이 잘못되었다.
    ✔️원래 설계는 pos가 다음 target이 있는 자리의 index값을 가리키게 하는 것이었다. 그렇다면 pos를 target의 길이만큼 +2시키고, 또 indexOf메서드로 pos 인덱스에서 target을 찾은 값을 pos에 재할당해주는 것이 필요하다. ➡️ 그런데 또 pos의 값이 AB가 없는 순간까지 증가하는 순간
    pos = src.indxOf(target, pos);가 -1이 되어 무한 루프문에 빠지는 문제가 있다. ➡️ 그렇다면 pos가 -1이 되는 순간 문자열 탐색이 끝났다는 의미이므로 count를 반환하는 조건문을 추가해보자.

  • <문제접근3>

 public static int count(String src, String target) {
        int count = 0; // 찾은 횟수
        int pos = 0; // 찾기 시작할 위치

    /*
        (1) 반복문을 사용해서 아래의 과정을 반복한다.
            1.	src에서 target을 pos의 위치부터 찾는다.
            2.	찾으면 count의 값을 1 증가 시키고, pos의 값을 target.length만큼 증가시킨다.
            3.	indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
    */
        for(pos=src.indexOf(target); pos<src.length(); ) {    //⭐굳이 pos에 증감식을 붙이지 않는다. pos는 for문 안에서 계속 증가재할당 할 것이다.
            if(src.indexOf(target, pos)!=-1) { // src안에 target이 존재하면
                ++count;
                pos += target.length(); //⭐for문에서 조건문의 기준이 되는 변수에 =을 붙이지말자. 원래 실수로 =을 붙였는데 무한루프문에 빠졌다.
                pos = src.indexOf(target, pos);
                if(pos == -1) return count;
            } else return count;    //애초에 src에 target이 없을 경우 count 반환
        }
        return count;
    }

<결과>
3
0

  • <모범풀이> : while(true)문과 조건식을 사용하여 pos값을 적절히 재할당
class Exercise9_5 {
    public static int count(String src, String target) { int count = 0; // 찾은 횟수
        int pos = 0; // 찾기 시작할 위치

//	(1) 반복문을 사용해서 아래의 과정을 반복한다.
        while(true) {
//	1. src에서 target을 pos의 위치부터 찾는다.
            pos = src.indexOf(target,pos);

//	2. 찾으면 count의 값을 1 증가 시키고,
//	pos의 값을 target.length만큼 증가시킨다.
            if(pos!=-1) {
                count++;
                pos += target.length(); // pos를 찾은 단어 이후로 옮긴다.
            } else {
//	3. indexOf의 결과가 -1이면 반복문을 빠져나가서 count를 반환한다.
                break;
            }
        }

        return count;
    }

    public static void main(String[] args) { System.out.println(count("12345AB12AB345AB","AB")); System.out.println(count("12345","AB"));
    }
}

  • 내 풀이와 차이점 :
  1. while(true)문으로 pos의 값을 적절히 indexOf(target, pos)로 재할당한다. ⭐단, 해당 if(pos=-1)이 아닐 조건일 때 pos += target.length()해야 무한 순환에 빠지지 않는다.
  2. ⭐클린코드 관점에서 내가 src.indexOf(target, pos)로 사용했던 부분을 값을 재할당한 pos를 사용하여 조건식을 깔끔하게 만들었다.
    항상 코드의 가독성을 생각하자!

💡9-9. 주어진 문자열에서 금지된 문자열 제거하기

  • 예제
//[9-9] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

/*
메서드명 : delChar
기	능 : 주어진 문자열에서 금지된 문자들을 제거하여 반환한다.
반환타입 : String
매개변수 : String src - 변환할 문자열, String delCh - 제거할 문자들로 구성된 문자열
         [힌트] StringBuffer와 String클래스의 charAt(int i)과 indexOf(int ch)를 사용하라.
 */

class Exercise9_9 {
    /*
    (1) delChar메서드를 작성하시오.
    */


    public static void main(String[] args)  {
    System.out.println("(1!2@3^4~5)"+" -> "
            + delChar("(1!2@3^4~5)","~!@#$%^&*()"));
    System.out.println("(1 2	3 4\t5)"+" -> "
            + delChar("(1 2	3 4\t5)"," \t"));

}
}
/*
<실행결과>
(1!2@3^4~5) -> 12345
(1 2	3 4 5) -> (12345)
 */
  • ❌풀이 접근1:
    1. src의 char값들을 delCh 안의 char 타입과 일일이 대조해서 해당 데이터를 삭제하면 될 것이다.
    2. ⭐문자열 조작에 관한 내용이므로 StringBuffer 객체를 만들어서 사용한다.
    3. StringBuffer 객체 주소값을 담고 있는 참조변수 sb는 String src의 내용을 복사해서 만들어졌다.
    4.🔥for문을 사용해 sb의 요소 중 delCh안에 있는 각각의 요소를 확인하여 indexOf로 -1을 반환하지 않으면(지워야할 값이 sb안에 존재하면), sb.delete(delCh.charAt[i])를 사용해 삭제하게하면 될 것이다.

  • 내 풀이1
class Sol_Exercise9_9 {
    /*
    (1) delChar메서드를 작성하시오.
    */
    static String delChar(String src, String delCh) {
        if(src==null||delCh==null) return null;
        StringBuffer sb = new StringBuffer(src); //🔥 문자열 조작을 위한 SB객체 sb를 만든다.
        for(int i=0; i<sb.length(); i++) {
            char del = delCh.charAt(i); //🔥del은 delCh안의 문자를 순회한다.
            int delIndex = sb.indexOf(del+""); //🔥지워야할 문자 del의 인덱스를 delIndex에 저장
            if(delIndex!=-1) {    //🔥지워야할 문자 del의 인덱스가 -1이 아니면 (즉, 지워야할 문자 del이 sb에 존재하면)
                sb.deleteCharAt(delIndex);
            }    //delIndex==1이면 지워야할 문자가 없다는 말이므로 다시 for문으로 돌아가야 한다.
        }
        return sb.toString(); //⭐sb.toString()은 String 타입이기 때문에 잘 기억해두자!
    }

    public static void main(String[] args) {
        System.out.println("(1!2@3^4~5)" + " -> "
                + delChar("(1!2@3^4~5)", "~!@#$%^&*()"));
        System.out.println("(1 2	3 4\t5)" + " -> "
                + delChar("(1 2	3 4\t5)", " \t"));

    }
}
/*
<실행결과>
(1!2@3^4~5) -> 12345
(1 2	3 4 5) -> (12345)
 */

/*
<🔥문제접근>
src의 char값들을 delCh 안의 char 타입과 일일이 대조해서 해당 데이터를 삭제하면 될 것이다.
⭐문자열 조작에 관한 내용이므로 StringBuffer 객체를 만들어서 사용한다.
StringBuffer 객체 주소값을 담고 있는 참조변수 sb는 String src의 내용을 복사해서 만들어졌다.
🔥for문을 사용해 sb의 요소 중 delCh안에 있는 각각의 요소를 확인하여 indexOf로 -1을 반환하지 않으면
sb.delete(delCh.charAt[i])를 사용해 삭제하게하면 될 것이다.
 */
 
 <❌❌풀이결과>
 (1!2@3^4~5) -> (12345)
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 2
	at java.lang.String.charAt(String.java:658)
	at Sol_Exercise9_9.delChar(Sol_Exercise9_9.java:20)
	at Sol_Exercise9_9.main(Sol_Exercise9_9.java:33)
  • ❓왜 오류가 발생했나?
    delCh.length()src.length()보다 클 경우는 idelCh의 각 요소들을 순회하면서 src에 마찬가지의 요소가 있다면 해당 요소를 제거하는 식으로 정상적으로 동작한다.
    ❗❗그러나,
    src.length()delCh.length()보다 클 경우, isrc.length에 도달할 때까지 i++하게 되는데, 이 때 idelCh.length()보다 커져서 String의 배열을 벗어나는 에러가 발생하게 된다.
    ➡️✔️해결하기 위해서는
    idelCh.length()보다 커질 때 sb를 리턴하도록 조건을 걸어야 한다.
    if(i==delCh.length()-1) return sb.toString(); //✔️해당 조건이 있어야 순회하는 i의 크기가 delCh의 인덱스 범위를 넘지 않는다.


    ❌❌ 그러나, 또 다른 문제가 발생했는데,
    해당 식이 정상적으로 종료된다고 하더라도 src에서 delCh의 내용이 한 번씩 밖에 삭제되지 않는다.
  (1!2@3^4~5) -> (12345)
  (1 2	3 4	5) -> (123 4	5)	//❌❌ 여기서 문제 발생
  • ➡️ 💡⭐⭐핵심은 몇 번 반복되느냐가 아니라, 조건이 있을 때마다 반복되어야 하는 것이므로 while문을 사용해보는 것이 옳다.
    for문과 while문을 함께 사용해보자.
static String delChar(String src, String delCh) {
        if(src==null||delCh==null) return null; //⭐ 매개변수에 대한 유효성 검사 필수!
        StringBuffer sb = new StringBuffer(src);    //🔥 문자열 조작을 위한 SB객체 sb를 만든다.

            for(int i=0; i<delCh.length(); i++) {   //🔥 i는 0부터 시작하여 delCh의 인덱스를 순회한다.
              String delDest = delCh.charAt(i) +"";   //🔥delDest는 src에서 지워야할 목표문자. indexOf의 매개변수로 사용하기 위해 String으로 변환.
              while(sb.indexOf(delDest)!=-1) { //⭐🔥🔥지워야할 문자의 인덱스가 sb에 존재하면 계속해서 지운다. -1이면 다시 for문으로 돌아간다.
                  int delIndex = sb.indexOf(delDest); //🔥delIndex는 sb에서 지워야할 문자의 인덱스 ⭐재지정이 필요하다.
                  sb.deleteCharAt(delIndex);    //sb에서 delIndex번 인덱스를 지운다.
              }
            }
        return sb.toString(); //⭐sb.toString()은 String 타입이기 때문에 잘 기억해두자!
    }
<결과>
(1!2@3^4~5) -> 12345
(1 2	3 4	5) -> (12345)
  • 💡 for문과 while문을 균형있게 사용하는 것이 중요하다.
    ⭐while문은 반복횟수를 모르는데, 특정 조건을 계속 유지하고 싶을 때 사용하는 것이 좋다.
    ⭐반복횟수보다 조건이 중요하다면 while을 적절히 사용하자.
  • ⭐for문에서 적절히 값을 재할당할 요소를 주의깊게 생각해야한다.

모범 풀이

  • <모범풀이>
class Exercise9_9 {
    public static String delChar(String src, String delCh) { 
    	StringBuffer sb = new StringBuffer(src.length());

        for(int i=0; i < src.length();i++) {
            char ch = src.charAt(i);

        //⭐⭐ ch가 delCh에 포함되있지 않으면(indexOf()로 못찾으면) sb에 추가
            if(delCh.indexOf(ch)==-1) // indexOf(int ch)를 호출
                sb.append(ch);
        }

        return sb.toString(); // StringBuffer에 저장된 내용을 String으로 반환
    }
  • [해설] 반복문을 이용해서 주어진 문자열(src)의 문자를 순서대로 가져와서, 삭제할 문자열(delCh)에 포함되었는지 확인한다. 포함되어 있지 않을때(indexOf()의 결과가 -1일 때만) StringBuffer에 추가한다.

  • [내 풀이와의 차이]

  1. char ch라는 변수에 src.length(i)를 저장했다. src의 문자를 하나씩 순회한다.
  2. if(delCh.indexOf(ch)==-1 delCh에 해당 ch 문자가 포함되어 있지 않으면 새로 만든 SB 변수 sb에 이를 append한다.
    ➡️ 💡⭐⭐아예 처음부터 빈 문자열인 sb에 src와 delCh에서 겹치지 않는 문자를 추가하는 방식으로 했다는 점이 가장 큰 차이
    (나는 src의 값을 복사한 SB 객체 sb를 겹치는 순으로 delete하면서 남기려고 했던 반면,
    모범답안은 sb 자체를 빈 문자열로 만들었다가 겹치지 않는 것만 append함)

9-10.

  • 예제:
/*
[9-10] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

메서드명 : format
기	능 : 주어진 문자열을 지정된 크기의 문자열로 변환한다. 나머지 공간은 공백으로 채운다.
반환타입 : String
매개변수 : String str - 변환할 문자열
        int length - 변환된 문자열의 길이
        int alignment - 변환된 문자열의 정렬조건
        (0:왼쪽 정렬, 1: 가운데 정렬, 2:오른쪽 정렬)
 */

class Exercise9_10
{
/*
(1) format메서드를 작성하시오.
1.	length의 값이 str의 길이보다 작으면 length만큼만 잘라서 반환한다.
2.	1의 경우가 아니면, length크기의 char배열을 생성하고 공백으로 채운다.
3.	정렬조건(alignment)의 값에 따라 문자열(str)을 복사할 위치를 결정한다. (System.arraycopy()사용)
4.	2에서 생성한 char배열을 문자열로 만들어서 반환한다.
*/

    public static void main(String[] args) {
        String str = "가나다";
        System.out.println(format(str,7,0)); // 왼쪽 정렬
        System.out.println(format(str,7,1)); // 가운데 정렬
        System.out.println(format(str,7,2)); // 오른쪽 정렬
    }
}

/*
<실행결과>
가나다
    가나다
        가나다
 */
  • 내 풀이
/*
[9-10] 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

메서드명 : format
기	능 : 주어진 문자열을 지정된 크기의 문자열로 변환한다. 나머지 공간은 공백으로 채운다.
반환타입 : String
매개변수 : String str - 변환할 문자열
        int length - 변환된 문자열의 길이
        int alignment - 변환된 문자열의 정렬조건
        (0:왼쪽 정렬, 1: 가운데 정렬, 2:오른쪽 정렬)
 */

class Sol_Exercise9_10  {
/*
(1) format메서드를 작성하시오.
1.	length의 값이 str의 길이보다 작으면 length만큼만 잘라서 반환한다.
2.	1의 경우가 아니면, length크기의 char배열을 생성하고 공백으로 채운다.
3.	정렬조건(alignment)의 값에 따라 문자열(str)을 복사할 위치를 결정한다. (System.arraycopy()사용)
4.	2에서 생성한 char배열을 문자열로 만들어서 반환한다.
*/
    static String format(String str, int length, int alignment) {
        if(str==null||length<1||alignment<0) return "인자가 정상적이지 않습니다.";
        if(length<str.length()) return str.substring(0, length);
            char[] chars = new char[length];
            for(int i=0; i<length; i++) {
                chars[i]=' ';
            } //❓원래 char타입 배열에서 char 기본값이 공백이지 않나? 그런데 이걸 써주지않으면 이상한 문자가출력됨.
            switch (alignment) {
                case 0: //왼쪽 정렬
                    System.arraycopy(str.toCharArray(), 0, chars, 0, str.length());   //⭐str.toCharArray()로 문자열을 문자배열로 바꿔주지 않으면 ArrayStoreException 예외발생!
                    break;
                case 1: //가운데 정렬
                    System.arraycopy(str.toCharArray(), 0, chars, length/3, str.length());
                    break;
                case 2: //오른쪽 정렬
                    System.arraycopy(str.toCharArray(), 0, chars, length*2/3, str.length());
                    break;
            }
        return new String(chars);
    }

    public static void main(String[] args) {
        String str = "가나다";
        System.out.println(format(str,7,0)); // 왼쪽 정렬
        System.out.println(format(str,7,1)); // 가운데 정렬
        System.out.println(format(str,7,2)); // 오른쪽 정렬
    }
}

<실행결과>
가나다    
  가나다  
    가나다
  • ⭐ System.arraycopy()에서 str.toCharArray()로 문자열을 문자배열로 바꿔주지 않으면 ArrayStoreException 예외발생!

  • 모범 풀이
    static String format(String str, int length, int alignment) {

//	1. length의 값이 str의 길이보다 작으면 length만큼만 잘라서 반환한다.
        int diff = length - str.length();
        if(diff < 0) return str.substring(0, length);

//	2. 1의 경우가 아니면, length크기의 char배열을 생성하고 공백으로 채운다. char[] source = str.toCharArray(); // 문자열을 char배열로 변환 char[] result = new char[length];

        for(int i=0; i < result.length; i++)
            result[i] = ' ';	// 배열 result를 공백으로 채운다.

//	3. 정렬조건(alignment)의 값에 따라 문자열(str)을 복사할 위치를 결정한다.
        switch(alignment)
        {   case 0 :
            default :   //⭐case와 default를 같이 사용하여 1,2를 제외한 값이 나오면 왼쪽정렬하도록 함.
                System.arraycopy(source, 0, result, 0, source.length); break;
            case 1 :
                System.arraycopy(source, 0, result, diff/2, source.length); break; //⭐(length-str.length)를 2로 나누는 지점이 복사스타트지점
            case 2 :
                System.arraycopy(source, 0, result, diff, source.length); break;
        }

//	4. 2에서 생성한 char배열을 문자열로 만들어서 반환한다.
        return new String(result);
    } // static String format(String str, int length, int alignment) {

    public static void main(String[] args) { String str = "가나다";

        System.out.println(format(str,7,0)); // 왼쪽 정렬 System.out.println(format(str,7,1)); // 가운데 정렬 System.out.println(format(str,7,2)); // 오른쪽 정렬
    }
}
  • 차이점:
  1. defualt:를 잊어버림. 스위치문을 사용할 때는 반드시 default문이 필수이다.
  2. result.lenth - str.length()diff라는 변수에 담아서,
    diff/2, diff 등으로 복사시작점을 잡아주는 것이 모범풀이,
    나는 (chars.)length를 기준으로 /3 /2로 정렬 위치를 잡았다.

💡9-12. Math.random() 시작 값과 끝 값 지정하기

  • 예제
/*
[9-12] 다음과 같이 작성된 메서드를 작성하고 테스트하시오.
[주의] Math.random()을 사용하는 경우 실행결과와 다를 수 있음.

메서드명 : getRand
기	능 : 주어진 범위(from~to)에 속한 임의의 정수값을 반환한다. (양쪽 경계값 모두 범위에 포함)
from의 값이 to의 값보다 클 경우도 처리되어야 한다.
반환타입 : int
매개변수 : int from - 범위의 시작값, int to - 범위의 끝값

[Hint] Math.random()과 절대값을 반환하는 Math.abs(int a),
그리고 둘 중에 작은 값을 반환하는 Math.min(int a, int b)를 사용하라.

 */

class Exercise9_12 {
/*
(1) getRand메서드를 작성하시오.
*/

    public static void main(String[] args) {

        for (int i = 0; i < 20; i++)
            System.out.print(getRand(1, -3) + ",");
    }
}

/*
<실행결과>
0,-1,1,0,-2,-2,1,1,-3,0,-1,1,1,1,0,-1,1,0,-1,-3,
 */
  • 💡풀이 접근 :
    0<=x<1을 조작해 from<=x<to로 만들고 싶다면
    양변에 (to-from)을 곱한 다음 + from 해주면 된다.
    그리고 from<=x<=to로 끝 숫자 to까지 포함하고 싶으면
    양변에 (to-from+1)을 곱한 다음 + 다시 from 해주면 된다.


    원리는 다음과 같다.
    최우변이 1이므로 to-from을 곱해주는 것은, 최우변을 to-from으로 만들어주는 것과 같다. (이 때, 최좌변은 0이다.)
    최우변이 to-from일 때 다시 from을 더해주면 최우변은 to가 된다.
    (이 때, 최좌변은 from이 더해진 값이다.)

  • 풀이 코드:

class Sol_Exercise9_12 {
    /*
    (1) getRand메서드를 작성하시오.
    */
    static int getRand(int from, int to) {
        if(from>to) {   //from이 to보다 클 경우 두 변수의 값 바꾸기
            int tmp = from;
            from = to;
            to = tmp;
        }
        int result = (int)(Math.random()*(to-from+1))+from;
        return result;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 20; i++)
            System.out.print(getRand(1, -3) + ",");
    }
}
<실행결과>
-2,0,0,-1,-2,0,1,-1,1,-3,0,0,1,-1,-1,-3,-1,-3,-1,0,
  • 모범 풀이
class Exercise9_12
{
    public static int getRand(int from, int to) {
        return (int)(Math.random() * (Math.abs(to-from)+1))+ Math.min(from,to);
    }

    public static void main(String[] args)
    {
        for(int i=0; i< 20; i++) System.out.print(getRand(1,-3)+",");
    }
}
  • 내 답과 다른점
  1. 조건문을 사용하지 않았다. (from, to의 대소비교 후 바꿔주는 조건문x)
  2. 대신, Math.abs(to-from+1)을 곱해주고, Math.min(from, to)를 더해줬다. 이렇게 하면 조건문을 사용하지 않아도 대소비교에 따라서 적절한 값을 곱하고 더할 수 있다.

💡9-13. 문자열에서 특정 문자열의 개수 찾기

  • 예제
/*
[9-13] 다음은 하나의 긴 문자열(source) 중에서 특정 문자열과 일치하는 문자열의 개수 를 구하는 예제이다.
빈 곳을 채워 예제를 완성하시오.

*/
public class Exercise9_13 {
    public static void main(String[] args) {
        String src = "aabbccAABBCCaa";
        System.out.println(src);
        System.out.println("aa를 "+stringCount(src,"aa")+"개 찾았습니다.");
    }

    static int stringCount(String src,String key){
        return stringCount(src,key,0);
    }

    static int stringCount(String src,String key,int pos){
        int count=0;
        int index=0;

        if(key==null||key.length()==0) return 0;

        /*
        (1) 알맞은 코드를 넣어 완성하시오.
        */

        return count;
    }
}

/*
<실행결과>
aabbccAABBCCaa
aa를 2개 찾았습니다.
 */
  • 내 풀이
	static int stringCount(String src,String key,int pos){
        int count=0;
        int index=0;

        if(key==null||key.length()==0) return 0;

        /*
        (1) 알맞은 코드를 넣어 완성하시오.
        */
        for(pos=src.indexOf(key); pos<src.length(); ) { //🔥
            if(pos == -1) break;
            ++count;    //pos가 존재하면 ++count
            pos += key.length();    //pos는 key.length()값이 더해져야함.
            pos = src.indexOf(key, pos); //재할당된 pos 값부터 다시 key를 찾고 for문으로 되돌아감
        }   //🔥

        return count;
    }
  • 모범 풀이
<모범풀이>
   static int stringCount(String src, String key, int pos) {
        int count = 0;
        int index = 0;

        if (key == null || key.length() == 0) return 0;

        while((index = src.indexOf(key, pos))!=-1) { //⭐ index에 src.indexOf(key, pos)를 저장한 값이 -1이 아닌 동안
            count++;    //⭐ count를 1 증가하고
            pos = index + key.length(); //⭐ pos 값을 재할당한다.
        }

        return count;
    }
  • 💡 모범풀이에서 while((index = src.indexOf(key, pos))!=-1)라는 조건문에 주목하자.
    index 변수에 src.indexOf(key, pos)를 저장한 후, 그 값을 -1과 비교함으로써 값의 재할당과 조건식의 기준 모두를 충족할 수 있다.
    추후 활용해보자.

❓💡 Pattern과 Matcher 클래스 활용하여 2차원 배열 값 찾기

  • 예제
/*
[9-14] 다음은 화면으로부터 전화번호의 일부를 입력받아 일치하는 전화번호를 주어진
        문자열 배열에서 찾아서 출력하는 프로그램이다.
        알맞은 코드를 넣어 프로그램을 완성하시오.

        [Hint] Pattern, Matcher클래스를 사용할 것
*/

import java.util.*;
import java.util.regex.*;

class Exercise9_14 {
    public static void main(String[] args) {
        String[] phoneNumArr = {
                "012-3456-7890",
                "099-2456-7980",
                "088-2346-9870",
                "013-3456-7890"
        };
        ArrayList list = new ArrayList();
        Scanner s = new Scanner(System.in);

        while (true) {
            System.out.print(">>");
            String input = s.nextLine().trim();

            if (input.equals("")) {
                continue;
            } else if (input.equalsIgnoreCase("Q")) {
                System.exit(0);
            }

            /*
            (1) 알맞은 코드를 넣어 완성하시오.
            */

            if (list.size() > 0) {
                System.out.println(list);
                list.clear();
            } else {
                System.out.println("일치하는 번호가 없습니다.");
            }
        }
    } // main
}

/*
<실행결과>
>>
>>
>>asdf
일치하는 번호가 없습니다.
>>
>>
>>0
[012-3456-7890, 099-2456-7980, 088-2346-9870, 013-3456-7890]
>>234
[012-3456-7890, 088-2346-9870]
>>7890
[012-3456-7890, 013-3456-7890]
>>q
 */
  • 모범 답안
import java.util.*; import java.util.regex.*;

class Exercise11_18 {
public static void main(String[] args) { String[] phoneNumArr = {
"012-3456-7890",
"099-2456-7980",
"088-2346-9870",
"013-3456-7890"
};
Vector list = new Vector(); // 검색결과를 담을 Vector Scanner s = new Scanner(System.in);

while(true) {
System.out.print(">>");
String input = s.nextLine().trim(); // trim()으로 입력내용에서 공백을 제거

if(input.equals("")) { continue;
} else if(input.equalsIgnoreCase("Q")) { System.exit(0);
}

String pattern = ".*"+input+".*"; // input을 포함하는 모든 문자열
Pattern p = Pattern.compile(pattern);

for(int i=0; i< phoneNumArr.length;i++) { String phoneNum = phoneNumArr[i];
String tmp = phoneNum.replace("-",""); // phoneNum에서 '-'를 제거

Matcher m = p.matcher(tmp);

if(m.find()) {	// 패턴과 일치하면, list에 phoneNum을 추가한다. list.add(phoneNum);
}
}

if(list.size()>0) {	// 검색결과가 있으면 System.out.println(list); // 검색결과를 출력하고 list.clear();	// 검색결과를 삭제
} else {
System.out.println("일치하는 번호가 없습니다.");
}
}
} // main
}

<실행결과>
>>
>>
>>asdf
일치하는 번호가 없습니다.
>>
>>
>>0
[012-3456-7890, 099-2456-7980, 088-2346-9870, 013-3456-7890]
>>234
[012-3456-7890, 088-2346-9870]
>>7890
[012-3456-7890, 013-3456-7890]
>>q


  • ❓💡추후 자바의 정석 3판으로 Vector와 Pattern, Matcher 클래스를 보고 다시 이해해보자.
profile
9에서 0으로, 백엔드 개발블로그

0개의 댓글