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));
	}
	
}
 

[무료] 자바스크립트로 알아보는 함수형 프로그래밍 (ES5) - 인프런 | 강의

마플(http://www.marpple.com)의 CTO 유인동님이 알려주는 함수형 프로그래밍에 대한 강좌 입니다. 함수형 프로그래밍으로 라이브러리를 직접 만들어가며 함수형 프로그래밍의 패러다임과 코딩의 즐거

www.inflearn.com

map, filter에 curryr 적용

map, filter의 평가 시점을 조절

const _ = (()=>{
    return {
        filter:curryr(filter) , //커링
        map:curryr(map) ,      //커링
        each: each,
        curry:curry,
        curryr:curryr,
        reduce: reduce,
    };
    //.... 생략

map도 동일하다.

console.log(_.filter);
console.log(_.map);

const upperAge30 = _.filter(u=>u.age>=30);
const userName = _.map(u=>u.name);
console.log(upperAge30);
console.log(userName);

디버거로 확인

console.log(upperAge30(users));
console.log(userName(users));
console.log(userName(upperAge30(users)));

 

reduce

요소를 하나로 접어가는 함수

function reduce(list, iter, memo){
    each(list, val =>{
        memo = iter(memo, val);
    });
    return memo;
}
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,0));
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,10));
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val,-10));

reduce 개선

현재 아쉬운 점은 반드시 3번째 인자 memo가 있어야 한다.

memo가 없을 시 list[0] 인자를 memo로 사용하도록 수정

/*요소를 하나로 접어가는 함수
list 요소, iter 보조함수, memo 초기값*/
function reduce(list, iter, memo){
    if(arguments.length===2){
        memo = list[0];
        list = rest(list);
    }
    each(list, val =>{
        //memo에 계속 누적하여 적용한다.
        memo = iter(memo, val);
    });
    return memo;
}
/* 배열 첫 번째 요소를 잘라 나머지 배열을 리턴*/
function rest(list, num){
    return Array.prototype.slice.call(list,num||1);
}
console.log(_.reduce([1,2,3,4,5],(sum,val)=>sum+val ));
//30세 이상 사용자 age 합
console.log(_.reduce(_.map(upperAge30(users),u=>u.age),(sum,val)=>sum+val ));

memo 인자 없이도 문제없이 동작

function.html
0.01MB

'개발 > 함수형 프로그래밍' 카테고리의 다른 글

07 - 컬렉션 중심 프로그래밍 - map (수집하기)  (0) 2023.06.17
06 - 다형성 높이기  (0) 2023.06.16
05 - 파이프라인, pipe, go  (1) 2023.06.14
03 - 커링, curry, curryr  (0) 2023.06.11
02 - map, filter, each  (0) 2023.06.09

+ Recent posts