본문 바로가기

지식/STL

set, multiset, bitset

8장: set, multiset, bitset

8.1 set 데이터 추상(data abstraction)

Sets, Ordered and Not 

set 값들의 콜렉션이다. set 데이터 구조를 구현하는데 사용되는 컨테이너는 순서를 유지하여 값들을 관리하기 때문에, 원소의 삽입과 삭제뿐만 아니라, 특정 값이 콜렉션에 들어있는지의 여부를 검사하는데 있어 최적화되어 있다. 이러한 연산들은 각각 logarithmic 횟수로 수행된다. 반면에 list, vector, deque에서는 각각의 연산들이 최악의 경우에 컨테이너에 담긴 모든 원소들을 살피게 될수도 있다. 이러한 이유때문에, 삽입, 삭제와 값의 포함여부에 대한 검사가 중요한 문제에서는 set 선택해야 한다. list에서처럼, set 사이즈에 제한을 받지 않고, 콜렉션으로부터 원소들을 첨가하거나 삭제할 늘어나거나 줄어든다.

표준 라이브러리는 두가지 종류의 set 제공하는데, set 컨테이너에서는 모든 값들이 유일해야 하고, set 이미 들어있는 값을 중복 삽입하는 것은 무시가 된다. 한편, multiset 컨테이너에서는 같은 값이 여러개 있어도 이를 허용한다.

8.1.1 Include 화일

set이나 multiset 사용할 때는 set 헤더화일을 반드시 포함시켜야 한다.

   #include <set>
				

8.2 set과 multiset 연산

Set Bag

set multiset 제공하는 멤버 함수들을 자세히 알아본다. 멤버 함수들이 기초적인 연산들을 제공하지만, 13장과 14장에서 설명하는 generic 알고리듬을 사용함으로써 이들 데이터 구조를 좀더 유용하게 사용할 있다.

8.2.1 set의 선언과 초기화

set 템플릿 타입으로 첫번째 인자로 원소 타입을, 두번째 인자로 키값을 비교하는 연산자를 명시해주어야 한다. 두번째 인자는 생략가능한데, 따로 명시하지 않으면, 키타입에서 정의하고 있는 less-than 연산자를 사용한다. 원소의 타입은 int 또는 double 같은 primitive 타입이나, 포인터 타입 또는 사용자 정의 타입이 있다. 원소 타입은 상등 연산자(==) less-than 연산자(<) 가지고 있어야 한다.
Initializing Sets with Iterators

set 초기값없이 선언될 수도 있고, 한쌍의 반복자를 써서 다른 컨테이너에 담긴 값들로 초기화할 수도 있다. 두가지 경우 모두 비교 함수를 인자로 사용할 있으며, 생략가능하다. 비교함수는 템플릿 인자로 명시된 비교 함수를 덮어쓴다. 이런식으로 비교함수를 지정함으로써, 값은 같지만 순서를 다르게 매기는 두개 이상의 set 다루는 경우에 유용하다. set 멤버 함수들이 여러개 개체화되는 것을 막을 있기 때문이다. 복사 생성자를 사용하여 다른 set 복사본이 되는 set 만들 있다.

   set <int> set_one;

   set <int, greater<int> > set_two;
   set <int> set_three(greater<int>());

   set <gadget, less<gadget> > gset;
   set <gadget> gset(less<gadget>());

   set <int> set_four (aList.begin(), aList.end());
   set <int> set_five 
      (aList.begin(), aList.end(), greater<int>());

   set <int> set_six (set_four);                // copy constructor
				

set 다른 set 대입할 수도 있고, swap() 연산을 써서 set간의 값들을 교환할 수도 있다.

   set_one = set_five;
   set_six.swap(set_two);
				

8.2.2 타입 정의

set multiset 클래스는 많은 타입 정의들을 포함한다. 이들은 선언문에서 가장 많이 사용된다. 예를 들어, 정수 set 사용할 반복자는 다음과 같이 선언된다.

   set<int>::iterator location;
				

iterator뿐만 아니라, 다음 타입들도 정의되어 있다.
 

value_type

The type associated with the elements the set maintains.

const_iterator

An iterator that does not allow modification of the underlying sequence.

reverse_iterator

An iterator that moves in a backward direction.

const_reverse_iterator

