[C++] STL - algorithm

seunghyun·2023년 6월 28일
0

1. find

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>

// Q 01. 특정 숫자가 있는지? 

int main()
{
	vector<int> v;
    
	int number = 50;
    
    vector<int>::iterator it;
    for (it = v.begin(); it != v.end(); it++)
    {
    	int value = *it;
        if (value == number)
        {
			break;
        }
    }
    
    // 위처럼 하면 좀 길다. algorithm 을 include 하면 
    // 아래처럼 표준 권장 방식으로 간결하고 가독성 있게 작성할 수 있다.
    // 위와 완전히 동일한 의미이다 !!!
    
    auto it = std::find(v.begin(), v.end(), number);
    if (it == v.end())
    {
		cout << "못찾음" << endl;
    }
    else
    {
		cout << "찾음" << endl;
	}
}

2. find_if

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>

// Q 02. 11 로 나뉘는 숫자가 있는지?
int main()
{
	vector<int> v;
    
	int div = 11;
    
    vector<int>::iterator it;
    for(it = v.begin(); it != v.end(); it++)
    {
		int value = *it;
        if (value & div == 0)
        {
			break;
        }
	}


	// 람다로 만드는 것이 제일 좋긴한데, 일단 펑터로 predicate 을 만들어보자!
	struct CanDivideBy11
    {
    	bool operator()(int n)
        {
        	return n % 11 == 0;
        }
    };
    
	// 위와 똑같은 의미이되 더 간결하고 가독성이 좋은 것이 있다.
    std::find_if(v.begin(), v.end(), CanDivideBy11());
    
	if (it == v.end())
    {
		cout << "못찾음" << endl;
    }
    else
    {
		cout << "찾음" << endl;
	}
}

왜 끝 요소 다음 ?

끝 범위를 끝 요소 다음으로 설정하는 이유는 일관성을 유지하고, 예외 상황을 방지하기 위함입니다.

C++의 반복자(iterator)는 현재 위치를 가리키는 포인터와 유사한 개념입니다. 반복자는 시작 요소부터 끝 요소까지의 범위를 나타내며, 일반적으로 시작 요소를 가리키는 반복자로부터 시작하여 끝 요소의 다음 위치를 가리키는 반복자까지 포함합니다.

끝 범위를 끝 요소 다음으로 설정하는 이유는 다음과 같습니다:

  • 일관성: 시작과 끝을 모두 포함하는 범위를 지정함으로써, 다양한 알고리즘 함수에서 동일한 방식으로 범위를 처리할 수 있습니다. 시작과 끝을 구분하여 처리하는 것보다 일관성을 유지하는 것이 좋습니다.
  • 예외 상황 방지: 끝 범위를 끝 요소의 다음 위치로 설정하면, 예외 상황을 방지할 수 있습니다. 예를 들어, 끝 범위가 마지막 요소를 가리킬 경우, 마지막 요소 이후에 유효하지 않은 요소를 참조하게 되어 예기치 않은 동작을 유발할 수 있습니다. 끝 요소 다음을 가리키는 반복자를 사용하면 이러한 예외 상황을 방지할 수 있습니다.

따라서, 시작과 끝 범위를 정확하게 명시함으로써 일관성을 유지하고 예외 상황을 방지할 수 있습니다. 이는 C++의 컨테이너와 알고리즘 함수의 일관된 동작을 보장하고, 안정성을 높이는 데 도움을 줍니다.


predicate ?

predicate (조건자) 는 함수 객체 또는 람다 표현식으로 구현된 조건을 나타내는 함수이다. 주로 알고리즘 함수와 함께 사용되며, 해당 알고리즘에서 요소를 처리할 때 어떤 조건을 만족하는지를 판별하는 데 사용된다.

true 혹은 false를 반환한다. predicate는 찾고자 하는 조건을 나타내는 함수 객체 또는 람다 표현식으로 제공됩니다. std::find_if는 주어진 범위에서 predicate를 사용하여 조건을 만족하는 첫 번째 요소를 찾습니다.

bool IsEven(int n)
{
	return n % 2 == 0;
}

std::vector<int> v = {1, 2, 3, 4, 5};
auto it = std::find_if(v.begin(), v.end(), IsEven);

