Power up C++ with STL: Part III (more on STL)

I strongly recommend you to read the two articles below first.
Power up C++ with STL: Part I (introduction, vector)
Power up C++ with STL: Part II (string, set, map)

Creating Vector from Map
As you already know, map actually contains pairs of element. So you can write it in like this:
 
 
 map < string int >  M; 
 
//  ... 
 vector <  pair < string int >   >  V(all(M));  //  remember all(c) stands for 
 (c).begin(),(c).end()
Now vector will contain the same elements as map. Of course, vector will be sorted, as is map. This feature may be useful if you are not planning to change elements in map any more but want to use indices of elements in a way that is impossible in map.

Copying data between containers
Let's take a look at the copy(...) algorithm. The prototype is the following:
 
 
 copy(from_begin, from_end, to_begin);

This algorithm copies elements from the first interval to the second one. The second interval should have enough space available. See the following code:
 
 
 vector < int >  v1;
 vector
< int >  v2; 
  
 
//  ... 
  
 
//  Now copy v2 to the end of v1
 v1.resize(v1.size()  +  v2.size()); 
 
//  Ensure v1 have enough space 
 copy(all(v2), v1.end()  -  v2.size()); 
 
//  Copy v2 elements right after v1 ones 


 
Another good feature to use in conjunction with copy is inserters. I will not describe it here due to limited space but look at the code:
 
 
 vector < int >  v; 
 
//  ... 
  set < int >  s; 
 
//  add some elements to set
 copy(all(v), inserter(s)); 


The last line means:
 
 
 tr(v, it) 
// remember traversing macros from Part I
      s.insert(*it); 
 }

But why use our own macros (which work only in gcc) when there is a standard function? It’s a good STL practice to use standard algorithms like copy, because it will be easy to others to understand your code.

To insert elemements to vector with push_back use back_inserter, or front_inserter is available for deque container. And in some cases it is useful to remember that the first two arguments for ‘copy’ may be not only begin/end, but also rbegin/rend, which copy data in reverse order.

Merging lists
Another common task is to operate with sorted lists of elements. Imagine you have two lists of elements -- A and B, both ordered. You want to get a new list from these two. There are four common operations here:
  • 'union' the lists, R = A+B
  • intersect the lists, R = A*B
  • set difference, R = A*(~B) or R = A-B
  • set symmetric difference, R = A XOR B
STL provides four algorithms for these tasks: set_union(...), set_intersection(...), set_difference(...) and set_symmetric_difference(...). They all have the same calling conventions, so let's look at set_intersection. A free-styled prototype would look like this:
 
 
end_result  =  set_intersection(begin1, end1, begin2, end2, begin_result);

Here [begin1,end1) and [begin2,end2) are the input lists. The 'begin_result' is the iterator from where the result will be written. But the size of the result is unknown, so this function returns the end iterator of output (which determines how many elements are in the result). See the example for usage details:
int  data1[]  =   12568910 }
int  data2[]  =   02347810 }
 
vector
< int >  v1(data1, data1 + sizeof (data1) / sizeof (data1[ 0 ]));
vector
< int >  v2(data2, data2 + sizeof (data2) / sizeof (data2[ 0 ])); 
 