A combination constant and reverse iterator.

reference

A reference to an underlying element.

const_reference

A reference to an underlying element that will not permit modification.

size_type

An unsigned integer type, used to refer to the size of containers.

value_compare

A function that can be used to compare two elements.

difference_type

A signed integer type, used to describe the distance between iterators.

allocator_type

An allocator used by the container or all storage management.

8.2.3 삽입

Pair 데이터 타입

list vector 달리 set 원소를 새로 추가하려면 단한가지 방법밖에 없다. insert() 멤버 함수를 써서 set이나 multiset 값을 삽입할 있는 것이다. multiset 경우에는 방금 삽입한 값을 가리키는 반복자를 리턴한다. set 경우에는 pair 리턴하는데, 첫번째 필드는 반복자이고, 두번째 필드는 부울값으로 원소가 삽입되었는 지의 여부를 표시한다. set에서는 원소를 삽입할 이미 콜렉션내에 해당 원소가 있으면, 삽입이 이루어지지 않기 때문이다.

   set_one.insert(18);

   if (set_one.insert(18).second)
      cout << "element was inserted" << endl;
   else
      cout << "element was not inserted " << endl;
				

다른 컨테이너에 들어있는 값들을 삽입할 때는 한쌍의 반복자를 사용한다.

   set_one.insert(set_three.begin(), set_three.end());
				

pair 데이터 구조는 값들의 투플이다. 첫번째 값은 first라는 필드명으로 접근하고, 두번째 값은 second라는 필드명으로 접근한다. make_pair()라는 함수는 pair 클래스의 개체를 만드는 작업을 간편하게 해준다.

template <class T1, class T2>
struct pair {
   T1 first;
   T2 second;
   pair (const T1 & x, const T2 & y) : first(x), second(y) { }
};

template <class T1, class T2>
inline pair<T1, T2> make_pair(const T1& x, const T2& y)
   { return pair<T1, T2>(x, y); }
				

예를 들어, 새로운 원소의 키부분이 이미 존재하는 키값인지 알아보기 위해서, 키값들이 서로 일치하는지를 검사할 , 상등(==) 연산자를 사용하는 것이 아니라, 비교 함수를 사용한다. 키값들의 순서를 매기기위해 사용되는 비교 함수가 양방향으로 모두 거짓이면, 키값은 서로 같다고 본다. 다시 말해서, Compare(key1, key2) 거짓이고, Compare(key2, key1) 거짓이면, key1 key2 상등이라고 보는 것이다.

8.2.4 set에서의 삭제

set에서 값을 삭제할 때는 erase() 멤버 함수를 사용한다. 인자는 특정 값이거나, 값을 가리키는 반복자 또는 값들의 range 지정하는 한쌍의 반복자일 있다. 첫번째 형태를 multiset 사용할 때는 인자로 주어진 값과 일치하는 모든 원소들이 삭제되고, 리턴값은 삭제된 원소들의 갯수를 의미한다.

   // erase element equal to 4
   set_three.erase(4);

   // erase element five   
   set<int>::iterator five = set_three.find(5);
   set_three.erase(five);

     

   // erase all values between seven and eleven
   set<int>::iterator seven = set_three.find(7);
   set<int>::iterator eleven = set_three.find(11);
   set_three.erase (seven, eleven);
				

컨테이너가 가지고 있는 원소들의 타입이 소멸자를 가지고 있다면, 원소가 삭제되기 전에 소멸자를 호출하게 된다.

8.2.5 검색과 카운팅

size() 멤버함수는 컨테이너가 가지고 있는 원소의 갯수를 반환한다. empty() 멤버 함수는 컨테이너가 비어 있을 참을 리턴하는데, size() 0 비교하는 것보다 대체로 빠르다.

find() 멤버 함수는 특정값을 인자로 취해서 set내에 해당 값이 존재하면, 값의 위치를 가리키는 반복자를 리턴하고, set내에 존재하지 않으면, end-of-set 해당하는 반복자를 리턴한다(이는 end() 리턴하는 값과 같은 것이다). 같은 값이 여러개 존재할 있는 multiset 경우에는 이들 값중에서 적절한 값을 리턴한다.

   set<int>::iterator five = set_three.find(5);
   if (five != set_three.end())
       cout << "set contains a five" << endl;
				

