반응형

find_if는 전달된 범위에서 지정된 조건을 만족하는 첫번째 요소를 리턴해줍니다. 

함수 정의

함수의 정의를 보시면 순차 검색을 통해서 조건에 만족하는 iterator를 리턴합니다. 

// 자세한 구현 사항은 컴파일러를 통해서 정의된 구문을 참조 바랍니다. 
template<class _InIt, class _Pr>
_InIt find_if(_InIt _First, const _InIt _Last, _Pr _Pred) { 
    for (; _First != _Last; ++_First) {
        if (_Pred(*_First)) 
            break;        
    }
    return _First;
}
매개변수 설명 
_First 검색할 범위의 첫번째 요소의 iterator
_Last 검색할 범위의 마지막 요소의 iterator
_Pred 검색 조건을 정의하는 사용자 정의 함수 객체입니다. 
bool(const T& arg)의 형식을 갖춰야 합니다. 

기본 사용 구문

아래의 예제는 컨테이너에서 5를 초과하는 요소를 찾는 간단한 예제 구문입니다. 

#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

int main()
{
	vector<int> ints = { 1,2,3,4,5,6,7,8,9,10 };

	// 5 초과 값을 가진 요소를 찾으시오
	const auto findIt = find_if(begin(ints), end(ints), [](int value) { return 5 < value;  });

	std::cout << "findIt : " << *findIt << std::endl; 

	return 0;
}

// 출력 : 6

 

응용 사용 구문

이번에는 find_if를 실무에서 발생할 수 있는 간단한 사례를 들어서 설명 드립니다. 

"주어진 AbilityTicket에서 weight 기반으로 랜덤 뽑기를 구현해주세요. "  다음과 같은 조건으로

개발 요청이 들어 온 경우 컨테이너에서 weight를 검사하여 찾는 구문에 find_if를 사용할 수 있습니다. 

#include <algorithm>
#include <vector>
#include <iostream>

using namespace std;

struct AbilityTicket
{
	AbilityTicket(const std::string& name, uint16_t value, uint16_t weight)
		: name(name), value(value), weight(weight)
	{}

	std::string name;					// 이름
	uint16_t	value;					// 값		
	uint16_t	weight;					// 비중
};

vector<AbilityTicket> AbilityLotteryContainer = {
	{"attack", 5, 5},
	{"defense", 5, 5},
	{"critical", 2, 5},
	{"reduction", 3, 3},
	{"hp", 100, 10},
	{"mp", 50, 10}
};

// 리턴값 : 0 ~ (base - 1), 간략한 랜덤 함수
const int getRandomValue(const int base)
{
	return rand() % base;
}

int main()
{
	srand((unsigned int)time(nullptr));

	// 요청 : AbilityLotteryContainer에서 weight를 기반으로 랜덤 옵션 하나를 뽑아주세요
	uint32_t totalWeight{ 0 };
	std::vector<uint32_t> accumulateWeights;
	accumulateWeights.reserve(AbilityLotteryContainer.size());
    
	// ability별 가중도를 accumulateWeights 추가 합니다. 
	for (auto& abilityTicket : AbilityLotteryContainer)
	{
		totalWeight += abilityTicket.weight;
		accumulateWeights.push_back(totalWeight);
	}

	// 랜덤 값 추출
	const auto lot{ static_cast<uint32_t>(getRandomValue(totalWeight)) };

	// lot 값을 기반으로 ability를 찾습니다.(이곳에서 find_if를 사용합니다.)
	const auto findIt = std::find_if(begin(accumulateWeights), end(accumulateWeights),
		[lot](const auto & accumulateWeight)
		{ return lot < accumulateWeight; });

	// 같은 기능을 lower_bound를 통해서 구현 할 수도 있습니다. 
	// const auto findIt2 = std::lower_bound(begin(accumulateWeights), end(accumulateWeights), lot + 1);

	// 값을 찾지 못하였는지 체크 
	if (findIt == end(accumulateWeights)) {
		std::cout << "not find" << '\n';
		return 0;
	}

	// 찾은 인덱스를 구합니다. 
	const auto index = findIt - begin(accumulateWeights);

	std::cout << "lot : " << lot << '\n';
	std::cout << "find Ability : " << AbilityLotteryContainer[index].name << ", " << AbilityLotteryContainer[index].value << " index : " << index << '\n';

	return 0;
}

데이터가 정렬되어 있다면 비슷한 기능을 lower_bound 를 통해서도 구현 할 수 있습니다. 

lower_bound는 이진 탐색으로 구현되기에 검색 데이터가 많다면 find_if보다 빠른 검색 속도를 제공합니다. 

성능차이가 궁금해서 제 로컬 환경에서 테스트 했을 때 결과를 공유 드리면 다음과 같습니다. 

  • Release 빌드, 최적화를 피하기 위해서 결과 데이터 저장 
  • 대략적인 성능 측정 및 테스트 도중에 오차가 있을수 있음을 감안 부탁 드립니다. 
Ticket 갯수 성능차이( find_if와 lower_bound의 성능 측정)
100개 약 2배
300개 약 3배
600개  약 6배
1200개  약 10배

참고

 

std::find, std::find_if, std::find_if_not - cppreference.com

(1) template< class InputIt, class T > InputIt find( InputIt first, InputIt last, const T& value ); (constexpr since C++20) (until C++26) template< class InputIt, class T = typename std::iterator_traits                                    

en.cppreference.com

 

반응형

+ Recent posts