unique함수는

https://velog.io/@whipbaek/c-unique-%ED%95%A8%EC%88%98%EC%97%90-%EA%B4%80%ED%95%98%EC%97%AC

 

c++ unique() 함수에 관하여

공부하게 된 배경https://programmers.co.kr/learn/courses/30/lessons/12906위 문제를 푸는데 unique와 erase의 조합 한 줄 만으로 해결하는 코드가 있었다. 문제를 풀 때는 생각이 안났으나 두 함수 모두 대강 알

velog.io

의 블로그를 참고하여 작성하였습니다.


unique(구간의 시작값, 구간의 마지막값) ;

-범위내에 인접하는(연속적인) 중복 요소들을 제거해준다.

-정렬 후에 배열의 사이즈가 바뀌지 않음

-배열의 사이즈가 바뀌지 않고 중복요소인 원소들은 뒤로 밀려난다.

-반환값은 중복없이 나열된 마지막 원소 다음의 반복자를 반환한다.

- <algorithm> 헤더를 사용한다.

 

unique함수 하나로는 vector 중복 원소 제거를 하는것은 어렵다.sort()함수를 사용하여 정렬 한 뒤 erase()함수를 활용하여 쓰레기값을 제거해줘야한다. 

 

 

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

void main()
{
    std::vector<int> v1 = { 10,10,20,30,20,10 };

    std::vector<int>::iterator iter = unique(v1.begin(), v1.end());

    for (int index : v1)
    {
        std::cout << index << " ";
    }
    std::cout << " 반환값 : " << *iter << " " << &iter << std::endl;

    //======================================================

    std::vector<int> v2 = { 10,10,20,30,20,10 };

    sort(v2.begin(), v2.end()); // 10,10,10,20,20,30
    iter = unique(v2.begin(), v2.end());
    for (int index : v2)
    {
        std::cout << index << " ";
    }
    std::cout << " 반환값 : " << *iter << " " << &iter << std::endl;
	
    v2.erase(iter, v2.end());
    // v2.erase(unique(v2.begin(), v2.end()), v2.end());
    //unique와 erase함수를 사용하여 정렬한 vector의 중복된 원소를 제거할 수 있다.  

    for (int index : v2)
    {
        std::cout << index << " ";
    }


    return;
}

 

<출력 결과>

첫번째 결과 : 정렬없이 unique함수를 동작했을때 반환되는 값은 v1[4]의 반복자가 반환된다.

두번째 결과 : 오름차순 정렬 뒤에 unique함수를 동작했을때 10의 원소가 한개씩 20,30으로 변경 되었다. 하지만 의미 중복을 제거하는 것이 목적이기에 의미 없는값들이다. (작동원리는 밑에서 서술)

세번쨰 결과 : v2.erase함수를 범위 : iter(v2[3]) ~ v2.end() 만큼 제거한뒤 출력한 값이다. 

 

<작동 원리>

더보기

위 링크는 첫번째 결과값을 작동원리를 설명했으니 나는 정렬한 두번째 값(10 10 10 20 20 30)으로 설명을 해보겠다.

//unique함수 원문
template <class ForwardIterator>
  ForwardIterator unique (ForwardIterator first, ForwardIterator last)
  {
  if (first==last) return last;

  ForwardIterator result = first;
  while (++first != last)
  {
    if (!(*result == *first))  // or: if (!pred(*result,*first)) for version (2)
      *(++result)=*first;
  }
  return ++result;
}//출처 : https://www.cplusplus.com/reference/algorithm/unique/

  if (first==last) return last;

first iter와 last iter가 같은경우는 원소가 1개 이하라는 뜻이므로 end() 값을 리턴

 

 

first, last , result iter(반복자) 초기화 완료
while 반복문의 함수 동작후 확인, // while문이 참이므로 반복분 시작

while 반복문에 들어와서 

 if (!(*result == *first)) , if조건문이 있는데  result와 first가 같으므로 true에 !를 만나 false를 반환

 

다시 while 반복문의 조건을 확인해서 계속 roop 시키면 ......