lower_bound() upper_bound() 멤버 함수는 multiset 같이 쓰일 아주 쓸모가 있다. set 사용하면, 단지 find() 함수를 흉내내는 것에 지나지 않는다. lower_bound() 멤버 함수는 인자로 주어진 키값과 일치하는 첫번째 원소를 리턴하고, 반면에 upper_bound() 멤버 함수는 키값과 일치하는 마지막 원소를 지나서 첫번째 원소를 반환한다. 마지막으로, equal_range() 멤버 함수는 lower bound upper bound 포함하는 반복자의 pair 리턴한다.

count() 멤버 함수는 인자로 주어지는 값과 일치하는 원소의 갯수를 리턴한다. set 경우에는 값이 0 또는 1 반면, multiset 경우에는 음수가 아닌 임의의 값이 있다. 0 아닌 정수값은 참으로 간주하므로, count() 함수를 원소의 포함 검사에 사용할 수도 있다. 또는 find() 리턴값을 end-of-collection 반복자와 비교하여 포함 검사를 수도 있다.

   if (set_three.count(5))
      cout << "set contains a five" << endl;
				

8.2.6 반복자

No Iterator Invalidation

begin() end() 멤버 함수는 set multiset 대한 반복자를 생성한다. 이들 함수들이 만들어내는 반복자들은 상수이어야 하는데, 이는 set 원소에 새로운 값을 대입하게 되어 set 대한 순서 관계가 깨지는 것을 막기 위해서이다. 이들 반복자들을 사용하여, set 선언했을 명시한 비교 연산자로 정해진 순서대로 원소들을 접근할 있다. rbegin() rend() 멤버 함수는 역순으로 원소들을 접근하는 반복자들을 생성한다.

8.2.7 set 연산

전통적인 집합 연산인 부분집한 검사, union, intersection, difference 연산들은 멤버함수로 제공되지 않지만, 대신에 ordered 구조에 사용되는 generic 알고리듬으로 구현되어 있다. 이들 함수들에 관해서는 14.7절에 자세히 설명되어 있다. 다음은 이들 함수들이 set multiset 클래스와 함께 사용하는 법을 간략히 설명하고 있다.

8.2.7.1 부분집합 검사

includes()함수는 집합이 다른 집합의 부분집합인지 알아보는데 사용된다. multiset 경우에는 두번째 집합내에서 일치하는 원소의 갯수가 첫번째 원소의 갯수를 초과해야 한다. 인자의 갯수는 포함되는 집합을 나타내는 반복자 한쌍과 포함하는 집합을 나타내는 반복자 한쌍해서, 모두 4개가 필요하다.

   if (includes(set_one.begin(), set_one.end(),
      set_two.begin(), set_two.end()))
         cout << "set_one is a subset of set_two" << endl;
				

원소를 비교하는데 사용되는 것은 less-than 연산자(<)이며, set 선언할 명시한 비교 연산자를 사용하는 것이 아니다. 이것이 맘에 안들면, 다른 형태의 includes() 함수를 사용하면 된다. 이것은 집합의 원소의 순서를 매기기 위해서 사용되는 비교 함수가 인자로 추가되어, 5개의 인자를 필요로 한다.

8.2.7.2 합집합과 교집합

set_union() 함수는 집합의 합집합을 구성하는데 사용된다. 연산에 관여되는 두집합은 반복자의 쌍으로 표시하고, 합집합의 결과는 5번째 인자로 명시된 출력 반복자로 복사된다. 결과를 set으로 구성하기 위해서, 삽입 반복자를 사용하여 출력 반복자를 만들어야 한다.(삽입 반복자에 관해서는 2.4절을 참고)

   // union two sets, copying result into a vector
   vector<int> v_one (set_one.size() + set_two.size());

   set_union(set_one.begin(), set_one.end(), 
      set_two.begin(), set_two.end(), v_one.begin());

   // form union in place
   set<int> temp_set;
   set_union(set_one.begin(), set_one.end(), 
      set_two.begin(), set_two.end(), 
      inserter(temp_set, temp_set.begin()));
   set_one.swap(temp_set);  // temp_set will be deleted
				

set_intersection() 함수도 이와 비슷하며, 두집합의 교집합을 형성한다.