vector
< int >  tmp(max(v1.size(), v2.size()); 
 
vector
< int >  res  =  vector < int >  (tmp.begin(), set_intersection(all(v1), all(v2), tmp.begin());


Look at the last line. We construct a new vector named 'res'. It is constructed via interval constructor, and the beginning of the interval will be the beginning of tmp. The end of the interval is the result of the set_intersection algorithm. This algorithm will intersect v1 and v2 and write the result to the output iterator, starting from 'tmp.begin()'. Its return value will actually be the end of the interval that forms the resulting dataset.

One comment that might help you understand it better: If you would like to just get the number of elements in set intersection, use int cnt = set_intersection(all(v1), all(v2), tmp.begin()) – tmp.begin();

Actually, I would never use a construction like ' vector<int> tmp'. I don't think it's a good idea to allocate memory for each set_*** algorithm invoking. Instead, I define the global or static variable of appropriate type and enough size. See below:
 
 
set < int >  s1, s2; 
for ( int  i  =   0 ; i  <   500 ; i ++
        s1.insert(i
*(i+1% 1000); 
        s2.insert(i
*i*% 1000); 
}
 
 
static   int  temp[ 5000 ];  //  greater than we need 
 
vector
< int >  res  =  vi(temp, set_symmetric_difference(all(s1), all(s2), temp)); 
int  cnt  =  set_symmetric_difference(all(s1), all(s2), temp) – temp;


 
Here 'res' will contain the symmetric difference of the input datasets.

Remember, input datasets need to be sorted to use these algorithms. So, another important thing to remember is that, because sets are always ordered, we can use set-s (and even map-s, if you are not scared by pairs) as parameters for these algorithms.

These algorithms work in single pass, in O(N1+N2), when N1 and N2 are sizes of input datasets.

Calculating Algorithms
Yet another interesting algorithm is accumulate(...). If called for a vector of int-s and third parameter zero, accumulate(...) will return the sum of elements in vector:
 
 
vector < int >  v; 
//  ... 
int  sum  =  accumulate(all(v),  0 );

The result of accumulate() call always has the type of its third argument. So, if you are not sure that the sum fits in integer, specify the third parameter's type directly:
 
 
vector < int >  v; 
//  ... sum= 0 + v[0]+..+v[n-1]
long   long  sum  =  accumulate(all(v), ( long   long ) 0 );  


Accumulate can even calculate the product of values. The fourth parameter holds the predicate to use in calculations. So, if you want the product:
 
 
vector < int >  v; 
//  ... product= 1*v[0]*v[1]...v[n-1]
double  product  =  accumulate(all(v),  double ( 1 ), multiplies < double > ()); 
//  don’t forget to start with 1 !


Another interesting algorithm is inner_product(...). It calculates the scalar product of two intervals. For example:
 
 
vector < int >  v1; 
vector
< int >  v2; 
for ( int  i  =   0 ; i  <   3 ; i ++
      v1.push_back(
10-i); 
      v2.push_back(i
+1); 
}
 
int  r  =  inner_product(all(v1), v2.begin(),  0 ); 


'r' will hold (0+v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]), or (10*1+9*2+8*3), which is 52.

As for ‘accumulate’ the type of return value for inner_product is defined by the last parameter. The last parameter is the initial value for the result. So, you may use inner_product for the hyperplane object in multidimensional space: just write inner_product(all(normal), point.begin(), -shift).

It should be clear to you now that inner_product requires only increment operation from iterators, so queues and sets can also be used as parameters. Convolution filter, for calculating the nontrivial median value, could look like this:
 
 
set < int >  values_ordered_data(all(data));
int  n  =  sz(data);  //  int n = int(data.size());
vector < int >  convolution_kernel(n);
for ( int  i  =   0 ; i  <  n; i ++ {
     convolution_kernel[i] 
= (i+1)*(n-i);
}

double  result  =   double (inner_product(all(ordered_data),
convolution_kernel.begin(), 
0 ))  /  accumulate(all(convolution_kernel),  0 );

Of course, this code is just an example -- practically speaking, it would be faster to copy values to another vector and sort it.

It's also possible to write a construction like this:
 
 
vector < int >  v; 
//  ... 
int  r  =  inner_product(all(v), v.rbegin(),  0 ); 


 
This will evaluate V[0]*V[N-1] + V[1]+V[N-2] + ... + V[N-1]*V[0] where N is the number of elements in 'v'.

Nontrivial Sorting
Actually, sort(...) uses the same technique as all STL:
  • all comparison is based on 'operator <'
This means that you only need to override 'operator <'. Sample code follows:
 
 
struct  fraction 
      
int n, d; // (n/d) 
      
// ... 
      bool operator < (const fraction& f) const 
           
if(false
                
return (double(n)/d) < (double(f.n)/f.d); 
                
// Try to avoid this, you're the TopCoder! 
           }
 
           
else 
                
return n*f.d < f.n*d; 
           }
 
      }
 
 }

  
 
//  ... 
  
 vector
< fraction >  v; 
  
 
//  ... 
  
 sort(all(v));  


In cases of nontrivial fields, your object should have default and copy constructor (and, maybe, assignment operator -- but this comment is not for TopCoders).

Remember the prototype of 'operator <' : return type bool, const modifier, parameter const reference.

Another possibility is to create the comparison functor. Special comparison predicate may be passed to the sort(...) algorithm as a third parameter. Example: sort points (that are pair<double,double>) by polar angle.
 
 
typedef pair < double double >  dd; 

const   double  epsilon  =  1e - 6 ;
 