result와 first의 값이 달라지는 경우가 발생

 

 if (!(*result == *first)의 true임으로 if문이 동작한다.

*(++result)=*first; 동작 완료시

*first 가 가르키고 있는 20의 data를 result의 다음 반복자(++result)에 data를 정의하는것이다. 

그럼 vector의 [1] index의 data는 10-> 20으로 변경된다. 그리고 이 과정을 while이 false가 나올때까지 반복하게 되면  

중복 되지 않은 원소는 전부 앞으로 정렬하게 되고, 중복된 원소들은 뒤로 밀리게 되며 반환되는 return 값은 현재 가르키고있는 result의 다음 iter 값이 반환된다. 즉, unique함수의 return값은 쓰게기값(중복값)들이 모여있는 첫번째 인덱스를 iter를 반환하게 된다. 

 

이 점을 이용하여 unique한뒤 erase함수로 현재 반환 받은 값에서부터 end() 까지 지워버리면 

10 20 30 만 남게 되는것이다. 

 

reverse_iterator(역방향 반복자) : 반복자(iterator)와는 정반대로 동작하는 반복자

++,-- 연산자를 이용하여 iter를 변경하고 그랬는데 반대로 동작하게된다.

 

std::vector에서 자주 사용해온 begin(), end() 명령어는 반환값으로 iter(반복자)를 반환하는데

rbegin(), rend()는 riter(역방향반복자)를 반환한다.

즉,begin() = rend() , end() = rbegin() 인것이다.

 

예제코드)

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

int main(void)
{
    std::vector<int> v = { 10,20,30,40,50 };
	
    //벡터의 첫 원소부터 차례대로 출력
    for (std::vector<int>::iterator iter = v.begin(); iter != v.end(); iter++)
    {
        int num = *iter;
        cout << num;
    }

    cout <<endl;
    
    //벡터의 뒷 원소부터 역으로 출력 ( riter가 ++ 로 증가하지만 역으로 감소한다)
    for (std::vector<int>::reverse_iterator riter = v.rbegin(); riter != v.rend(); riter++)
    {
        int num = *riter;
        cout << num;
    }

}

//출력 결과
1020304050
5040302010

이 역방향 반복자를 이용하여 vector 자료형의 내림차순 정렬을 쉽게 만들 수 있다.

내림차순 정렬을 하는 방법은 많지만 주로 코딩테스트를 풀 때 이 방법들을 사용해 왔다.

(sort(구간의 시작값, 구간의 끝값) , sort 함수는 algorithm 헤더에 포함되어있다,)

 

1.sort함수에서 조건자에 grearter<자료형>() 을 쓴다.
2.조건자를 재량것 compare 함수를 만들어 쓴다.

3.sort함수로 오름차순 정렬한것을 reverse()로 뒤집어 내림차순 정렬로 만들어 쓴다.

4.sort함수의 인자값을 역방향반복자를 매개변수로 넣는다. (이 코드 만들수 있다.)

 

예제 코드)

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

void main()
{
	std::vector<int> v = { 1,5,2,8,4,9 };

	//오름차순 정렬
	sort(v.begin(), v.end());
	
	for (int index : v)
	{
		std::cout << index << " ";
	}
	std::cout << std::endl;

	//내림차순 정렬 
	sort(v.rbegin(), v.rend()); // 이부분이 v의 원소들을 내림차순 정렬을 한다. 
	for (int index : v)
	{
		std::cout << index << " ";
	}
	return;
}

//출력 결과
//1 2 4 5 8 9 (오름차순 정렬)
//9 8 5 4 2 1 (내림차순 정렬)

 

'C++' 카테고리의 다른 글

unique() (vector의 중복 값 제거)  (0) 2023.06.12
STL 범용 수치 알고리즘 (accumulate , inner_product)  (0) 2023.06.09
문자집합(Character Set)  (0) 2023.06.04

 

//accumulate 함수의 원문
#include <numeric>

template <class InputIterator, class T>							
T accumulate (InputIterator first, InputIterator last, T init);

template <class InputIterator, class T, class BinaryOperation>	
T accumulate (InputIterator first, InputIterator last, T init, BinaryOperation binary_op);

accumulate(구간의 시작 값, 구간의 마지막 값 , 초기 값)

