#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;
}
}
#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 (조건자) 는 함수 객체 또는 람다 표현식으로 구현된 조건을 나타내는 함수이다. 주로 알고리즘 함수와 함께 사용되며, 해당 알고리즘에서 요소를 처리할 때 어떤 조건을 만족하는지를 판별하는 데 사용된다.
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()
함수는 특정 값을 찾아 해당 값을 가진 첫 번째 요소를 반환한다.
선형 탐색을 수행하며, 컨테이너의 첫 번째 요소부터 순차적으로 비교하여 값을 찾는다. 값을 찾으면 해당 요소를 반환하고, 찾지 못하면 container.end()
를 반환한다.
find_if()
함수는 조건자를 사용하여 특정 조건을 만족하는 첫 번째 요소를 찾는다. 조건자는 주어진 요소를 평가하여 true
혹은 false
를 반환하는 함수 객체(함수 포인터, 람다식 등) 이다.
이러한 차이로 인해 find_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());
}
모든 데이터를 순회하며 작업할 때 쓴다.
활용 빈도가 높다.
#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());
}
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()
까지의 요소를 제거한다. 이때, it
는 std::remove_if
함수의 반환값으로, 홀수 값을 제외한 벡터의 시작 위치를 가리킨다. 따라서 erase
함수를 사용하여 범위를 지정하면 홀수 값을 제거한 최종적인 결과가 남게 된다.