reduce
@SuppressWarnings("unchecked")
/*
* 자바는 스트림 안에서 외부 변수를 참조하면 그 변수는 final 속성이 된다.
* 따라서 갱신하면서 누산을 하지 못해, 간접적으로 값을 수정해야 한다.
*/
static <T> T reduce(List<T> list,BiFunction<T, T, T> reducer ,T memo) {
//방법1
T[] result = (T[])Array.newInstance(memo.getClass(), 1);
result[0] = memo;
//방법2
HashMap<Class<T>, T> map = new HashMap<>();
Class<T> typeToken = (Class<T>) memo.getClass();
map.put(typeToken, memo);
each(list, data->{
result[0] = reducer.apply(result[0], data);
map.compute(typeToken, (k,v)->reducer.apply(v, data));
});
// return result[0];
return map.get(typeToken);
}
자바에선 람다에서 참조하는 변수는 사실상 final이 된다. 그래서 일반적인 변수 하나에 값을 누산하면서 갱신하는 것이 불가능하다.
따라서 일종의 편법으로 길이 1 인 배열을 생성해 간접적으로 값을 갱신하는 방법을 사용한다.
다른 방법으로 Map을 이용해 간접적으로 값을 갱신하는 방법도 있다.
reduce 리팩터링 1
/*
* 자바는 스트림 안에서 외부 변수를 참조하면 그 변수는 final 속성이 된다.
* 따라서 갱신하면서 누산을 하지 못해, 간접적으로 값을 수정해야 한다.
*/
static <T> T reduce(List<T> list,BiFunction<T, T, T> reducer ,T memo) {
//방법1 Type safety: Unchecked cast from Object
// T[] result = (T[])Array.newInstance(memo.getClass(), 1);
//방법2
HashMap<Class<?>, T> map = new HashMap<>();
map.put(memo.getClass(), memo);
each(list, data->{
map.compute(memo.getClass(), (k,v)->reducer.apply(v, data));
});
return map.get(memo.getClass());
}
방법 1의 경우 지네릭 메서드로 타입이 런타임 호출 시에 결정된다. 따라서 명시적으로 고정된 배열을 생성하지 못한다. 리플렉션 API를 사용해서 배열을 생성한다. 이때 반환 타입은 Objecr로 지네릭 배열로 형변환이 필요하다. 따라서 방법 1을 배제했다.
reduce 오버로딩
/*
* 자바는 스트림 안에서 외부 변수를 참조하면 그 변수는 final 속성이 된다.
* 따라서 갱신하면서 누산을 하지 못해, 간접적으로 값을 수정해야 한다.
*/
static <T> T reduce(List<T> list, BinaryOperator<T> reducer ,T memo) {
//간소화된 유효성 검사, 본질을 흐리지 않는 선에서 간략화
if(list.size() < 2) return list.get(0);
HashMap<Class<?>, T> map = new HashMap<>();
map.put(memo.getClass(), memo);
each(list, data->map.compute(memo.getClass(), (k,v)->reducer.apply(v, data)));
return map.get(memo.getClass());
}
/*
* 자바는 람다에서 사용하는 변수는 final 속성을 띄기 때문에 memo 값 처리가 애매해진다.
* 삼항연산자 등 별도 처리하기보다 오버로딩으로 처리했다.
*/
static <T> T reduce(List<T> list, BinaryOperator<T> reducer) {
if(list.size() < 2) return list.get(0);
return reduce(list.subList(1, list.size()), reducer, list.get(0));
}
memo가 없는 경우를 상정해 reduce메서드를 오버로딩 했다.
System.out.println("reduce = "+
reduce(Arrays.asList(1,2,3,4,5), (a,b)->a+b, Integer.valueOf(0)));
System.out.println("reduce = "+
reduce(Arrays.asList(1,2,3,4,5), (a,b)->a+b));
System.out.println("reduce = "+
reduce(Arrays.asList(1), (a,b)->a+b, Integer.valueOf(0)));
System.out.println("reduce = "+
reduce(Arrays.asList(1), (a,b)->a+b));
reduce = 15
reduce = 15
reduce = 1
reduce = 1
전체 코드
package javabasic.stream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class FuncEx01 {
static class User{
final Long id;
final String name;
final Integer age;
public User(Long id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "[id=" + id + ", name=" + name + ", age=" + age + "]\n";
}
}
static List<User> getUsers(){
return Arrays.asList(
new User(10L, "ID", 36),
new User(20L, "BJ", 32),
new User(30L, "JM", 32),
new User(40L, "PJ", 27),
new User(50L, "HA", 25),
new User(60L, "JE", 26),
new User(70L, "JI", 31),
new User(80L, "MP", 23)
);
}
public static void main(String[] args) {
List<User> users = getUsers();
System.out.println("filter = "+
filter(users,user->user.age>30)+"\n"
+users);
System.out.println("map = "+
map(users, user->user.id));
each(Arrays.asList(1,2,3,4,5),System.out::print);
System.out.println();
System.out.println("reduce = "+
reduce(Arrays.asList(1,2,3,4,5), (a,b)->a+b, Integer.valueOf(0)));
System.out.println("reduce = "+
reduce(Arrays.asList(1,2,3,4,5), (a,b)->a+b));
System.out.println("reduce = "+
reduce(Arrays.asList(1), (a,b)->a+b, Integer.valueOf(0)));
System.out.println("reduce = "+
reduce(Arrays.asList(1), (a,b)->a+b));
}
/*
* filter는 처리 결과가 입력 결과와 타입은 같다.
* 길이는 같거나, 작을 수 밖에 없다.
*/
static <T> List<T> filter(List<T> list, Predicate<T> predi) {
//함수형 프로그래밍은 원본 데이터를 수정하지 않는다. 새로운 데이터를 리턴하여
//부수효과를 극단적으로 배제한다.
ArrayList<T> newList = new ArrayList<>();
each(list,data->{
if(predi.test(data))
newList.add(data);
});
return newList;
}
/*
* map은 처리 결과가 입력 결과와 타입은 같거나 다르다.
* 길이는 항상 같다.
*/
static <T,R> List<R> map(List<T> list, Function<T, R> mapper){
ArrayList<R> newList = new ArrayList<>();
each(list, data->newList.add(mapper.apply(data)));
return newList;
}
/*
* 반복문을 중복을 대체할 함수
*/
static <T> List<T> each(List<T> list, Consumer<T> iter){
for(T data : list)
iter.accept(data);
return list;
}
/*
* 자바는 스트림 안에서 외부 변수를 참조하면 그 변수는 final 속성이 된다.
* 따라서 갱신하면서 누산을 하지 못해, 간접적으로 값을 수정해야 한다.
*/
static <T> T reduce(List<T> list, BinaryOperator<T> reducer ,T memo) {
//간소화된 유효성 검사, 본질을 흐리지 않는 선에서 간략화
if(list.size() < 2) return list.get(0);
HashMap<Class<?>, T> map = new HashMap<>();
map.put(memo.getClass(), memo);
each(list, data->map.compute(memo.getClass(), (k,v)->reducer.apply(v, data)));
return map.get(memo.getClass());
}
/*
* 자바는 람다에서 사용하는 변수는 final 속성을 띄기 때문에 memo 값 처리가 애매해진다.
* 삼항연산자 등 별도 처리하기보다 오버로딩으로 처리했다.
*/
static <T> T reduce(List<T> list, BinaryOperator<T> reducer) {
if(list.size() < 2) return list.get(0);
return reduce(list.subList(1, list.size()), reducer, list.get(0));
}
}
'개발 > 함수형 프로그래밍' 카테고리의 다른 글
자바스크립트 함수형 코드 자바화 해보기 - find, findIndex, some, every (0) | 2023.06.24 |
---|---|
자바스크립트 함수형 코드 자바화 해보기 - pipe, go 를 대체할 Stream 클래스 정의 (0) | 2023.06.23 |
자바스크립트 함수형 코드 자바화 해보기 - filter, map, each (0) | 2023.06.21 |
11 - each, map 개선, 마무리 (0) | 2023.06.20 |
10 - 컬렉션 중심 프로그래밍 - reduce(접기) (0) | 2023.06.19 |