-지정한 구간에 속한 값들을 모든 더한 값을 계산한다.

-기본적으로 더하기 연산, 조건자 이용시 이외의 연산 가능 (binary_op) 

-필요 헤더 : <numeric>

 

예제 코드)

#include <iostream>
#include <vector>
#include <numeric>

void main()
{
	std::vector<int> v1 = { 1,2,3,4 };

	int result = std::accumulate(v1.begin(), v1.end(), 0);

	std::cout << result;

	return;
}

//출력결과
//10
//1 + 2 + 3 + 4 = 10

//inner_product 함수 원문
#include <numeric>

template <class _InIt1, class _InIt2, class _Ty, class _BinOp1, class _BinOp2>
_Ty inner_product(_InIt1 _First1, _InIt1 _Last1, _InIt2 _First2, _Ty _Val, _BinOp1 _Reduce_op, _BinOp2 _Transform_op)

template <class _InIt1, class _InIt2, class _Ty>
_Ty inner_product(const _InIt1 _First1, const _InIt1 _Last1, const _InIt2 _First2, _Ty _Val)

inner_product는 내적이란 뜻이며 벡터(방향,크기를 가지고있는 값)를 마치 수처럼 곱하는 개념)을 뜻한다.

a라는 std::vecotr<int> b라는  std::vecotr<int> 있다고 가장할때

inner_product(시작값a,마지막값a,시작값b,초기값);

-두 입력값의 내적을 계산하는 알고리즘으로 기본적으로 +,*을 사용한다.

-반환값은 a,b의 값을 곱한뒤 서로 더한값이다.

-두번째(b)의 크기는 첫번째(a)의 크기보다 크거나 같아야한다.

 

p.s 벡터의 내적은 벡터의 곱(dot product) 라고도 부르며 벡터 사이의 크기(스칼라)를 나타날때 사용된다. 

(수학 - 벡터 관련 수학 작성후  링크 필요)

 

예제코드)

#include <iostream>
#include <vector>
#include <numeric>

void main()
{
	std::vector<int> v1 = { 1,2,3 };
	std::vector<int> v2 = { 4,5,6 };

	int result = std::inner_product(v1.begin(),v1.end(),v2.begin(),0);

	std::cout << result;

	return;
}

//출력결과
//32
//0(초기값) + (1 * 4) + (2 * 5) + (3 * 6) = 32

 

여러 API에서 제공하는 함수를 사용할때 매개변수를 문자열이나 단일문자로 인자값을 넣을때 함수의 자료값을 보고 멀티바이트인지 유니코드인지 헷갈릴때가 종종 생겨서 내가 정리하는겸 만들었다. 내용은 정말 간략하게 정리했다.

 

틀린 내용이 있거나 알고있으면 좋은 정보가 있으면 댓글 부탁드립니다. 


문자집합(Character Set) 정의

더보기

문자 집합은 정보를 표현하기 위한 글자나 기호들의 집합을 정의한 것

 

문자나 기호의 집합을 컴퓨터에 저장하거나 통신에 사용할 모적으로 부호화 하는 것을 문자인코딩(부호화)이라

하고 인코딩 된 문자부호(Character Set)를 다시 디코딩(복호화)하여본래 문자나 기호로 표현 할 수 있습니다.

 

문자의 크기(Size)

더보기

8bit = 1byte

Char 단일문자 자료형 크기가 1byte (0~ 255)다.

 

아스키 코드 1Byte
영어,숫자 등등 (반각문자) 1Byte
한글,일본어 등등 (전각문자) 2Byte

 

중국어 같은경우 획수가 많은건 3Byte도 있지만 그런건 예외이기 떄문에 위에 내용만 알고있으면 된다. 

 

ASCII(아스키코드)

더보기
아스키코드표

ASCII(American Standeard Code for Information Interchage) 의 약자다.

아스키 코드표는 용량이 1byte이지만
실제로 7bit까지만 써서 0~127(2^7)의 숫자를 나타낸다.

 

그리고 아스키코드 이후에 ISO/IEC 8859-1, ISO/IEC 8859-N 문자집합의 등장으로

