본문 바로가기

항해 99/Java

페어 프로그래밍 - 코딩 테스트 2

개인 풀이

코드 리뷰 - ChatGPT

 

14. 서울에서 김서방 찾기

class Solution {
    public String solution(String[] seoul) {
        String answer = "";
        for (int i = 0; i < seoul.length; i++) {
            if (seoul[i].equals("Kim")) {
                answer = "김서방은 "+i+"에 있다";
                break;
            }
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 변수 이름: answer 변수는 적절하게 이름이 지어졌습니다. 그러나, seoul 배열의 이름은 이 배열이 무엇을 담고 있는지에 대한 정보를 더 명확하게 해주면 좋을 것 같습니다. 예를 들어, names나 people처럼 더 의미 있는 이름을 사용할 수 있습니다.
    • 문자열 결합: answer = "김서방은 "+i+"에 있다"; 이 부분은 가독성이 좋으며, 목적을 명확하게 전달합니다. Java 8 이상을 사용한다면, String.format을 사용하여 문자열을 조금 더 명확하게 만들 수 있습니다. 예: answer = String.format("김서방은 %d에 있다", i);
  • 성능
    • 루프와 조건문: 이 코드는 "Kim"을 찾으면 즉시 반복을 중단합니다. 이는 배열을 전체적으로 탐색할 필요가 없는 경우 성능을 최적화하는 좋은 방법입니다.
    • 배열의 크기가 매우 크지 않다면, 현재 구현 방식의 성능에 문제가 되지 않습니다. 그러나, 배열이 매우 클 경우 또는 이 코드가 자주 호출되어 성능이 중요한 상황이라면, 데이터 구조를 최적화하거나 다른 접근 방식을 고려할 수 있습니다.
  • 확장성과 유지보수
    • 현재 구현은 "Kim"이라는 이름을 찾는 데 특화되어 있습니다. 만약 다른 이름으로도 검색할 수 있게 하려면, 메서드에 name 매개변수를 추가하여 더 범용적으로 사용할 수 있게 만들 수 있습니다.
    • 예외 처리: "Kim"이 배열에 없는 경우를 처리하는 로직이 추가되면 더 견고한 코드가 될 것입니다. 예를 들어, "Kim"을 찾지 못한 경우에 대한 적절한 메시지를 반환하거나 예외를 던질 수 있습니다.

개선 코드

class Solution {
    public String findPerson(String[] names, String targetName) {
        for (int i = 0; i < names.length; i++) {
            if (names[i].equals(targetName)) {
                return String.format("%s는 %d에 있다", targetName, i);
            }
        }
        return targetName + "을(를) 찾을 수 없습니다.";
    }
}
  • 메서드 이름을 findPerson으로 변경하여, 어떤 동작을 하는지 더 명확하게 표현합니다.
  • targetName 매개변수를 추가하여 다양한 이름으로 검색할 수 있는 유연성을 제공합니다.
  • String.format을 사용하여 문자열을 구성하여 가독성을 향상시킵니다.
  • "Kim"을 찾지 못한 경우에 대한 처리를 추가하여 코드의 견고함을 높였습니다.

다른 답안 코드

import java.util.Arrays;

public String solution (String[] seoul){
    int x = Arrays.asList(seoul).indexOf("Kim");        
    return "김서방은 "+ x + "에 있다";
}

 

 

 

15. 나누어 떨어지는 숫자 배열

import java.util.Arrays;

class Solution {
    public int[] solution(int[] arr, int divisor) {
        int[] answer = Arrays.stream(arr).filter(n -> n % divisor == 0).sorted().toArray();
        if (answer.length == 0) {
            answer = new int[]{-1};
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 코드의 간결성: 스트림 API를 사용하여 구현된 로직은 가독성이 높으며, 한 눈에 이해하기 쉽습니다. filter, sorted, toArray 메소드를 체이닝하는 방식으로 작성된 이 코드는 높은 수준의 가독성을 제공합니다.
  • 성능
    • 스트림 API의 효율: 스트림 API는 내부적으로 최적화되어 있으며, 병렬 처리를 위한 parallelStream() 메소드로 쉽게 전환할 수 있는 장점이 있습니다. 그러나, 본 코드의 경우 배열 크기가 매우 크지 않은 이상, 병렬 스트림으로의 전환은 오버헤드를 증가시킬 수 있으므로, 필요에 따라 선택적으로 사용해야 합니다.
  • 확장성과 유지보수
    • 확장성과 유연성: 이 함수는 divisor에 따라 배열의 원소를 필터링하는 범용적인 기능을 제공합니다. 필요에 따라  다른 조건으로 필터링하는 로직을 추가하기도 쉽습니다.
    • 에러 처리와 예외 상황: 이 코드는 divisor가 0일 때의 상황을 명시적으로 처리하지 않습니다. divisor가 0인 경우, ArithmeticException이 발생할 수 있으므로, 이를 처리하는 로직을 추가하는 것이 좋을 수 있습니다. 그러나, 문제의 맥락에 따라 divisor가 항상 0이 아니라고 가정할 수도 있습니다.

개선 코드

import java.util.Arrays;

class Solution {
    public int[] solution(int[] arr, int divisor) {
        if (divisor == 0) {
            throw new IllegalArgumentException("Divisor cannot be 0.");
        }
        
        int[] answer = Arrays.stream(arr)
                             .filter(n -> n % divisor == 0)
                             .sorted()
                             .toArray();
                             
        if (answer.length == 0) {
            return new int[]{-1};
        }
        
        return answer;
    }
}

 

 

 

16. 음양 더하기

class Solution {
    public int solution(int[] absolutes, boolean[] signs) {
        int answer = 0;
        for (int i = 0; i < signs.length; i++) {
            if (signs[i] == false) {
                answer += (-absolutes[i]);
            } else {
                answer += absolutes[i];
            }
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 명확한 로직: 로직이 단순하며 이해하기 쉽습니다. for 루프를 사용하여 absolutes 배열을 순회하고, signs 배열에 따라 각 숫자의 부호를 결정한 후 총합에 더합니다.
    • 코드 최적화: 현재 코드는 이미 간결하며, 의도를 명확하게 전달합니다. 가독성을 더욱 향상시키기 위한 필요성은 낮아 보입니다.
  • 성능
    • 효율적인 접근: 이 문제에 대한 접근 방식은 효율적입니다. 모든 요소를 단 한 번씩만 순회하므로, 시간 복잡도는 O(n)입니다. 여기서 n은 absolutes 배열의 길이입니다.
  • 확정성과 유지보수
    • 코드 재사용성: 이 함수는 특정 문제에 맞춰져 있으며, 재사용성은 제한적입니다. 하지만, 주어진 문제를 해결하기 위한 목적으로는 충분히 적합합니다.
    • 매개변수 검증: 현재 코드에서는 absolutes와 signs 배열의 길이가 동일하다는 가정 하에 작성되었습니다. 실제 환경에서는 이 두 배열의 길이가 서로 다를 경우를 대비한 검증 로직을 추가하는 것이 좋을 수 있습니다. 예를 들어, 두 배열의 길이가 다르면 예외를 던지거나 오류 메시지를 반환할 수 있습니다.

개선 코드

class Solution {
    public int solution(int[] absolutes, boolean[] signs) {
        if (absolutes.length != signs.length) {
            throw new IllegalArgumentException("Length of absolutes and signs must be equal.");
        }
        
        int answer = 0;
        for (int i = 0; i < signs.length; i++) {
            answer += signs[i] ? absolutes[i] : -absolutes[i];
        }
        return answer;
    }
}
  • 매개변수 검증: absolutes와 signs 배열의 길이가 같은지 확인합니다. 길이가 다를 경우, IllegalArgumentException을 던집니다.
  • 조건문 단순화: 삼항 연산자를 사용하여 if-else 문을 더 간결하게 표현합니다.

다른 답안 코드

class Solution {
    public int solution(int[] absolutes, boolean[] signs) {
        int answer = 0;
        for (int i=0; i<signs.length; i++)
            answer += absolutes[i] * (signs[i]? 1: -1);
        return answer;
    }
}
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

class Solution {
    public int solution(int[] absolutes, boolean[] signs) {
            AtomicInteger index = new AtomicInteger();
            return Arrays.stream(absolutes).reduce(0, (i, i1) -> {
                index.getAndIncrement();
                return signs[index.get() - 1] ? i + i1 : i - i1;
            });
        }
}

 

 

 

17. 핸드폰 번호 가리기

class Solution {
    public String solution(String phone_number) {
        int target = phone_number.length()-4;
        String answer = phone_number.substring(0, target).
            replaceAll("[0-9]","*")+phone_number.substring(target);
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 명확한 변수 사용: target 변수는 마스킹 처리할 전화번호의 길이를 잘 나타냅니다. answer 변수는 최종 결과를 저장하는 데 사용되며, 의미가 명확합니다.
    • 메서드 체이닝: substring과 replaceAll 메서드를 체이닝하여 사용함으로써 코드의 길이를 줄이고, 읽기 쉽게 만듭니다.
  • 성능
    • 성능 고려사항: 이 방법은 간단하고 직관적이지만, replaceAll 메서드가 정규 표현식을 사용하기 때문에, 문자열의 길이가 매우 클 경우 성능 저하가 발생할 수 있습니다. 정규 표현식 처리는 상대적으로 비용이 많이 드는 연산일 수 있습니다.
  • 확장성과 유지보수
    • 재사용성 및 유지보수: 이 코드는 전화번호 마스킹에 특화되어 있지만, 비슷한 유형의 문자열 처리가 필요한 다른 상황에서도 쉽게 재사용할 수 있습니다. 예를 들어, 다른 형식의 개인 정보를 마스킹할 때도 유사한 접근 방식을 사용할 수 있습니다.

개선 코드

class Solution {
    public String solution(String phone_number) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < phone_number.length() - 4; i++) {
            sb.append("*");
        }
        sb.append(phone_number.substring(phone_number.length() - 4));
        return sb.toString();
    }
}

 

다른 답안 코드

class Solution {
  public String solution(String phone_number) {
    return phone_number.replaceAll(".(?=.{4})", "*");
  }
}
  • 성능 최적화: StringBuilder를 사용하여 문자열을 생성함으로써, 불필요한 문자열 인스턴스 생성을 줄이고 성능을 향상 시킵니다. 또한, 정규 표현식을 사용하지 않아 처리 속도가 빨라질 수 있습니다.
  • 가독성 유지: 코드는 여전히 간결하며, 의도가 명확하게 전달됩니다.

 

 

 

18. 없는 숫자 더하기

import java.util.Arrays;
import java.util.stream.IntStream;

class Solution {
    public int solution(int[] numbers) {
        Arrays.sort(numbers);
        return IntStream.rangeClosed(0, 9).
            filter(n -> Arrays.binarySearch(numbers, n) < 0).sum();
    }
}

 

코드 리뷰

  • 가독성
    • 코드의 간결성과 명확성: Arrays.sort를 사용하여 배열을 먼저 정렬하고, IntStream.rangeClosed를 사용해 0부터 9까지의 숫자 스트림을 생성한 다음, filter를 통해 numbers 배열에 없는 숫자만을 찾아내어 이들의 합을 계산합니다. 이 과정은 매우 명확하고 간결하게 구현되어 있습니다.
  • 성능
    • 정렬과 이진 검색의 효율성: 배열을 정렬하는 데 O(n log n)의 시간이 소요되며, 이진 검색은 O(log n)의 시간 복잡도를 가집니다. 여기서 n은 배열 numbers의 길이입니다. 그러나 문제의 범위가 0부터 9까지로 제한되어 있으므로, 이진 검색의 호출 횟수가 최대 10회로 제한됩니다. 따라서 전체적인 성능은 매우 우수합니다.
    • 스트림 API의 효율성: 스트림 API를 사용하는 이 방식은 선언적이며 가독성이 좋지만, 때로는 명령형 접근 방식보다 약간 느릴 수 있습니다. 그러나 이 경우에는 처리해야 할 데이터 양이 매우 작으므로 성능 차이는 무시할 수 있습니다.
  • 확장성과 유지보수
    • 재사용성: 이 코드는 0부터 9까지의 숫자에 대해서만 작동하도록 설계되었습니다. 다른 범위의 숫자를 처리해야 하는 경우, IntStream.rangeClosed의 인자를 변경함으로써 쉽게 확장할 수 있습니다.
    • 유지보수성: 코드가 간결하고, 각 단계가 명확하게 분리되어 있어 유지보수 측면에서 우수합니다. 필요에 따라 각 부분을 수정하거나 확장하기 쉬울 것입니다.

다른 답안 코드

class Solution {
    public int solution(int[] numbers) {
        int sum = 45;
        for (int i : numbers) {
            sum -= i;
        }
        return sum;
    }
}

 

 

 

19. 제일 작은 수 제거하기

class Solution {
    public int[] solution(int[] arr) {
        int[] answer = new int[arr.length - 1];
        int minNum = arr[0];
        int idx = 0;
        for (int i = 0; i < arr.length; i++) {
            if (minNum > arr[i]) {
                minNum = arr[i];
            }
        }
        if (arr.length == 1) {
            answer = new int[]{-1};
        }
        for (int i = 0; i < arr.length; i++) {
            if (minNum == arr[i]) {
                continue;
            } else {
                answer[idx] = arr[i];
                idx++;
            }
        }
        return answer;
    }
}

 

 

코드 리뷰

  • 가독성
    • 변수 명명: 변수명은 그 목적을 잘 나타내고 있습니다. minNum은 배열에서 최소값을, idx는 새 배열의 인덱스를 추적합니다.
    • 로직 분리: 최소값을 찾는 로직과 최소값을 제외하고 배열을 채우는 로직이 분리되어 있어, 각 단계의 목적이 명확합니다.
  • 성능
    • 이중 순회: 현재 코드는 두 번의 배열 순회를 필요로 합니다. 첫 번째 순회에서는 배열의 최소값을 찾고, 두 번째 순회에서는 최소값을 제외하고 새 배열을 생성합니다. 이는 필요한 작업이지만, 최적화의 여지가 있습니다.
  • 확장성과 유지보수
    • 하드코딩된 조건: 배열의 길이가 1인 경우 -1을 반환하는 로직은 이 문제의 특정 요구 사항에 따른 것입니다. 이러한 특수한 경우를 처리하는 방식은 명확하며, 문제의 요구사항을 잘 충족시킵니다.

개선 코드

import java.util.Arrays;
import java.util.stream.Stream;
import java.util.List;
import java.util.ArrayList;

class Solution {
  public int[] solution(int[] arr) {
      if (arr.length <= 1) return new int[]{ -1 };
      int min = Arrays.stream(arr).min().getAsInt();
      return Arrays.stream(arr).filter(i -> i != min).toArray();
  }
}

 

 

 

20. 가운데 글자 가져오기

class Solution {
    public String solution(String s) {
        int middle = s.length() / 2;

        if (s.length() % 2 == 0) {
            return s.substring(middle - 1, middle + 1);
        } else {
            return s.substring(middle, middle + 1);
        }
    }
}

 

코드 리뷰

  • 가독성
    • 조건문 사용: if-else 조건문을 사용하여 문자열의 길이가 홀수인지 짝수인지에 따라 다른 로직을 실행합니다. 이는 매우 직관적이며, 코드의 의도를 쉽게 파악할 수 있게 합니다.
  • 성능
    • 효율적인 접근: 이 코드는 주어진 문자열 s의 길이를 한 번만 계산하고, 필요한 부분 문자열을 한 번의 연산으로 추출합니다. 이는 문자열 처리 작업에서 중요한 성능 최적화 포인트입니다.
  • 확장성과 유지보수
    • 재사용성: 이 함수는 문자열의 중앙을 찾는 일반적인 작업을 수행하므로, 다양한 상황에서 재사용될 수 있습니다.
    • 유지보수성: 코드가 간결하고 로직이 명확하기 때문에, 유지보수 측면에서도 유리합니다. 만약 로직을 변경하거나 확장해야 할 경우, 수정하기 쉬운 구조입니다.

 

 

 

21. 수박수박수박수박수박수?

class Solution {
    public String solution(int n) {
        StringBuffer sb = new StringBuffer();
        if (n % 2 == 0) {
            for (int i = 0; i < n / 2; i++) {
                sb.append("수박");
            }
            return sb.toString();
        } else {
            for (int i = 1; i <= n; i++) {
                if (i % 2 == 0) {
                    sb.append("박");
                } else {
                    sb.append("수");
                }
            }
            return sb.toString();
        }       
    }
}

 

코드 리뷰

  • 가독성
    • 조건문 최적화: if-else 구조를 사용하여 짝수와 홀수일 때의 로직을 분리했습니다. 이는 명확하고 이해하기 쉽습니다.
    • 반복문 최적화: 홀수일 경우, 두 번째 반복문에서 모든 숫자에 대해 조건을 검사합니다. 이는 필요한 연산을 줄일 수 있는 방법이지만, 반복되는 패턴을 더 단순화할 수 있습니다.
  • 성능
    • StringBuffer의 사용: 문자열을 더하는 연산에서 StringBuffer를 사용하는 것은 좋은 선택입니다. StringBuffer는 문자열 추가나 변경 시 성능이 더 우수한 클래스입니다. Java 5부터는 StringBuilder가 추가되었는데, StringBuilder는 StringBuffer와 API가 거의 동일하지만 동기화를 지원하지 않아 단일 스레드 환경에서 더 빠릅니다.
  • 코드 개선 제안
    • 반복 로직 간소화: 홀수일 경우에 "수박"을 n/2번 반복한 후 마지막에 "수"를 추가하는 방식으로 코드를 간소화할 수 있습니다. 이렇게 하면 짝수와 홀수일 때의 로직을 더 통합하고 간결하게 만들 수 있습니다.
    • StringBuilder 사용 고려: 멀티 스레드 환경이 아닌 경우, StringBuffer 대신 StringBuilder를 사용하는 것이 성능상 더 유리할 수 있습니다.

코드 개선

class Solution {
    public String solution(int n) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n / 2; i++) {
            sb.append("수박");
        }
        if (n % 2 != 0) {
            sb.append("수");
        }
        return sb.toString();
    }
}

 

다른 답안 코드

public String watermelon(int n){

        return new String(new char [n/2+1]).replace("\0", "수박").substring(0,n);
    }

 

 

 

22. 내적

class Solution {
    public int solution(int[] a, int[] b) {
        int answer = 0;
        for (int i = 0; i < a.length; i++) {
            answer += a[i] * b[i];
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 명확한 로직: 반복문을 사용하여 배열 a와 b의 각 요소를 순차적으로 접근하고, 해당 요소들을 곱한 결과를 answer에 누적하는 방식은 매우 직관적입니다.
    • 변수명: answer라는 변수명은 최종적으로 계산되어 반환될 결과값을 잘 나타냅니다.
  • 성능
    • 효율적인 접근: 이 방법은 배열의 길이에 따라 선형적으로 시간이 증가하는 O(n)의 시간 복잡도를 가집니다. 여기서 n은 배열 a 또는 b의 길이입니다. 각 배열의 요소를 단 한 번씩만 접근하기 때문에, 이 문제를 해결할 수 있는 가장 효율적인 방법 중 하나입니다.
  • 확장성과 유지보수
    • 유연성: 이 코드는 두 배열의 내적을 계산하는 기본적인 작업을 수행합니다. 비슷한 유형의 문제나 다양한 수학적 연산을 수행하는 데 쉽게 확장하거나 재사용될 수 있습니다.
    • 입력값 검증: 코드는 두 배열의 길이가 같다는 가정 하에 작성되었습니다. 실제 사용 시나리오에서는 두 배열의 길이가 다를 수도 있으므로, 길이가 같은지 검증하는 로직을 추가하는 것이 좋습니다. 길이가 다른 경우 예외를 던지거나 적절한 오류 메시지를 반환할 수 있습니다.

코드 개선

class Solution {
    public int solution(int[] a, int[] b) {
        if (a.length != b.length) {
            throw new IllegalArgumentException("The lengths of a and b must be the same.");
        }

        int answer = 0;
        for (int i = 0; i < a.length; i++) {
            answer += a[i] * b[i];
        }
        return answer;
    }
}

 

다른 답안 코드

import java.util.stream.IntStream;

class Solution {
    public int solution(int[] a, int[] b) {
        return IntStream.range(0, a.length).map(index -> a[index] * b[index]).sum();
    }
}

 

 

 

23. 약수의 개수와 덧셈

import java.util.stream.IntStream;

class Solution {
    public int solution(int left, int right) {
        int answer = 0;
        int[] numbers = IntStream.rangeClosed(left, right).toArray();
        for (int num : numbers) {
            int cnt = 0;
            for (int i = 1; i <= num; i++) {
                if (num % i == 0) {
                    cnt++;
                }
            }
            answer += (cnt % 2 == 0) ? num : -num;
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 스트림 API 사용: IntStream.rangeClosed(left, right).toArray()를 사용하여 주어진 범위의 정수 배열을 간결하게 생성합니다. 이는 코드의 가독성을 높이는 좋은 예입니다.
    • 명확한 로직: 외부 루프에서 각 정수를 순회하고, 내부 루프에서는 해당 정수의 약수를 찾아 개수를 세는 방식은 매우 명확하고 직관적입니다.
  • 성능
    • 이중 루프의 비효율성: 각 숫자에 대해 1부터 그 숫자까지 모두 순회하며 약수를 찾는 방식은 비효율적일 수 있습니다. 특히, num이 큰 경우 성능 저하의 원인이 될 수 있습니다.
    • 성능 최적화 방안: 약수의 개수를 효율적으로 계산하는 방법 중 하나는 제곱근을 사용하는 것입니다. 어떤 정수 n의 약수는 n의 제곱근을 초과할 수 없기 때문에, 1부터 sqrt(n)까지만 검사하고, 이 과정에서 약수를 찾을 때마다 카운트를 2씩 증가시킬 수 있습니다(제곱수인 경우를 고려하여 카운트를 조정해야 합니다).
  • 확장성과 유지보수
    • 유연한 구조: 이 코드는 주어진 범위 내의 숫자에 대해 약수의 개수에 따라 수를 더하거나 빼는 일반적인 문제 해결 방식을 제공합니다. 필요에 따라 다른 유형의 문제에 적용하기 쉽습니다.
    • 유지보수 용이성: 로직이 분명하고 간결하여, 필요에 따라 내부 로직을 수정하거나 확장하기 용이합니다.

개선 코드

class Solution {
    public int solution(int left, int right) {
        int answer = 0;
        for (int num = left; num <= right; num++) {
            int cnt = 0;
            for (int i = 1; i * i <= num; i++) {
                if (num % i == 0) {
                    cnt += (i * i == num) ? 1 : 2;
                }
            }
            answer += (cnt % 2 == 0) ? num : -num;
        }
        return answer;
    }
}

 

다른 답안 코드

class Solution {
    public int solution(int left, int right) {
        int answer = 0;

        for (int i=left;i<=right;i++) {
            //제곱수인 경우 약수의 개수가 홀수
            if (i % Math.sqrt(i) == 0) {
                answer -= i;
            }
            //제곱수가 아닌 경우 약수의 개수가 짝수
            else {
                answer += i;
            }
        }

        return answer;
    }
}

 

 

 

24. 문자열 내림차순으로 배치하기

import java.util.Comparator;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class Solution {
    public String solution(String s) {
        return Stream.of(s.split("")).
            sorted(Comparator.reverseOrder()).
            collect(Collectors.joining());
    }
}

 

코드 역할

  • Stream.of(s.split("")).sorted(Comparator.reverseOrder()).collect(Collectors.joining());
    • 주어진 문자열 's'를 개별 문자로 분할한 후, 문자들을 역순 정렬하고 정렬된 문자들을 하나의 문자열로 결합
    • Stream.of(s.split("")) : 배열을 스트림으로 변환 후 s를 길이가 1인 문자열의 배열로 분할(문자열을 개별 문자로 나눈다)
    • sorted(Comparator.reverseOrder()) : 스트림의 요소들을 주어진 Comparator에 따라 정렬, reverseOrder()는 내림차순 정렬을 수행하는 Comparator 반환(알파벳 역순 정렬)
    • collect(Collectors.joining()) : 스트림의 모든 문자열 요소들을 하나의 문자열로 결합, Collectors.joining()은 스트림의 요소들을 연결하는 컬렉터를 생성하고 구분자 없이 요소들을 이어붙임

코드 리뷰

  • 가독성
    • 스트림 API의 활용: Stream.of(s.split(""))를 통해 문자열을 문자 단위로 분리하고 스트림으로 변환합니다. 이후 sorted(Comparator.reverseOrder())를 사용하여 문자들을 내림차순으로 정렬하고, collect(Collectors.joining())을 통해 스트림의 모든 문자를 하나의 문자열로 결합합니다. 이 과정은 매우 직관적이며, 스트림 API의 강력한 기능을 잘 보여줍니다.
  • 성능
    • 효율적인 문자열 처리: 스트림 API는 내부적으로 최적화되어 있으며, 이 경우에는 문자열의 각 문자를 처리하는데 있어서 빠르고 효율적인 방법을 제공합니다. 그러나, 문자열이 매우 큰 경우 스트림 연산의 오버헤드가 느껴질 수 있습니다.
  • 확장성과 유지보수
    • 코드의 재사용성과 확장성: 이 코드는 문자열을 내림차순으로 정렬하는 일반적인 작업을 수행합니다. 필요에 따라 다른 정렬 기준이나 다른 유형의 컬렉션에도 적용할 수 있을 만큼 유연합니다.
    • 유지보수의 용이성: 스트림 API를 사용한 이 방식은 코드의 수정과 유지보수를 용이하게 합니다. 정렬 기준을 변경하거나 추가적인 필터링, 변환 작업을 적용하기 쉽습니다.

 

 

 

25. 부족한 금액 계산하기

import java.util.stream.LongStream;

class Solution {
    public long solution(int price, int money, int count) {
        long answer = LongStream.rangeClosed(1,count).map(n -> n * price).sum() - money;

        if (answer <= 0) {
            answer = 0;
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성 및 성능
    • 가독성: 코드는 매우 간결하고, 스트림을 사용하여 의도를 명확하게 전달합니다. 스트림 연산을 통해 복잡한 반복문 없이도 필요한 연산을 수행할 수 있습니다.
    • 성능: 스트림을 사용하는 이 방식은 간결성을 제공하지만, 큰 count 값에 대해서는 반복문을 사용하는 것에 비해 성능상의 손해를 볼 수 있습니다. 그러나, 이 경우 성능 차이는 미미할 수 있으며, 가독성과 코드의 간결성이 더 우선시될 수 있습니다.

다른 답안 코드

class Solution {
    public long solution(long price, long money, long count) {
        return Math.max(price * (count * (count + 1) / 2) - money, 0);
    }
}

 

 

 

26. 문자열 다루기 기분

class Solution {
    public boolean solution(String s) {
        if (s.length() == 4 || s.length() == 6) {
            return s.matches("[0-9]+") ? true : false;
        }
        return false;
    }
}

 

코드 리뷰

  • 가독성
    • s.matches("[0-9]+") ? true : false;는 s.matches("[0-9]+")의 결과가 이미 boolean 타입이므로 삼항 연산자 없이 직접 반환할 수 있습니다. 이는 코드의 가독성을 향상시키며, 불필요한 연산을 줄입니다.
  • 성능
    • s.matches("[0-9]+")는 필요한 작업을 수행하는 데 있어 효율적입니다. 주어진 문제의 범위 내에서는 성능에 큰 영향을 미치지 않습니다. 문자열 길이가 상대적으로 짧기 때문에 정규 표현식 처리로 인한 성능 저하는 미미할 것입니다.

개선 코드

class Solution {
    public boolean solution(String s) {
        return (s.length() == 4 || s.length() == 6) && s.matches("[0-9]+");
    }
}
  • 간결성: 코드를 더 간결하게 만들고, 직접적으로 문제의 요구사항을 표현합니다.
  • 가독성 향상: 삼항 연산자를 제거함으로써 가독성을 높입니다. s.matches("[0-9]+")의 결과가 boolean이므로 이를 직접 반환합니다.
  • 효율성 유지: 문자열의 길이와 내용을 효과적으로 검사하여 문제의 요구사항을 충족합니다.

 

 

 

27. 행렬의 덧셈

class Solution {
    public int[][] solution(int[][] arr1, int[][] arr2) {
        int[][] answer = new int[arr1.length][];
        for (int i = 0; i < arr1.length; i++) {
            answer[i] = new int[arr1[i].length];
            for (int j = 0; j < arr1[i].length; j++) {
                answer[i][j] = arr1[i][j] + arr2[i][j];
            }
        }
        return answer;
    }
}

 

코드 리뷰

  • 가독성
    • 명확한 변수명: 변수명 answer는 결과 배열을 잘 나타냅니다. 또한, i와 j는 전통적으로 반복문에서 인덱스로 사용되므로 이해하기 쉽습니다.
    • 직관적인 로직: 이중 for 반복문을 사용하여 각 배열의 요소를 순회하고 더하는 방식은 매우 직관적이며, 2차원 배열 작업에 자주 사용되는 패턴입니다.
  • 성능
    • 효율적인 접근: 각 요소를 단 한 번씩만 접근하여 바로 계산을 수행하기 때문에, 이 코드는 주어진 작업에 대해 상당히 효율적입니다. 시간 복잡도는 O(n*m)입니다, 여기서 n은 행의 수, m은 열의 수입니다.
    • 메모리 사용: 새로운 배열 answer를 생성하여 결과를 저장하기 때문에, 입력 배열과 동일한 크기의 추가 메모리가 필요합니다. 이는 필수적인 부분이며, 주어진 문제를 해결하는 데 있어 최적의 방법입니다.
  • 확장성과 유지보수
    • 코드 재사용성과 유지보수: 코드는 간결하고, 특정한 작업(2차원 배열의 요소별 덧셈)을 수행하도록 잘 구성되어 있습니다. 비슷한 유형의 배열 연산(예: 요소별 곱셈, 뺄셈)을 수행하는 데 쉽게 확장하거나 수정할 수 있습니다.
    • 입력 배열의 크기 검증: 현재 코드는 arr1과 arr2가 동일한 차원과 크기를 가지고 있음을 가정합니다. 실제 사용 시나리오에서는 입력 배열의 크기가 다를 수 있으므로, 이에 대한 검증 로직을 추가하는 것이 좋을 수 있습니다.

개선 코드

class Solution {
    public int[][] solution(int[][] arr1, int[][] arr2) {
        if (arr1.length != arr2.length || arr1[0].length != arr2[0].length) {
            throw new IllegalArgumentException("The dimensions of arr1 and arr2 must be the same.");
        }
        
        int[][] answer = new int[arr1.length][];
        for (int i = 0; i < arr1.length; i++) {
            answer[i] = new int[arr1[i].length];
            for (int j = 0; j < arr1[i].length; j++) {
                answer[i][j] = arr1[i][j] + arr2[i][j];
            }
        }
        return answer;
    }
}

 

 

 

28. 직사각형 별찍기

import java.util.Scanner;

class Solution {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int a = sc.nextInt();
        int b = sc.nextInt();

        for (int i = 0; i < b; i++) {
            for (int j = 0; j < a; j++) {
                System.out.print("*");
            }
            System.out.println();
        }
    }
}

 

코드 리뷰

  • 가독성
    • 명확한 로직: 별을 출력하는 로직은 매우 직관적이며, 코드를 처음 보는 사람도 쉽게 이해할 수 있습니다. 변수명 a와 b는 간결하지만, 이들이 각각 너비와 높이를 의미한다는 것을 명시적으로 나타내지 않습니다.
    • 변수명 개선: a와 b보다는 width와 height와 같이 의미가 더 명확한 변수명을 사용하는 것이 좋을 수 있습니다.
  • 성능
    • 효율적인 실행: 본 코드는 주어진 문제를 해결하기 위해 필요한 최소한의 연산만 수행합니다. 별을 출력하는 데 있어서 추가적인 메모리 할당이나 불필요한 연산을 하지 않습니다.
    • 출력 성능 고려: 각 별을 출력할 때마다 System.out.print("*")를 호출하는 것은 출력 버퍼에 대한 여러 번의 접근을 유발합니다. 대량의 출력이 있을 경우, 이는 성능 저하를 일으킬 수 있습니다.
  • 확장성 및 유지보수
    • 코드 재사용성: 본 코드는 직사각형 별 패턴을 출력하는 특정 작업을 수행합니다. 다른 형태의 패턴을 출력하도록 확장하는 것은 추가적인 로직 변경을 요구할 것입니다.
    • 입력 값 검증: 사용자로부터 입력받는 값에 대한 검증 로직이 포함되어 있지 않습니다. 잘못된 입력(예: 음수)에 대한 처리를 추가하는 것이 좋을 수 있습니다.

개선 코드

import java.util.Scanner;

class Solution {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int width = sc.nextInt();
        int height = sc.nextInt();

        String line = "*".repeat(width); // Java 11 이상에서 사용 가능

        for (int i = 0; i < height; i++) {
            System.out.println(line);
        }
    }
}

 

다른 답안 코드

import java.util.Scanner;
import java.util.stream.IntStream;

public class Solution {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int a = sc.nextInt();
        int b = sc.nextInt();

        StringBuilder sb = new StringBuilder();
        IntStream.range(0, a).forEach(s -> sb.append("*"));
        IntStream.range(0, b).forEach(s -> System.out.println(sb.toString()));
    }
}

 

 

 

페어 프로그래밍 코드

8~21

 

8.

package pair2;

/* 문자열을 정수로 바꾸기
 * 문자열 s를 숫자로 변환한 결과를 반환하는 함수를 완성하세요
 */
public class StringToInt {
    private static int solution(String s) {
        return Integer.parseInt(s);
    }

    public static void main(String[] args) {
        int answer = StringToInt.solution("1234");
        System.out.println("answer =? " + answer);
    }
}

 

9.

package pair2;

/* 정수 제곱근 판별*/
public class IntSqrt {
    private static long solution(long n) {
        long answer = 0L;
        long x = (long) Math.sqrt(n);

        if (x == Math.sqrt(n)) {
//            answer = (x + 1L) * (x + 1L);
            answer = (long) Math.pow(x+1, 2);
        } else {
            answer = -1;
        }

        return answer;
    }

    public static void main(String[] args) {
        long answer = IntSqrt.solution(121);
        System.out.println(answer);
    }
}

 

10.

package pair2;

import java.util.Arrays;

/* 정수 내림차순 배치 */
public class DescendingOrder {
    private static long solution(long n) {
        char[] arr = String.valueOf(n).toCharArray();
        Arrays.sort(arr);
        String reverseN = new StringBuilder(new String(arr)).reverse().toString();

        return Long.parseLong(reverseN);
    }

    public static void main(String[] args) {
        long answer = DescendingOrder.solution(118372);
        System.out.println(answer);
    }
}

 

11.

package pair2;

import java.util.stream.Stream;

/* 하샤드 수 */
public class HarshadNum {
    private static boolean solution(int x) {
        int tmp = x;
        int sum = 0;

        // 자리수 더하기
        while (true) {
            sum += x % 10;
            x = x / 10;
            if (x == 0) break;
        }

        if (tmp % sum == 0) return true;

//        int[] arr = Stream.of(String.valueOf(x).split("")).mapToInt(Integer::parseInt).toArray();

        return false;
    }

    public static void main(String[] args) {
        System.out.println(HarshadNum.solution(10));
        System.out.println(HarshadNum.solution(12));
        System.out.println(HarshadNum.solution(11));
        System.out.println(HarshadNum.solution(13));
    }
}

 

12.

package pair2;

import java.util.stream.LongStream;

/* 두 정수 사이의 합 */
public class SumBetweenInt {
    private static long solution(int a, int b) {
        if (a==b) return (long) a;

        long answer = 0L;
        if (a < b) {
            answer = LongStream.rangeClosed(a, b).sum();
//            for (long i=a; i<=b; i++) {
//                answer += i;
//            }
        } else {
            answer = LongStream.rangeClosed(b, a).sum();
//            for (long i=b; i<=a; i++) {
//                answer += i;
//            }
        }

        return answer;
    }

    public static void main(String[] args) {
        System.out.println(SumBetweenInt.solution(3,5));
        System.out.println(SumBetweenInt.solution(3,3));
        System.out.println(SumBetweenInt.solution(5,3));
    }
}

 

13.

package pair2;

/* 콜라츠 추측 */
// 정수형 범위 주의!!
public class Collatz {
    private static int solution(int smallNum) {
        int cnt = 0;
        long num = smallNum;
        while (cnt <= 500) {
            if (num ==1) break;

            num = (num % 2 == 0) ? num / 2 : num * 3 + 1;
            cnt++;
        }

        if (num != 1) return -1;

        return cnt;
    }

    public static void main(String[] args) {
        System.out.println(Collatz.solution(6));
        System.out.println(Collatz.solution(16));
        System.out.println(Collatz.solution(626331)); // error
    }
}

 

14.

package pair2;

import java.util.Arrays;

/* 서울에서 김서방 찾기 */
public class SearchKim {
    private static String solution(String[] seoul) {
        String answer = "";
        for (int i=0; i<seoul.length; i++) {
            if (seoul[i].equals("Kim")) {
                answer = "김서방은 " + i + "에 있다";
                break;
            }
        }
        return answer;
    }

    public static void main(String[] args) {
        String[] seoul = {"Jane", "Kim"};
        System.out.println(SearchKim.solution(seoul));
    }
}

 

15.

package pair2;

import java.util.Arrays;

/* 나누어 떨어지는 수*/
public class DivisibleNum {
    private static int[] solution(int[] arr, int divisor) {
        // arr 반복문 돌면서
        // divisor로 나누어 떨어지는 수를 찾아서
        // list
        // return 배열 길이를 모르는데???
        // return 배열에 넣어야하는데

        int[] answer = Arrays.stream(arr).filter(i -> i % divisor == 0).sorted().toArray();
        if (answer.length == 0) return new int[]{-1};

        return answer;
    }

    public static void main(String[] args) {

    }
}

 

16.

package pair2;

/* 음양 더하기 */
public class TrueFalseSum {
    private static int solution(int[] absolutes, boolean[] signs) {
        int answer = 0;
        for (int i = 0; i < absolutes.length; i++) {
            answer += (signs[i]) ? absolutes[i] : absolutes[i] * -1;
        }
        return answer;
    }

    public static void main(String[] args) {
        System.out.println(TrueFalseSum.solution(new int[]{4,7,12}, new boolean[]{true, false, true}));
        System.out.println(TrueFalseSum.solution(new int[]{1,2,3}, new boolean[]{false, false, true}));
    }
}

 

17.

package pair2;

/* 휴대폰 번호 가리기 */
public class HidePhoneNum {
    private static String solution(String phone_number) {
        return phone_number.substring(0, phone_number.length() - 4).replaceAll("[0-9]", "*")
                + phone_number.substring(phone_number.length() - 4);

        //phone_number.replaceAll(".(?=.{4})", "*");
        //. -> 임의의 문자 한 개 / (?=.) -> 뒷쪽에 임의의 문자 한 개를 제외하고 선택/ {숫자} -> 숫자 만큼의 자릿수 / .(?=.{4}) ==> 뒤쪽에서 임의의 문자 4개를 제외한 임의의 문자 한 개 선택
        //정규식 전방탐색
    }

    public static void main(String[] args) {
        System.out.println(HidePhoneNum.solution("01033334444"));
    }
}

 

18.

package pair2;

import java.util.Arrays;
import java.util.stream.IntStream;

/* 없는 숫자 더하기 */
public class AddMissingNum {
    private static int solution(int[] numbers) {
//        int answer = 45;
//        for (int i : numbers) {
//            answer -= i;
//        }
//        return answer;
        Arrays.sort(numbers);
        return IntStream.rangeClosed(0, 9).filter(n -> Arrays.binarySearch(numbers, n) < 0).sum();
    }

    public static void main(String[] args) {
        System.out.println(AddMissingNum.solution(new int[]{1,2,3,4,6,7,8,0}));
    }
}

 

19.

package pair2;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/* 제일 작은 수 제거하기 */
public class RemoveSmallNum {
    private static int[] solution(int[] arr) {
//        if (arr.length == 1) return new int[]{-1};
//
//        int[] tmp = Arrays.copyOf(arr, arr.length);
//        Arrays.sort(arr);
//        int min = arr[0];
//
//        List<Integer> list = new ArrayList<>();
//        for (int i : tmp) {
//            if (i != min) list.add(i);
//        }
//
//        return list.stream().mapToInt(i -> i).toArray();

        if (arr.length <= 1) return new int[]{ -1 };

        int min = Arrays.stream(arr).min().getAsInt();
        return Arrays.stream(arr).filter(i -> i != min).toArray();
    }

    public static void main(String[] args) {
        System.out.println(Arrays.toString(RemoveSmallNum.solution(new int[]{4,3,2,1})));
    }
}

 

20.

package pair2;

/* 가운데 글자 가져오기 */
public class GetMiddleLetter {
    public static String solution(String s) {
        String answer = "";
        int len = s.length();
        if (len % 2 == 0) {
            // answer += s.substring(len / 2 - 1, len / 2 + 1);
            answer += s.charAt(len / 2 - 1) + "" + s.charAt(len / 2);
        } else {
            answer += s.charAt(len / 2);
        }

        return answer;
    }

    public static void main(String[] args) {
        System.out.println(GetMiddleLetter.solution("abcde"));
        System.out.println(GetMiddleLetter.solution("qwer"));
    }
}

 

21.

package pair2;

/* 박수박수박수... */
public class Clap {
    private static String solution(int n) {
        StringBuilder sb = new StringBuilder();

        for (int i=0; i<n; i++) {
            sb.append((i % 2 == 0) ? "수" : "박");
        }

        return sb.toString();
        //new String(new char [n/2+1]).replace("\0", "수박").substring(0,n);
    }

    public static void main(String[] args) {
        System.out.println(Clap.solution(3));
        System.out.println(Clap.solution(4));
    }
}

 

 

추가 학습 내용

Effectively final

  • Java 8에 추가된 syntactic sugar의 일종, 초기화 된 이후 값이 한 번도 변경되지 않았다면 effectively final 이라고 할 수 있음(final 키워드가 붙어 있지 않지만 final 키워드를 붙힌 것과 동일하게 컴파일러에서 처리)

lambda에서 사용되는 Local variable이 final or effectively final 이어야 하는 이유

  • 람다식에서 참조하는 외부 지역 변수는 final 혹은 effectively final이어야 한다.
    • 인스턴스 변수나 클래스 변수는 final 혹은 effective final 하지 않아도 람다식에서 사용할 수 있음
private int instanceNumber = 1;
private static int staticNumber = 1;    
    
// Error, 외부 지역변수는 final 혹은 effectively final 이어야 람다식에서 사용할 수 있다.
public void testPlusByLocalVariable() {
    int localNumber = 1;

    localNumber = 2;
    Addable addableImple = () -> localNumber + 1; 
}
    
// OK, 값을 변경하더라도 문제 없다.
public void testPlusByInstanceVariable() {
    instanceNumber = 2;
    Addable addableImple = () -> instanceNumber + 1;
}

// OK, 값을 변경하더라도 문제 없다.
public void testPlusByStaticVariable() {
    staticNumber = 2;
    Addable addableImple = () -> staticNumber + 1;
}

 

Capturing lambda 와 Non-Capturing lambda

  • Capturing lambda
    • 외부 변수를 이용하는 람다식(외부 변수는 지역변수, 인스턴스 변수, 클래스 변수를 모두 포함)
  • Non-Capturing lambda
    • 외부 변수를 이용하지 않는 람다식
// Capturing lambda
String message = "CapturingLambda";
Runnable runnable = () -> System.out.println(message);

// Non-Capturing lambda
Runnable runnable = () -> System.out.println("NonCapturingLambda");

Runnable runnable = () -> {
    String message = "NonCapturingLambda";
    System.out.println(message);
}

 

Capturing lambda 하위 분류

  • local capturing lambda
    • 외부 변수로 지역 변수를 이용하는 람다식
      1. 람다식에서 사용되는 외부 지역 변수는 복사본
      2. final 혹은 effectively final인 지역 변수만 람다식에서 사용할 수 있다.
      3. 복사된 지역 변수 값은 람다식 내부에서도 변경할 수 없다(final 변수로 다뤄야 한다).
  • Non - local capturing lambda
    • 외부 변수로 인스턴스 변수 혹은 클래스 변수를 이용하는 람다식
      • final 제약 조건이 없고, 외부 변수 값도 복사하지 않는다.
      • 메모리 영역에서 변수를 저장하기 때문에 람다식에서 바로 참조 가능
      • 멀티 쓰레드 환경에서는 volatile, synchronized 등을 이용하여 sync를 맟춰야 함

1. 람다식에서 사용되는 외부 지역변수는 복사본

  • 지역 변수는 stack 영역에 생성되고, 지역 변수가 선언된 block 종료 시 제거 됨
    • 메서드 내 지역 변수를 참조하는 람다식을 리턴하는 메서드가 있을 때, 메서드 block이 끝나면 지역 변수가 제거 되어 추후 람다식이 수행될 때 참조 불가
  • 지역 변수를 관리하는 쓰레드와 람다식이 실행되는 쓰레드가 다를 수 있다.
    • 스택은 각 쓰레드의 고유 공간이고, 쓰레드끼리 공유되지 않음, 람다식이 수행될 때 값을 참조할 수 없음

2. final 혹은 effectively final 인 지역 변수만 람다식에서 사용할 수 있다.

  • 람다식에서 참조하려 하는 지역 변수가 변경이 가능할 경우 발생할 수 있는 문제
    • 값이 보장되지 않는 다면 매번 다른 결과가 도출 될 수 있다.
  • 외부 지역 변수는 전달되는 복사본이 변경되지 않은 최신 값임을 보장하기 위해 final 혹은 effectively final 이어야 함

3. 복사된 지역 변수 값은 람다식 내부에서도 변경할 수 없다. 즉 final 변수로 다뤄야 한다.

  • 복사될 값의 변조를 막아 최신 값임을 보장하기 위해 final 제약을 걸었는데 람다식 내부에서 변경이 가능할 경우 다시 제자리로 돌아오게 된 격.
  • 컴파일된 람다식은 static 메서드 형태로 변경이 됨(복사된 값이 파라미터로 전달되므로 스택 영역에 존재하게 되서 sync를 해줄 수 없음)
  • 컴파일러 레벨에서 앞, 뒤로 final 제약을 걸어줌으로써 멀티 쓰레드 환경에서 대응하기 어려운 이슈를 미연에 방지

결론

  • 람다식에서 외부 지역 변수를 이용할 때 final 혹은 effectively final 이어야 하는 이유는 지역 변수가 스택에 저장되기 때문에 람다식에서 값을 바로 참조하는 것에 제약이 있어 복사된 값을 이용하게 되는데, 이때 멀티 쓰레드 환경에서 복사될/ 복사된 값이 변경 가능할 경우 이로 인한 동시성 이슈에 대응할 수 없어서.

 

 

멘토링 시 질문 사항

  • 정규 표현식 정리 사이트
  • Stream API 사용

 

학습 필요

  • Stream API 기능
  • String, StringBuffer, StringBuilder 각각의 기능
  • 정규 표현식

'항해 99 > Java' 카테고리의 다른 글

페어 프로그래밍 - 코딩 테스트 4  (1) 2024.02.16
페어 프로그래밍 - 코딩 테스트 3  (0) 2024.02.15
페어 프로그래밍 - 코딩 테스트  (1) 2024.02.13
WIL - 1  (1) 2024.02.12
역할과 구현, OCP  (0) 2024.02.08