find, findIndex

find() 주어진 조건에 일치하는 요소를 찾으면 그 요소를 반환한다.

findIndex() 주어진 조건에 일치하는 요소 인덱스를 리턴한다.

 

반복문을 끝까지 순회하지 않고 순환을 제어해야 한다. 그래서 each를 사용 못한다.

findIndex가 추상화 수준이 더 높아 다른 곳에 활용도가 많다.

예를 들어, 지네릭 메서드, 지네릭 클래스로 코드를 정의하면, find는 타입변수에 따라 리턴타입이 가변적이라 동일한 수준으로 제어하기가 힘들다. 반면에 findIndex는 인덱스를 리턴하므로 모든 클래스를 같은 수준에서 다룰 수 있다. 

 

/*유틸클래스 스태틱 메서드*/
static <T> Optional<T> find(List<T> list, Predicate<T> predi) {
    for(int i=0;i<list.size();i++) {
        T value = list.get(i);
        if(predi.test(value)) return Optional.of(value);
    }
    return Optional.empty();//편의상 null리턴
}
static <T> Integer findIndex(List<T> list, Predicate<T> predi) {
    for(int i=0;i<list.size();i++) {
        if(predi.test(list.get(i))) return i;
    }
    return -1;
}


/* String 클래스 메서드*/
Optional<T> find(Predicate<T> predi) {
    for(int i=0;i<this.list.size();i++) {
        T value = this.list.get(i);
        if(predi.test(value)) return Optional.of(value);
    }
    return Optional.empty();//편의상 null리턴
}
Integer findIndex(Predicate<T> predi) {
    for(int i=0;i<this.list.size();i++) {
        if(predi.test(this.list.get(i))) return i;
    }
    return -1;
}
		//find, findIndex 실습
		System.out.println(find(users, user->user.age<32));
		System.out.println(find(users, user->user.age>55));
		System.out.println(findIndex(users, user->user.age<32));
		System.out.println(findIndex(users, user->user.age>55));
		System.out.println(Stream.stream(users).find(user->user.age<32));
		System.out.println(Stream.stream(users).find(user->user.age>55));
		System.out.println(Stream.stream(users).findIndex( user->user.age<32));
		System.out.println(Stream.stream(users).findIndex( user->user.age>55));
Optional[[id=40, name=PJ, age=27]
]
Optional.empty
3
-1
Optional[[id=40, name=PJ, age=27]
]
Optional.empty
3
-1

some, every

findIndex()메서드를 활용해서 만든다.

some()메서드는 주어진 조건에 하나라도 일치하는 값이 있으면 참을 반환한다.

every()메서드는 주어진 조건에 모두 일치하면 참을 반환한다.

 

some() 메서드의 경우, findIndex() 호출 시 못찾는 경우인 -1만 아니면 찾은 것

every() 메서드의 경우, 반드시 모든 리스트 요소를 순회해서 검색해야 한다. 

findIndex() 는 모든 요소를 탐색해 못찾으면 -1을 반환한다. 즉, 끝까지 탐색했음을 보장하는 값이 -1 반환이다.

이점을 이용해 주어진 조건에 반대 조건으로 검사해 -1을 반환한다면, 이는 주어진 조건에 모두 해당하는 것과 같다.

static <T> Boolean some(List<T> list, Predicate<T> predi) {
    return findIndex(list, predi) != -1;
}
static <T> Boolean every(List<T> list, Predicate<T> predi) {
    return findIndex(list, predi.negate()) == -1;
}
    
    
    
/*Stream 클래스 메서드*/
Boolean some(Predicate<T> predi) {
    return findIndex(predi) != -1;
}
Boolean every(Predicate<T> predi) {
    return findIndex(predi.negate()) == -1;
}
		System.out.println("every , find 테스트");
		System.out.println(some(users, u->u.age>30));
		System.out.println(some(users, u->u.age>50));
		System.out.println(every(users, u->u.age>30));
		System.out.println(every(users, u->u.age>10));
		System.out.println(Stream.stream(users).some(u->u.age>30));
		System.out.println(Stream.stream(users).some(u->u.age>50));
		System.out.println(Stream.stream(users).every(u->u.age>30));
		System.out.println(Stream.stream(users).every(u->u.age>10));
every , find 테스트
true
false
false
true
true
false
false
true

전체 코드

