주간 일과 시간 중에 코딩도장 이라고, 알고리즘 문제를 하루에 한 개씩 푸는 시간이 있다. 매번 할 때마다 해결 방법을 고민하기에도 시간이 모자라서 다소 뻔한 풀이 방법으로 문제를 풀어내고는 했었는데, 이렇게만 해서는 시야가 넓어질 것 같지가 않았다. 그래서 이제는 답 제출 후 시간이 많이 남으면 답을 제출 한 뒤에 다른 사람들이 한 코드를 보면서 이렇게도 풀 수 있구나~ 하고 혼자 분석해보는 시간을 가져보려고 한다.
오늘 푼 것은 프로그래머스 문제 중에 '제일 작은 수 제거하기' 라는 문제였고, 정수를 저장한 배열에서 가장 작은 수를 제거하는 배열을 리턴하는 함수를 완성하는 미션이였다. 제일 많은 사람들이 푼 방법은 아래와 같은데 나도 이런 방식으로 풀어냈다. (힌트 살짝 보긴 함 ㅎㅎ)
class Solution {
public int[] solution(int[] arr) {
if (arr.length == 1) {
return new int[] {-1};
}
//배열의 길이가 1이 아닐 때, 가장 작은 수 하나 뺀 크기로 배열 초기화
int[] answer = new int[arr.length - 1];
//임의로 하나의 값을 제일 작은 수로 가정함
int min = arr[0];
for (int i = 0; i < arr.length; i += 1) {
//두 인자값 중 더 작은 값을 min 에 저장
min = Math.min(min, arr[i]);
}
//반복문 증감 인덱스용 변수 초기화
int x = 0;
//가장 작은 수 제외한 배열 만들기
for (int i = 0; i < arr.length; i += 1) {
//arr 배열의 값이 가장 작은 수인 min 값과 같다면 continue
if (arr[i] == min) {
continue;
}
//arr 배열의 값이 가장 작은 수인 min 과 같지 않다면 arr 배열에 순서대로 넣어준다.
answer[x++] = arr[i];
}
return answer;
}
}
그런데 다른 사람의 풀이를 보던 중 스트림과 람다식을 이용한 방법이 단 몇 줄 만에 끝나는 것을 보고 한 번 시도해보고 싶어서 따라해봤다. 코드는 아래와 같다.
import java.util.Arrays;
import java.util.List;
class Solution {
public int[] solution(int[] arr) {
if (arr.length == 1) {
return new int[] {-1};
}
// arr 배열의 최소값을 구해서 int 형으로 반환함
int min = Arrays.stream(arr).min().getAsInt();
// arr 배열에서 최소값 min과 같지 않은 것만 배열로 생성함
return Arrays.stream(arr).filter(i -> i != min).toArray();
}
}
사실 스트림과 람다식이라고 하면 나한테는 너무 어렵고 난해한 기술로 다가왔기 때문에 기존에는 아예 시도해 보려는 생각 조차 안해봤었지만 이제는 피할 수 없는 시기가 온 것 같다!
먼저 스트림은 java 8 부터 추가된 것으로, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 도와주는 반복자이다. 쉽게 말해 배열이나 컬렉션으로 원하는 값을 얻을 때 for 문의 도배를 방지하기 위해 나온 개념인데 람다식을 사용하면 코드가 훨씬 간결해진다. 하지만 스트림은 한 번에 모든 것이 수행되기 때문에 디버깅이 힘들고, 한 번 쓰면 close 되기 때문에 재활용이 힘든 것과, 속도가 느리다는 단점이 있다. 실제로 위의 두 코드 풀이의 속도를 비교해보면 stream 이 훨씬 느렸다.
![](https://blog.kakaocdn.net/dn/AjEyK/btrLTUUTjto/xt5g6vDcjEXuLaYfPRge6k/img.png)
스트림은 선언, 가공, 반환 세 부분으로 이루어진다.
Arrays.stream(arr).boxed().sorted().collect(Collectors.toList())
Arrays.stream(arr) // 선언
.boxed().sorted() // 가공
.collect(Collectors.toList()) // 반환
=> arr 배열을 정렬 후 List 타입으로 반환
Arrays.stream(arr).findFirst().getAsInt()
Arrays.stream(arr) // 선언
.findFirst() // 가공
.getAsInt() // 반환
=> arr 배열의 첫번째 값을 int 타입으로 반환
list.stream().skip(1).collect(Collectors.toList())
list.stream() // 선언
.skip(1) // 가공
.collect(Collectors.toList()) // 반환
list명의 첫번째 값은 생략한 뒤 list 타입으로 반환
1) 선언 : 배열, 컬렉션 등을 스트림 형태로 만들기
- 각각 배열과 컬렉션을 사용하는 경우나 직접 값을 넣어 사용하는 경우인데, 꼭 stream 을 선언한 후 값을 넣고 사용할 필요는 없고 바로 가공 형태로 사용해도 된다.
// 1) 선언 : 배열, 컬렉션 등을 스트림 형태로 만들기
Stream<데이터타입> stream명 = Arrays.stream(배열명);
Stream<데이터타입> stream명 = 리스트명.stream();
Stream<데이터타입> stream명 = Stream.of('값', '값'....);
2) 가공 : 스트림을 필요한 형태로 가공하기 (가공 메서드 사용)
// 2) 가공 : 스트림을 필요한 형태로 가공하기 (가공 메서드 사용)
Arrays.stream(배열명).가공메소드...
리스트명.stream.가공메소드...
// count() 배열, 컬렉션 크기 확인할 때 사용
Arrays.stream(arr).count();
list.stream().count();
// sorted() 오름차순으로 정렬할 때 사용
Arrays.stream(arr).sorted();
// sorted(Comparator.reverseOrder()) 역정렬
Arrays.stream(arr).sorted(Comparator.reverseOrder());
// boxed() Int, Long, Double 배열로 stream 을 만들었을 경우 각종 메서드를 사용하기 위해 사용
// 컬렉션 스트림에서는 사용하지 않는 메서드
Arrays.stream(arr).boxed().sorted();
// 그 외에 다른 메서드들
findFirst() //스트림의 처음 값 가져오기
skip(배열크기 - 1).findFirst() //스트림의 마지막 값 가져오기
skip(값) //값의 인덱스는 생략하고 나머지를 가져옴
limit(값) //값의 인덱스까지 가져옴
distinct() //중복값은 생략
max(데이터타입::compare) //최대값
min(데이터타입::compare) //최소값
average() //평균값. 배열일 경우에는 바로 사용되지만, 컬렉션의 경우에는 mapToDouble() 을 이용해 한 번 바꿔준 후 사용함
sum() //합계. average() 와 설명 동일함
3) 반환 : 가공한 값을 원하는 형태로 가져오기
// 3) 반환 : 가공한 값을 원하는 형태로 가져오기
// 값이 하나만 있는 경우
get()
getAsInt()
// 배열, 컬렉션 형태로 가져오는 경우
toArray()
// 해당하는 개수 반환
collect(Collectors.counting())
// 모든 값을 합치면서 "|" 를 붙여줌.
//"" 빈 문자열을 쓰면 값만 붙어서 나옴.
collect(Collectors.joining("|"))
// 값을 int, double, long 형태로 변환한 뒤 해당 형태의 평균을 구해 반환
collect(Collectors.averagingInt(val -> Integer.parseInt(val))
collect(Collectors.averagingDouble(val -> Double.parseDouble(val))
collect(Collectors.averagingLong(val -> Long.parseLong(val))
스트림은 람다식과 자주 쓰인다. 자바에서 람다는 기존의 클래스에 메서드를 만들고 객체화 한 뒤에 끌어쓰는 방식이 아니라 그 때 바로 만들어서 사용하는 방식이다. 람다의 핵심은 (파라미터) -> { 수행할 코드 } 를 통해 메서드를 정의하지 않고도 메서드처럼 사용할 수 있다는 것이다.
파라미터와 코드가 각 하나씩일 경우에는 파라미터 -> 코드 이렇게 사용해도 되지만, 파라미터나 코드가 늘어날수록 (파라미터1, 파라미터2) -> { 코드 } 이런 식으로도 사용한다.
// 각 인덱스의 값을 파라미터로 넘기고 코드 수행
forEach((파라미터) -> {코드})
// 스트림 중 하나의 값이라도 조건에 맞으면 true
anyMatch((파라미터) -> {코드})
// 스트림 중 하나의 값이라도 조건에 맞지 않으면 true
noneMatch((파라미터) -> {코드})
// 스트림의 값이 모두 조건에 맞아야 true
allMatch((파라미터) -> {코드})
// 요소들을 조건에 따라 걸러내는 작업. 코드에 맞는 값만 가져옴
filter(파라미터) -> {코드}
// 스트림의 값을 모두 하나로 합칠 때 사용.
//데이터 타입과 sum 으로 하나로 합친 뒤, 마지막에 값을 더해서 가져옴
reduce(값, 데이터타입::sum)
'TIL' 카테고리의 다른 글
TDD 한테 피드백 받기 (22.09.15 TIL) (0) | 2022.09.15 |
---|---|
문제-해결-반성의 성장 싸이클 (22.09.14 TIL) (0) | 2022.09.14 |
작은 성공 만들기 (22.09.12 TIL) (0) | 2022.09.12 |
ArraysList.asList() vs List.of() (22.09.11 TIL) (0) | 2022.09.11 |
약간의 뽀모도로를 곁들인 (22.09.10 TIL) (0) | 2022.09.10 |
댓글