includes() 함수에서처럼 원소를 비교할 때는 선언시 명시한 비교 연산자대신에 less-than 연산자(<) 사용한다. 이것이 부적절하다고 생각되면, 6번째 인자로 비교 연산자를 요구하는 다른 형태의 set_union() set_intersection() 사용하면 된다.

multiset 합집합 연산은 두집합을 합병하는 연산과는 구별되어야 한다. 집합이 7 3 포함하고 있고, 다른 집합이 7 2 포함하고 있다고 가정하자. 이들의 합집합은 7 3개만 포함하지만 합병한 결과에는 5개가 포함된다. 합병은 merge() 함수를 이용한다(14.6 참고). 함수의 인자는 set_union() 함수와 동일하다.

8.2.7.3 차집합

차집합 연산에는 두가지 형태가 있다. 단순 차집합은 첫번째 집합의 원소중에서 두번째 집합에 속하지 않는 원소들을 나타낸다. 대칭 차집합은 두번째 집합의 원소를 제외한 첫번째 집합의 원소들과 첫번째 집합의 원소를 제외한 두번째 집합의 원소들을 합한 것이다. 이들은 각각 set_difference() set_symmetric_difference() 함수로 구성된다. 이들 함수들은 set_union() 사용법이 비슷하다.

8.2.8 기타 generic 알고리듬

set 순서를 매기고, 상수 반복자를 가지기 때문에, 13절과 14절에 설명하고 있는 generic 함수중에는 set 적용할 없거나 별로 유용하지 않는 것들이 많다. 그러나, 다음 표는 set 데이터 타입과 함께 사용될 있는 몇가지 함수들을 보여주고 있다.
 

용도

이름

해당절

Copy one sequence into another

copy

13.2.2

Find an element that matches a condition

find_if

13.3.1

Find a sub-sequence within a set

search

13.3.3

Count number of elements that satisfy condition

count_if

13.6.1

Reduce set to a single value

accumulate

13.6.2

Execute function on each element

for_each

13.8.1


8.3 예제 프로그램 - 철자 검사기

A simple example program that uses a set is a spelling checker. The checker takes as arguments two input streams; the first representing a stream of correctly spelled words (that is, a dictionary), and the second a text file. First, the dictionary is read into a set. This is performed using a copy() and an input stream iterator, copying the values into an inserter for the dictionary. Next, words from the text are examined one by one, to see if they are in the dictionary. If they are not, then they are added to a set of misspelled words. After the entire text has been examined, the program outputs the list of misspelled words.

void spellCheck (istream & dictionary, istream & text)
{
   typedef set <string, less<string> > stringset;
   stringset words, misspellings;
   string word;
   istream_iterator<string> dstream(dictionary), eof;

   // first read the dictionary
   copy (dstream, eof, inserter(words, words.begin())); 

   // next read the text
   while (text >> word)
      if (! words.count(word))
         misspellings.insert(word);

   // finally, output all misspellings
   cout << "Misspelled words:" << endl;
   copy (misspellings.begin(), misspellings.end(),
      ostream_iterator<string>(cout, "\n"));
}
				

An improvement would be to suggest alternative words for each misspelling. There are various heuristics that can be used to discover alternatives. The technique we will use here is to simply exchange adjacent letters. To find these, a call on the following function is inserted into the loop that displays the misspellings.

void findMisspell(stringset & words, string & word)
{
   for (int I = 1; I < word.length(); I++) {
      swap(word[I-1], word[I]);
      if (words.count(word)) 
         cout << "Suggestion: " << word << endl;
      // put word back as before
      swap(word[I-1], word[I]);
      }
}
				

8.4 bitset 추상(abstraction)

bitset set vector cross이다. vector<bool> 같이, 추상은 이진값의 집합이다. 그러나, 논리적 bit 연산을 사용하여 bitset상에 set 연산을 수행할 있다. bitset 클래스는 원소를 접근하는데 사용할 반복자를 제공하지 않는다.

8.4.1 Include 화일

#include <bitset>

8.4.2 bitset의 선언과 초기화

bitset 템플릿 클래스이지만, 템플릿 인자가 타입이 아니라 정수값이 된다. 값은 set 포함하는 비트의 갯수를 나타낸다.