package javabasic.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
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));
		
		List<String> result = Stream.stream(users)
			.filter(user->user.age > 30)
			.map(user->user.name)
			.toList();
		System.out.println(result);
		//메서드 체이닝 방식과 차이 비교
		System.out.println("default = "+
					reduce(
							map(
								filter(users,user->user.age>30) 
								, user->user.age)
							, (a,b)->a+b));
		
		//기존은 안쪽에서부터 밖으로 나온다. 중첩구조가 심해질 수록 가독성이 안좋아진다.
		//스트림을 통한 메서드 체이닝 방식은 순차적으로 적용된다.
		
		System.out.println("stream = " +
				Stream.stream(users)
					.filter(user->user.age > 30)
					.map(user->user.age)
					.reduce(Integer::sum)
//					.reduce((a,b)->a+b)
				);
		*/
		//find, findIndex 실습
		System.out.println(find(users, user->user.age<32));
		System.out.println(find(users, user->user.age>55));
		System.out.println(findIndex(users, user->user.age<32));
		System.out.println(findIndex(users, user->user.age>55));
		System.out.println(Stream.stream(users).find(user->user.age<32));
		System.out.println(Stream.stream(users).find(user->user.age>55));
		System.out.println(Stream.stream(users).findIndex( user->user.age<32));
		System.out.println(Stream.stream(users).findIndex( user->user.age>55));
		
		System.out.println("every , find 테스트");
		System.out.println(some(users, u->u.age>30));
		System.out.println(some(users, u->u.age>50));
		System.out.println(every(users, u->u.age>30));
		System.out.println(every(users, u->u.age>10));
		System.out.println(Stream.stream(users).some(u->u.age>30));
		System.out.println(Stream.stream(users).some(u->u.age>50));
		System.out.println(Stream.stream(users).every(u->u.age>30));
		System.out.println(Stream.stream(users).every(u->u.age>10));
		
	}
	
	/*
	 * 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(memo == null ) return reduce(list.subList(1, list.size()), reducer, list.get(0));
		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));
	}
	/*
	 * null 값을 간접적으로 다루기 위한 래퍼클래스 사용
	 */
	static <T> Optional<T> find(List<T> list, Predicate<T> predi) {
		for(int i=0;i<list.size();i++) {
			T value = list.get(i);
			if(predi.test(value)) return Optional.of(value);
		}
		return Optional.empty();//편의상 null리턴
	}
	static <T> Integer findIndex(List<T> list, Predicate<T> predi) {
		for(int i=0;i<list.size();i++) {
			if(predi.test(list.get(i))) return i;
		}
		return -1;
	}
	
	static <T> Boolean some(List<T> list, Predicate<T> predi) {
		return findIndex(list, predi) != -1;
	}
	static <T> Boolean every(List<T> list, Predicate<T> predi) {
		return findIndex(list, predi.negate()) == -1;
	}
	
	
	/*
	 * pipe, go 구현을 시도하려 했지만, 근본적으로 자바는 함수가 개념이 없어
	 * 호출부를 단일로 추상화할 수 없다. apply, test ... 
	 * 따라서 하나의 클래스로 묶었다.
	 */
	static class Stream<T> {
		List<T> list;
		
		static <T> Stream<T> stream(List<T> list){
			Stream<T> stream = new Stream<>();
			stream.list = list;
			return stream;
		}
		Stream<T> filter(Predicate<T> predi) {
			//함수형 프로그래밍은 원본 데이터를 수정하지 않는다. 새로운 데이터를 리턴하여
			//부수효과를 극단적으로 배제한다.
			ArrayList<T> newList = new ArrayList<>();
			forEach(data->{
				if(predi.test(data))
					newList.add(data);
			});
			return stream(newList);
		}
		<R> Stream<R> map(Function<T, R> mapper){
			ArrayList<R> newList = new ArrayList<>();
			forEach(data->newList.add(mapper.apply(data)));
			return stream(newList);
		}
		
		void forEach(Consumer<T> iter){
			for(T data : list) iter.accept(data);
		}
		
		T reduce(BinaryOperator<T> reducer ,T memo) {
			//간소화된 유효성 검사, 본질을 흐리지 않는 선에서 간략화
			if(memo == null) return reduce(reducer);
			if(this.list.size() < 2) return this.list.get(0);
			HashMap<Class<?>, T> map = new HashMap<>();
			map.put(memo.getClass(), memo);
			each(this.list, data->map.compute(memo.getClass(), (k,v)->{
				T result = reducer.apply(v, data);
				return result;
			}));
			return map.get(memo.getClass());
		}
		T reduce(BinaryOperator<T> reducer) {
			T tmpValue = this.list.get(0);
			if(this.list.size() < 2) return tmpValue;
			this.list = this.list.subList(1, list.size());
			return reduce(reducer, tmpValue);
		}
		
		Optional<T> find(Predicate<T> predi) {
			for(int i=0;i<this.list.size();i++) {
				T value = this.list.get(i);
				if(predi.test(value)) return Optional.of(value);
			}
			return Optional.empty();//편의상 null리턴
		}
		Integer findIndex(Predicate<T> predi) {
			for(int i=0;i<this.list.size();i++) {
				if(predi.test(this.list.get(i))) return i;
			}
			return -1;//편의상 null리턴
		}
		
		Boolean some(Predicate<T> predi) {
			return findIndex(predi) != -1;
		}
		Boolean every(Predicate<T> predi) {
			return findIndex(predi.negate()) == -1;
		}
		
		List<T> toList(){
			return this.list;
		}
		
	}
	
	
}
 

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

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