기존 아스키코드 0~127은 그대로 사용하고 8bit까지 추가로 사용하게되며  128 ~ 255(2^8) 영역까지 사용하여 서유럽어에서 필요로 하는 문자와 몇가지 특수문자를 추가했다한다.

 

MultiByte(멀티바이트)

더보기

MBCS(Multy Byte Character Set) 의 약자이며 가변 너비 인코딩이라고한다.

 

다국어를 표한하기 위해 사용했던 방식의 문자 집합중 하나

문자의 크기에서 영어,숫자는 1byte 한국어,일본어는 2byte라고 소개했는데 이 부분을 해결해주는것이 멀티바이트 문자집합이다. 

 

간단히 말해서 입력값으로 들어오는 단일문자나 문자열을 자동으로 문자 종류에 따라 byte를 지정해주는 것이다. 

BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
//(hdc,x 좌표 , y 좌표, 문자열 , 문자열 길이)
//매크로 함수이며 문자열인자값을 무엇을 넣는냐에 따라 그에 맞는 함수가 호출된다.

//인자값 : 멀티바이트 문자열(LPCSTR)
BOOL TextOutA( _In_ HDC hdc, _In_ int x, _In_ int y, _In_reads_(c) LPCSTR lpString, _In_ int c);
//멀티바이트 문자나 문자열을 사용시 함수명뒤에 A가 붙는다.

 

UniCode(유니코드)

더보기

Universal Coded Character Set의 약자이며, 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준이다.

 

위에서 설명한 문자의 크기가 언어 종류에 따라 달랐는데 유니코드는 이런 가변적인 특성을 없애고 모든 문자를 2byte로 표현하기 위해 정의한 방식이다. 즉, 모든 문자를 2byte로 일관되게 지정하고 사용한다는것이다.

 

BOOL TextOut(HDC hdc, int nXStart, int nYStart, LPCTSTR lpString, int cbString);
//(hdc,x 좌표 , y 좌표, 문자열 , 문자열 길이)
//매크로 함수이며 문자열인자값을 무엇을 넣는냐에 따라 그에 맞는 함수가 호출된다.

//인자값 : 유니코드 문자열(LPCWSTR)
BOOL TextOutW( _In_ HDC hdc, _In_ int x, _In_ int y, _In_reads_(c) LPCWSTR lpString, _In_ int c);
//유니코드는 함수명뒤에 W(wide)를 붙인다. 또한 자료형에 W를 추가한다.(LPCWSTR)
//멀티바이트 문자열을 대입할때 "ABC123가나다" 이렇게 따옴표 안에 내용을 넣어서 사용한다.
//유니코드는 문자열 앞에 L을 추가하고 내용을 적는다. ex) L"ABC123가나다"

 

문자 집합에 따른 문자열 길이 계산시 유의사항

더보기
char* Ch = "멀티1234"; //멀티바이트 문자열 초기화
wchar_t* wCh = "유니1234" //유니코드 문자열 초기화

int len_Multi = strlen(Ch); //멀티바이트 문자열 길이 리턴
int size_Multi = sizeof(char) * Ch; //멀티바이트 문자열 크기 리턴

int len_Uni = strlen(wCh); //유니코드 문자열 길이 리턴
int size_Uni = sizeof(wchar_t) * wCh; //유니코드 문자열 크기 리턴

/* 결과값 */
// len_Multi = 8 : 한글은 (2글자 * 2byte), 숫자는 (4글자 * 1byte), 총 8글자.
// size_Multi = 8 : 한글은 2byte로 취급,숫자는 1byte취급 ,총 8byte.
// len_Uni = 6 : 유니코드는 전부 2byte로 취급, 전부 1글자 취급 ,총 6글자
//  size_Uni = 12  : 유니코드는 전부 2byte로 취급(6글자 * 2byte), 총 12글자

//p.s 멀티바이트 문자열 계산함수는 _mbslen() 함수를 이용해야 함.

 

프로젝트의 문자집합 확인 및 변경하기

더보기

컴파일러가 지정된 문자집합을 사용하게 하는 설정이다.

프로젝트> 속성 > 구성 속성 > 고급 > 문자 집합 >문자집합 변경

프로젝트의 문자 집합 변경

 

 

+ Recent posts