위 예시에서 IsEven 함수는 predicate 로 사용된다.


find vs find_if

find() 함수는 특정 값을 찾아 해당 값을 가진 첫 번째 요소를 반환한다.
선형 탐색을 수행하며, 컨테이너의 첫 번째 요소부터 순차적으로 비교하여 값을 찾는다. 값을 찾으면 해당 요소를 반환하고, 찾지 못하면 container.end()를 반환한다.

find_if()함수는 조건자를 사용하여 특정 조건을 만족하는 첫 번째 요소를 찾는다. 조건자는 주어진 요소를 평가하여 true 혹은 false를 반환하는 함수 객체(함수 포인터, 람다식 등) 이다.

이러한 차이로 인해 find_if()함수는 보다 유연한 탐색이 가능하며, 특정 조건을 만족하는 요소를 찾는 데 활용된다.


3. count_if 시리즈

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>

// Q 03. 홀수인 숫자 개수는?

int main()
{
	vector<int> v;
    
	int count = 0;
    for (auto it = v.begin(); it != v.end(); it++)
    {
		if (*it % 2 != 0)
        	count++;
	}
    
    struct IsOdd
    {
    	bool operator()(int n)
        {
        	return n % 2 != 0;
        }
    };
    
    // 홀수의 개수?
    int n = std::count_if(v.begin(), v.end(), IsOdd());
    
    // 모든 데이터가 홀수인가?
    bool b1 = std::all_of(v.begin(), v.end(), IsOdd());
    
    // 홀수가 하나라도 있나?
	bool b2 = std::any_of(v.begin(), v.end(), IsOdd());
    
    // 모든 데이터가 홀수가 아닌가?
    bool b2 = std::none_of(v.begin(), v.end(), IsOdd());
}

4. for_each ✨

모든 데이터를 순회하며 작업할 때 쓴다.
활용 빈도가 높다.

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>

// Q 04. 벡터에 있는 모든 숫자들에 3을 곱해주세요

int main()
{
	vector<int> v;
    
	struct MultiplyBy3
    {
    	void operator()(int& n)
        {
			n *= 3;
		}
    };
    
	std::for_each(v.begin(), v.end(), MultiplyBy3());
}

5. remove 시리즈 ✨

remove_if 를 많이 쓰는 편이다. remove, remove_if 는 값으로 삭제하느냐 predicate 으로 삭제하느냐 그 차이이다.
더럽게 만들었지만 받아들여야 해요.

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>

// Q 05. 홀수인 데이터를 일괄 삭제

int main()
{
	vector<int> v = {1, 4, 3, 5, 8, 2};
    
    // 원래 방식
    for (auto it = v.begin(); it != v.end();)
    {
    	if (*it % 2 != 0)
        	it = v.erase(it);
        else
        	it++;
    }
    
    // 이게 표준
    struct IsOdd
    {
    	bool operator()(int n)
		{
        	return n % 2 != 0;
        }
    };
    std::remove_if(v.begin(), v.end(), IsOdd());
    v.erase(it, v.end()); // 세트니까 꼭 기억하고 같이 해줘야해요
}

struct IsOdd 는 함수 객체로, operator()를 오버로딩하여 주어진 정수 n이 홀수인지 여부를 판단한다.
std::remove_if(v.begin(), v.end(), IdOdd())는 범위 v.begin()부터 v.end()까지의 요소 중에서 조건자 IsOdd()를 만족하는 요소를 찾아서 해당 요소를 벡터의 끝으로 이동시킨다. 이때, std::remove_if 알고리즘은 값이 일치하는 요소를 삭제하지 않고 이동시킨다. 따라서, 홀수를 제외한 요소들이 앞쪽으로 이동하고, 벡터의 끝 부분에는 삭제되지 않은 요소들이 남아있다.
v.erase(it, v.end())는 범위 it부터 v.end()까지의 요소를 제거한다. 이때, itstd::remove_if함수의 반환값으로, 홀수 값을 제외한 벡터의 시작 위치를 가리킨다. 따라서 erase함수를 사용하여 범위를 지정하면 홀수 값을 제거한 최종적인 결과가 남게 된다.

0개의 댓글