www.inflearn.com

find

filter와 유사하나 predi에 걸러진 처음으로 만나는 요소를 반환한다.

/*찾아내기 대표 함수 find
filter와 유사하나 predi에 걸러진 처음으로 만나는 요소를 반환한다.*/
function find(list, predi){
    const _keys = keys(list);
    for(let i=0;i<_keys.length;i++){
        const val = list[_keys[i]];
        if(predi(val)) return val;
    }
    return undefined; //생략 가능, 자동으로 undefined 리턴될 것
}
console.log(
    _.find(users, user => user.age >= 30),
    _.filter(users, user => user.age >= 30),
);

find_index

predi에 걸러진 처음으로 만나는 요소의 인덱스를 리턴한다.

/*predi에 걸러진 처음으로 만나는 요소의 인덱스를 리턴한다.*/
function find_index(list, predi){
    const _keys = keys(list);
    for(let i=0;i<_keys.length;i++){
        if(predi(list[_keys[i]])) return _keys[i];
    }
    return -1; //자바스크립트 표준에 맞춤
}
console.log(
    _.find_index(users, user => user.age <= 25),
    _.find_index(users, user => user.age > 40),
);

주의사항

설명으로 처음으로 만나는 요소라고 했는데, 이것이 순차 탐색을 의미하는 것이 아니다. 

언어를 넘어서 함수형을 부수효과를 제거하고, 항상 새로운 값을 만들어 리턴한다. 이러한 점은 병렬 처리에 강점이된다. 따라서 대부분의 언어에서 멀티 쓰레드로 처리할 수도 있다. 이 경우 가장 처음 조우하는 값을 리턴하게 된다.

 

some

주어진 조건에 값이 하나라도 존재하는지 검사한다.

/* find_index 특화 함수 주어진 조건에 값이 하나라도 존재하는지 검사한다. */
function some(list, predi){
    //하나라도 해당하면 -1을 리턴할 수 없다.
    return find_index(list, predi) !== -1;
}

every

주어진 조건에 값이 전부 일치하는지 검사한다.

/* find_index 특화 함수 주어진 조건에 값이 전부 일치하는지 검사한다.*/
function every(list, predi){
    //주어진 조건 반대에 해당하는 값을 못찾으면, 전부 해당하는 것
    return find_index(list, negate(predi)) === -1;
}
function every2(list, predi){
    return find_index(list, function(val){
        return !predi(val);
    })
}
function every3(list, predi){
    return find_index(list, (val)=> !predi(val) )
}

전부 일치를 확인하려면, 전제 조건이 모든 요소를 전부 순회해야한다.

따라서 find_index가 -1을 리턴하도록 유도한다. 그리고 나서 주어진 조건식을 활용할 방법을 생각하면, every 동작 방식이 쉽게 이해된다.

함수를 사용해 조건식 결과를 반대로 만들면 된다. 이때 이미 만들어둔 negate를 사용했다.

 

console.log(
    _.some(users, user=>user.age>30),
    _.every(users, user=>user.age>30),
    _.every(users, user=>user.age>10),
)

더 다듬기

조건식이 주어지지 않을때 기본적으로 동작할 방식을 지정해준다.

이에 identity 함수를 사용했다.

/* find_index 특화 함수 주어진 조건에 값이 하나라도 존재하는지 검사한다.*/
function some(list, predi){
    //하나라도 해당하면 -1을 리턴할 수 없다.
    return find_index(list, predi||identity) !== -1;
}
/* find_index 특화 함수 주어진 조건에 값이 전부 일치하는지 검사한다.*/
function every(list, predi){
    //주어진 조건 반대에 해당하는 값을 못찾으면, 전부 해당하는 것
    return find_index(list, negate(predi||identity)) === -1;
}

아무런 의미도 없을 것 같던 identity 함수가 요긴하게 쓰인다.

console.log(
    _.some(users),
    _.every(users),
    _.some(testArr),
    _.every(testArr),
)

contains

주어진 값이 존재하는지 검사하는 함수

/*주어진 값이 존재하는지 검사하는 함수*/
function contains(list, data){
    return find_index(list, val => val === data) !== -1;
}
console.log(
    _.contains(users, users[2] ),
    _.contains(users[2], "JM" ),
    _.contains(users[2], "홍길동" ),
)

 

function.html
0.01MB

 
 

function.html

0.01MB

 

+ Recent posts