struct  sort_by_polar_angle 
       dd center; 
       
// Constuctor of any type 
       
// Just find and store the center 
       template<typename T> sort_by_polar_angle(T b, T e) 
            
int count = 0;
            center 
= dd(0,0); 
            
while(b != e) 
                        center.first 
+= b->first;
                        center.second 
+= b->second;
                   b
++
                count
++;
            }
 
                   
double k = count ? (1.0/count) : 0;
            center.first 
*= k;
                   center.second 
*= k;
       }
 
 
// Compare two points, return true if the first one is earlier 
 
// than the second one looking by polar angle 
 
// Remember, that when writing comparator, you should 
 
// override not ‘operator <’ but ‘operator ()’ 
       bool operator () (const dd& a, const dd& b) const 
            
double p1 = atan2(a.second-center.second, a.first-center.first); 
            
double p2 = atan2(b.second-center.second, b.first-center.first); 
            
return p1 + epsilon < p2; 
       }
 
 }

  
 
//  ... 
  
 vector
< dd >  points; 
  
 
//  ... 
 
       sort(all(points), sort_by_polar_angle(all(points))); 


This code example is complex enough, but it does demonstrate the abilities of STL. I should point out that, in this sample, all code will be inlined during compilation, so it's actually really fast.

Also remember that 'operator <' should always return false for equal objects. It's very important – for the reason why, see the next section.

Using your own objects in Maps and Sets
Elements in set and map are ordered. It's the general rule. So, if you want to enable using of your objects in set or map you should make them comparable. You already know the rule of comparisons in STL:

| * all comparison is based on 'operator <'

Again, you should understand it in this way: "I only need to implement operator < for objects to be stored in set/map."

Imagine you are going to make the 'struct point' (or 'class point'). We want to intersect some line segments and make a set of intersection points (sound familiar?). Due to finite computer precision, some points will be the same while their coordinates differ a bit. That's what you should write:
 
 
const   double  epsilon  =  1e - 7
 
 
struct  point 
       
double x, y; 
  
       
// ... 
  
  
       
// Declare operator < taking precision into account 
       bool operator < (const point& p) const 
            
if(x < p.x - epsilon) return true
            
if(x > p.x + epsilon) return false
            
if(y < p.y - epsilon) return true
            
if(y > p.y + epsilon) return false
            
return false
       }
 
 }
;  


Now you can use set<point> or map<point, string>, for example, to look up whether some point is already present in the list of intersections. An even more advanced approach: use map<point, vector<int> > and list the list of indices of segments that intersect at this point.

It's an interesting concept that for STL 'equal' does not mean 'the same', but we will not delve into it here.

Memory management in Vectors
As has been said, vector does not reallocate memory on each push_back(). Indeed, when push_back() is invoked, vector really allocates more memory than is needed for one additional element. Most STL implementations of vector double in size when push_back() is invoked and memory is not allocated. This may not be good in practical purposes, because your program may eat up twice as much memory as you need. There are two easy ways to deal with it, and one complex way to solve it.

The first approach is to use the reserve() member function of vector. This function orders vector to allocate additional memory. Vector will not enlarge on push_back() operations until the size specified by reserve() will be reached.

Consider the following example. You have a vector of 1,000 elements and its allocated size is 1024. You are going to add 50 elements to it. If you call push_back() 50 times, the allocated size of vector will be 2048 after this operation. But if you write
 
 
v.reserve( 1050 ); 


before the series of push_back(), vector will have an allocated size of exactly 1050 elements.

If you are a rapid user of push_back(), then reserve() is your friend.

By the way, it’s a good pattern to use v.reserve() followed by copy(…, back_inserter(v)) for vectors.

Another situation: after some manipulations with vector you have decided that no more adding will occur to it. How do you get rid of the potential allocation of additional memory? The solution follows:
 
 
vector < int >  v; 
//  ... 
vector < int > (all(v)).swap(v);  


This construction means the following: create a temporary vector with the same content as v, and then swap this temporary vector with 'v'. After the swap the original oversized v will be disposed. But, most likely, you won’t need this during SRMs.

The proper and complex solution is to develop your own allocator for the vector, but that's definitely not a topic for a TopCoder STL tutorial.

width="728" scrolling="no" height="90" frameborder="0" align="middle" src="http://download1.csdn.net/down3/20070601/01184120111.htm" marginheight="0" marginwidth="0">
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值