bitset<126> bset_one;        // create a set of 126 bits
				

다른 기법으로 set 사이즈를 생성자의 인자로 명시할 수도 있다. 실제 사이즈는 템플릿 인자로 사용된 값과 생성자 인자로 사용된 작은 값으로 정해진다. 이렇게 하는 것은 프로그램에서 두개 이상의 서로 다른 사이즈의 bit vector 포함하는 경우에 유용하다. 템플릿 인자로 큰값을 사용함으로써, 해당 클래스에 대한 메쏘드를 한번만 생성하게 된다. 그러나, 실제 사이즈는 생성자가 결정하게 된다.

bitset<126> bset_two(100);   // this set has only 100 elements
				

생성자의 세번째 형태는 '0' '1'이라는 문자로 이루어진 문자열을 인자로 받는다. 문자열에 속하는 문자와 같은 갯수의 원소를 가지는 bitset 만들고, 문자열에 명시된 값들로 초기화한다.

bitset<126> small_set("10101010");   // this set has 8 elements
				

8.4.3 접근과 검사

bitset 각각의 bit 첨자 연산을 사용하여 접근이 가능하다. bit 1인지 0인지를 알아보기 위해서는 test() 멤버 함수를 사용한다. bitset bit 'on'인지를 알아보기 위해서는 any() 멤버 함수를 사용하고 불값을 리턴한다. any() 반대는 none() 멤버 함수이다.

   bset_one[3] = 1;
   if (bset_one.test(4))
      cout << "bit position 4 is set" << endl;
   if (bset_one.any())
      cout << "some bit position is set" << endl;
   if (bset_one.none()) cout << "no bit position is set" << endl;
				

set() 함수는 특정 bit 세팅하는데 사용된다. 'bset_one.set(I)' 'bset_one[I] = true' 동일하다. 아무 인자 없이 함수를 호출하면 모든 비트를 'on'으로 세팅한다. reset() 함수도 사용법이 비슷한데, 인자로 명시된 위치의 비트를 'off' 세팅하고, 인자가 없으면, 모든 비트를 'off' 세팅한다. flip() 함수도 제공되는데 비트를 가리키는 레퍼런스에 대한 멤버 함수로서 제공된다.

   bset_one.flip();   // flip the entire set
   bset_one.flip(12);    // flip only bit 12
   bset_one[12].flip();     // reflip bit 12
				

size() 멤버 함수는 bitset 사이즈를 리턴하고, count() 멤버 함수는 세팅되어 있는 비트들의 갯수를 리턴한다.

8.4.4 set 연산

bitset 대한 set 연산은 bit-wise 연산자를 사용하여 구현되는데, 정수값에 대해 수행되는 방식과 비슷하다.

bitset 부정 연산자(~ 연산자) 적용하면 인자로 지정된 bitset 원소들의 반대값을 포함하는 새로운 bitset 리턴한다.

bitset 교집합은 and 연산자(& 연산자) 통해 이루어진다. 대입형태의 연산자도 가능하며, 이때 좌변이 두집합의 disjunction 된다.

   bset_three = bset_two & bset_four;
   bset_five &= bset_three;
				

집합의 합집합도 or 연산자(| 연산자) 사용하여 비슷한 방식으로 이루어진다. exclusive-or exclusive-or 연산자(^ 연산자) 사용하여 이루어진다.

왼쪽 쉬프트 연산자(<< 연산자) 오른쪽 쉬프트 연산자(>> 연산자) bitset 왼쪽 또는 오른쪽으로 쉬프트하는데 사용된다. 비트를 왼쪽으로 'n'만큼 쉬프트하면, 새로운 비트 위치 I 이전의 I-n 값이 된다(?). 새로운 위치들은 0값들로 쉬프트된다.

8.4.5 변환

to_ulong() 멤버 함수는 bitset 'unsigned long'으로 바꾼다. 많은 원소를 가지는 bitset 연산을 수행하면 에러를 발생한다.

to_string() 멤버 함수는 bitset 'string' 타입으로 바꾼다. 이때 string bitset 원소갯수만큼의 문자를 가지게 된다. 각각의 0비트는 문자 '0'으로 바꾸고, 1비트는 문자 '1' 바꾼다.


출처 : http://quartzd.tistory.com