Showing posts with label Containers. Show all posts
Showing posts with label Containers. Show all posts

Sunday, September 22, 2024

priority_queue

Overview
The standard library provides priority_queue where the highest valued element is stored first.

Details
priority_queue stores elements in priority based on the user supplied comparer. It's implementation is similar to a maxheap.

Syntax
The syntax is as below. Template parameter represents the datatype to store and Container represents the container used for storage which can be vector or deque and Comparer represents the function used for comparison of the elements.
template < class T, class Container = vector<T>, class Comparer = less<T> > 
class priority_queue;

Members
 It defines following types
NameDescription
value_typeThe first template parameter (T)
container_typeThe second template parameter (Container)
value_compareThe third template parameter (Compare)
referencevalue_type&
const_referenceconst value_type&
size_typean unsigned integral type

Operation
priority_queue can be graphically represented as below.


Internally it's stored as a heap. The highest value is the root of the heap. When new elements are added the heap is modified such that the highest value stays as root as per the Comparer argument. Similarly, when the top element is removed, the element with the next highest value will be the root element. 

Complexity
The complexity of access operation is O(1). Insertion or removal is O(N). 

Functionality

Constructors
In following constructors use default allocator. Custom allocators can be used by passing them as an additional argument.

NameDescription
priority_queue(const Comparer& cmp = Compare(),
                    const Container&& cnt=Container())
Default Constructor. Contents of cnt will be moved. 
Example:

//v:{}
priority_queue<int> v;

//v2:{1,2,3,4}
priority_queue<int,vector<int>,greater<int>> v2(greater<int>(),move(vector<int>{1,2,3,4}));

//v3:{4,2,3,1}
priority_queue<int,vector<int>> v3(less<int>(),move(vector<int>{1,2,3,4}));

//v4:{4,2,3,1}
priority_queue<int,deque<int>> v4(less<int>(),move(deque<int>{1,2,3,4}));
priority_queue(const Comparer& cmp, const Container& cnt)Constructs priority_queue based on cnt and using comparison method.
Example:

//v:{1,2,3,4}
priority_queue<int,vector<int>,greater<int>> v(greater<int>(),vector<int>{1,2,3,4});

//v2:{4,2,3,1}
priority_queue<int,vector<int>> v2(less<int>(),vector<int>{1,2,3,4});

//v3:{4,3,2,1}
priority_queue<int,deque<int>> v3(less<int>(),deque<int>{1,2,3,4});
priority_queue(InputIterator first, InputIterator last,
    const Comparer& cmp, const Container& cnt)
Constructs a priority_queue and copies the elements in the range along with the contents of cnt.
Example:

int a[]{5,6,7};

//v:{1,2,3,6,5,7,4}
priority_queue<int,vector<int>,greater<int>> v(begin(a),end(a),greater<int>(),vector<int>{1,2,3,4});

//v2:{7,6,5,1,2,3,4}
priority_queue<int,vector<int>> v2(begin(a),end(a),less<int>(),vector<int>{1,2,3,4});

//v3:{7,6,5,1,2,3,4}
priority_queue<int,deque<int>> v3(begin(a),end(a),less<int>(),deque<int>{1,2,3,4});
priority_queue(InputIterator first, InputIterator last,
    const Comparer& cmp, const Container&& cnt=Container()
Constructs a priority_queue and copies the elements in the range along with the contents of cnt.
Example:

int a[]{5,6,7};

//v:{7,6,5}
priority_queue<int> v(begin(a),end(a));

//v2:{5,6,7}
priority_queue<int,vector<int>,greater<int>> v2(begin(a),end(a));

//v3:{7,6,5}
priority_queue<int,deque<int>> v3(begin(a),end(a));

//v4:{1,2,3,6,5,7,4}
priority_queue<int,vector<int>,greater<int>> v4(begin(a),end(a),greater<int>(),move(vector<int>{1,2,3,4}));
//v5:{7,6,5,1,2,3,4}
priority_queue<int,vector<int>> v5(begin(a),end(a),less<int>(),move(vector<int>{1,2,3,4}));
//v6:{7,6,5,1,2,3,4}
priority_queue<int,deque<int>> v6(begin(a),end(a),less<int>(),move(deque<int>{1,2,3,4}));
priority_queue(const priority_queue& x)copy constructor
priority_queue (priority_queue&& x)move constructor

Capacity
NameDescription
size_type size() Returns the number of elements. Call size() method on the container.

Example:

int a[]{5,6,7};

//v:{7,6,5}
priority_queue<int> v(begin(a),end(a));

//prints 3
cout << v.size();
bool empty()Test whether container is empty. Calls isempty() method of the underlying container.

Element Access
NameDescription
reference top()Returns reference to the top element.  It represents the root of the heap.

Example:

int a[]{5,6,7};

//v:{7,6,5}
priority_queue<int> v(begin(a),end(a));

//prints 7
cout << v.top();

Modifiers
NameDescription
void push(const value_type& val)
Adds element. The underlying heap is modified.

Example:

//v:{}
priority_queue<int> v;

//v:{1}
v.push(1);

//v:{2,1}
v.push(2);

void pop()Removes the top element. The underlying heap is modified.

Example:

int a[]{5,6,7};

//v:{7,6,5}
priority_queue<int> v(begin(a),end(a));

//v:{6,5}
v.pop();

//v:{5}
v.pop();
void swap(priority_queue & v)Swaps the contents with v. Note T has to be same.

Example:

int a[]{1,2,3};
int a2[]{4,5,6};

//v:{1,2,3}
priority_queue<int> v(begin(a),end(a));

//v2:{4,5,6}
priority_queue<int> v2(begin(a2),end(a2));

//v2:{1,2,3}
//v:{4,5,6}
v2.swap(v);
void emplace(Args ...arg)Constructs and inserts element using arg. The underlying heap is modified.

Example:

//v:{}
priority_queue<int> v;

//v:{1}
v.emplace(1);

//v:{2,1}
v.emplace(2);








Saturday, September 21, 2024

queue

Overview
The standard library provides queue adapter to provide FIFO (First In First Out) operations, where elements are inserted into one end of the container and extracted from the other.

Details
queue  is basically an adapter based on associative containers such as deque or  list that provides FIFO operations  such as push_back() and pop_front().

Syntax
The syntax is as below. Template parameter represents the datatype to store and Container represents the container used for storage and operations. deque and list are possible candidates.
template < class T, class Container = deque<T> > 
class queue;

Members
 It defines following types
NameDescription
value_typeThe first template parameter (T)
container_typeThe second template parameter (Container)
referencevalue_type&
const_referenceconst value_type&
size_typean unsigned integral type

Operation
stack can be graphically represented as below.
New elements are stored in the host container. Operations such as push and pop are supported where elements are added or removed from the top.

Complexity
The complexity of push and pop operations are O(1).

Functionality

Constructors
In following constructors use default allocator. Custom allocators can be used by passing them as an additional argument.

NameDescription
queue()Default Constructor. The default container is deque.

Example:

//v:{}
queue<int> v;

//v2:{}
queue<int,list<int>> v2;
queue(const container_type& c)Construct from container c. Note that the underlying container has to be same.

Example:

//v:{1,2,3,4}
queue<int> v(deque<int>{1,2,3,4});

//v2:{1,2,3,4}
queue<int,list<int>> v2(list<int>{1,2,3,4});
queue(const queue& x)copy constructor.  Note that the underlying container has to be same.

Example:

//v:{1,2,3,4}
queue<int,list<int>> v(list<int>{1,2,3,4});

//v2:{1,2,3,4}
queue<int,list<int>> v2(v);
queue(queue&& x)move constructor.

Capacity
NameDescription
size_type size() Returns the number of elements. Calls size() method of the underlying container.

Example:

//v:{1,2,3,4}
queue<int> v(deque<int>{1,2,3,4});
//prints 4 cout << v.size();
bool empty()Test whether vector is empty. Calls empty() method of the container.

Element Access
NameDescription
reference front()Returns reference to the first element. Calls front() method of the underlying container.
  
Example:

//v:{1,2,3,4}
queue<int> v(deque<int>{1,2,3,4});

//prints 1. 
cout << v.front(); 
reference back()Returns reference to the last element. Calls back() method of the underlying container.
 
Example:

//v:{1,2,3,4}
queue<int> v(deque<int>{1,2,3,4});

//prints 4. 
cout << v.back(); 

Modifiers
NameDescription
void push(const value_type& val)
void push(value_type&& val)
Adds element at the back. Calls push_back() method of the underlying container. 

Example:

queue<int> v{};

//v:{1}
v.push(1);

//v:{1,2}
v.push(2);
void pop()Deletes the element at the front. Calls pop_front() method of the underlying container.

Example:

//v:{1,2}
queue<int> v(deque<int>{1,2});
//v:{2}
v.pop();
void swap(queue& v)Swap content with v. Note T has to be same.

Example:

queue<int> v(deque({1,2,3});
queue<int> v2(deque{4,5,6});

//v2:{1,2,3}
//v:{4,5,6}
v2.swap(v);
void emplace(Args ...arg)Construct and insert element at the back using arg. Calls emplace_back() method of the underlying container.

Example:

queue<int> v(deque{1,2,3});

//v:{1,2,3,4}
v.emplace(4);





Friday, September 20, 2024

stack

Overview
The standard library provides stack adapter to provide LIFO (Last In First Out) operations where elements are inserted and extracted only from one end of the container.

Details
stack is basically an adapter based on associative containers such as deque or vector  or list that provides  operations  such as push_back() and pop_back().

Syntax
The syntax is as below. Template parameter represents the datatype to store and Container represents the container used for storage and operations. vectordeque and list are possible candidates.
template < class T, class Container = deque<T> > 
class stack;

Members
 It defines following types
NameDescription
value_typeThe first template parameter (T)
container_typeThe second template parameter (Container)
referencevalue_type&
const_referenceconst value_type&
size_typean unsigned integral type

Operation
stack can be graphically represented as below.

New elements are stored in the host container. Operations such as push and pop are supported where elements are added or removed from the top.

Complexity
The complexity of push and pop operations are O(1).

Functionality

Constructors
In following constructors use default allocator. Custom allocators can be used by passing them as an additional argument.

NameDescription
stack()Default Constructor. Default container is deque.

Example:
//deque
//v:{}
stack<int> v;

//v2:{}
stack<int,vector<int>> v2;

//v3:{}
stack<int,list<int>> v3;
stack(const container_type& c)Constructs a container from container c.

Example:
//v:{4,3,2,1}
stack<int> v2(deque<int>{1,2,3,4});

//v2:{4,3,2,1}
stack<int,vector<int>> v2(vector<int>{1,2,3,4});

//v3:{4,3,2,1}
stack<int,list<int>> v3(list<int>{1,2,3,4});
stack(const stack& x)copy constructor. Note that underlying container should be case.

Example:
//v:{4,3,2,1}
stack<int,vector<int>> v(vector<int>{1,2,3,4});

//v2:{4,3,2,1}
stack<int,vector<int>> v2(v);
stack(stack&& x)move constructor. Note that underlying container should be case.

Example:
//v:{4,3,2,1}
stack<int,vector<int>> v(vector<int>{1,2,3,4});

//v:{}
//v2:{4,3,2,1}
stack<int,vector<int>> v2(move(v));

Capacity
NameDescription
size_type size() Returns the number of elements by calling size() method of the container.
Example:
//v:{4,3,2,1}
stack<int> v(deque<int>{1,2,3,4});
//prints 4 cout << v.size();
bool empty()Tests whether stack is empty by calling empty() method of the container .

Element Access
NameDescription
reference top()Returns reference to the last element by calling back() method on container.  
Example:
//v:{4,3,2,1}
vector<int> v{1,2,3,4};

//prints 4. 
cout << v.top(); 

Modifiers
NameDescription
void push(const value_type& val)
void push(value_type&& val)
Adds element at the top by calling push_back() method of the container.

Example:
stack<int> v{};
//v:{1}
v.push(1);
void pop()Deletes the element at the top by calling pop_back() method of the container.

Example:
stack<int> v{1};
//v:{}
v.pop();
void swap(stack& v)Swap content with v. Note T has to be same.

Example:
stack<int> v{1,2,3};
stack<int> v2{4,5,6};
//v2:{3,2,1}
//v:{6,5,4}
v2.swap(v);
void clear()Clears the contents.

Example:
vector<int> v{1,2,3,4,5};
//v:{}
v.clear();
void emplace(Args ...arg)Constructs and inserts element at the top using arg by calling emplace_back() method of the container.

Example:
stack<int> v{1,2,3,4};
//v:{5,4,3,2,1}
v.emplace(5);

Monday, September 16, 2024

unordered_multimap

Overview
The standard library provides unordered_multimap to store key value pairs in a hash table where keys are unsorted and can have duplicates.

Details
unordered_multimap is basically a unsorted hash table of  elements where each  element is a key, value pair. The keys can be duplicate, that can be used to search and retrieve elements fast. elements are stored in buckets using the hash function provided as the template parameter. 
A bucket is a slot in the container's internal hash table to which elements are assigned based on the hash value of the key. The equal_to template parameter represents the compare function to check equality of two keys.

Syntax
The syntax is as below. Template parameter Key represents the datatype to store, Hash represents hash function object to generate, Pred represents comparison function object  to compare two Key data types and Alloc represents the allocator used for storage.
template <class Key, class Value, class Hash = hash<Key>, class Pred = equal_to<Key>, class Alloc = allocator<Key>> 
class unorered_multimap;

Members
 It defines following types
NameDescription
key_typeThe first template parameter (Key)
mapped_typeThe second template parameter (Value)
value_typepair<Key,Value>
hasherThe second template parameter (Hash)
key_equalThe third template parameter (Pred)
allocator_typeThe third template parameter (Alloc)
referencevalue_type&
const_referenceconst value_type&
pointerallocator_traits<allocator_type>::pointer
const_pointerallocator_traits<allocator_type>::const_pointer
iteratorforward iterator to value_type convertible to const_iterator
const_iteratorforward iterator to const value_type
local_iteratorSame as iterator. This iterator can be used to iterate through a single bucket but not across buckets.
const_local_iteratorSame as const_iterator. This iterator can be used to iterate through a single bucket but not across buckets.
difference_typea signed integral type
size_typean unsigned integral type

Operation
unordered_multimap can be graphically represented as below. It's basically a hash table. It holds values [{1,100}, {2,200}, {3,300}, {4,400}, {5,500}, {3,350}]. Notice key value 3 is repeated with different values.

New elements can be added or removed or searched in the unordered_multimap.  A hashtable is internally used for storage. 
As shown above, a hashtable basically comprises of multiple  buckets where the elements are stored. The hash function determines to which bucket the element will go. A bucket can contain multiple elements in such case, equal_to function is used to determine correct element.
The hashtable can be created with a user defined value. Otherwise a default value is used. Similarly custom hash and equal_to functions can be supplied. Otherwise default hash and equal_to function objects are used for hashing and equality checks.
The load_factor influences the probability of collision in the hash table (i.e., the probability of two elements being located in the same bucket). load_factor is calculated as 
load_factor = size bucket_count.
The container automatically increases the number of buckets to keep the load factor below  max_load_factor, causing a rehash each time an expansion is needed. This will invalidate iterators.
reserve can be used allocate more buckets.

Complexity
The cost of Insertion or removal or search is O(1).

Functionality

Constructors
In following constructors use default values. Custom number of buckets, custom hash, custom compare operations, custom allocators can be supplied  by passing them as an additional arguments. 

NameDescription
unordered_multimap ()Default Constructor.

Example:
//v:
unordered_multimap<int,int> v;
unordered_multimap (InputIterator first, InputIterator last)Constructs a set and copies the elements in the range.

Example:
pair<int,int> a[]{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v(begin(a),end(a));
unordered_multimap (const unordered_map & x)copy constructor.
unordered_multimap (unordered_map && x)move constructor.
unordered_multimap (initializer_list<value_type> il)initializer_list constructor.

Example:
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};

Iterator
NameDescription
iterator begin()
iterator end()

Returns iterator to beginning and end of the unordered_multimap.

Example:
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
for (auto itr=v.begin(); itr!=v.end(); ++itr)
    cout << *itr << ' ';
const_iterator cbegin()
const_iterator cend()
Returns const_iterator to beginning and end of the unordered_multimap.

Example:
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
for (auto itr=v.cbegin(); itr!=v.cend(); ++itr)
    cout << *itr << ' ';

Capacity
NameDescription
size_type size() Returns the number of elements in the unordered_multimap.

Example:
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};

//prints 6
cout << v.size();
size_type max_size() Returns maximum  possible number of elements possible. A very large number.
bool empty()Test whether unordered_multimap is empty.

Element Access
NameDescription
iterator find(const value_type& val)Returns iterator to the first element or end if not found.

Example:
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};


//itr = v.begin()+4
auto itr = v.find(2);

//itr = v.begin()+2
itr = v.find(3);

//itr = v.end()
itr = v.find(6);
size_type count (const value_type& val)Returns number of elements matching val.

Example:
//[5,500] [4,400] [3,300] [3,350] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};

//knt=1
auto knt =v.count(2);

//knt=2
knt =v.count(3);
//knt=0 knt =v.count(6);
pair<iterator,iterator>  equal_range(const value_type& val)Returns a pair object containing lower_bound and upper_bound iterators matching value val.

Example:
[5,500] [4,400] [3,350] [3,300] [2,200] [1,100] 
unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};

//p: {v.begin()+2,v.begin()+4}
auto p = v.equal_range(
3); //p: {v.begin()+5,v.end()}
p = v.equal_range(1);

//p: {v.end(),v.end()}
p = v.equal_range(6);


Modifiers
NameDescription
  1. void insert(InputIterator first, InputIterator last)
  2. iterator insert(const_iterator hintpos, const value_type& val)
  3. pair<iterator,bool>  insert(const value_type& val)
  4. void insert(initializer_list<value_type> il)

  1. Inserts the  elements in the range.
  2. Inserts an element initialized with val. hintpos, is used to scout for location.  Returns an iterator to the inserted element or existing element.
  3. Insert an element initialized with with val.  Returns an iterator to the inserted element.
  4. Insert elements with the contents of initializer_list.
    Example:

    pair<int,int> a[]{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
    unordered_multimap<int,int> v; //(1) // v:[3,300] [2,200] v.insert(begin(a)+1,begin(a)+3); //(2) // v:[3,300] [3,350] [2,200] //it=begin(v)+1 auto it = v.insert(v.begin(),{3,350}); //(3) // v:[1,100] [3,300] [3,350] [2,200] //it:begin(v) it = v.insert({1,100}); //(4) // v:[4,400] [1,100] [3,300] [3,350] [2,200] v.insert({4,400});
    1. iterator erase(const_iterator pos)
    2. iterator erase(const_iterator first, const_iterator last)
    3. size_type erase( const value_type& val)


    1. Erases element at pos. Returns an iterator that points to the element that was  located after the last erased element.
    2. Erases elements in the range. Returns an iterator that points to the element that was  located after the last erased element.
    3. Erases the elements of value val. Returns number of elements erased.
      Example:

      //v:[5,500] [4,400] [3,350] [3,300] [2,200] [1,100]
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};

      //(1) // v:[4,400] [3,350] [3,300] [2,200] [1,100]
      //it = begin(v) auto it = v.erase(cbegin(v)); //(2) // v:[4,400] [3,350] [3,300]
      //it = begin(v)+3 it = v.erase(begin(v)+3,cend(v)); //(3) // v:[4,400] //knt=2 auto knt = v.erase(3);
      void swap(unordered_multimap& v)Swaps content with v. Note T has to be same.

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200}, {3,300}};
      unordered_multimap<int,int> v2{{4,400},{5,500}};
      
      //v2:[3,300] [2,200] [1,100]
      //v:[5,500] [4,400]
      v2.swap(v);
      void clear()Clears the contents and resets size to 0.

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
      //v:{}
      v.clear();
      iterator emplace(Args ...arg)Constructs and inserts an element using arg. Returns an iterator to the inserted element.

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500}};

      //v:[1,100] [2,200] [3,350] [3,300] [4,400] [5,500]  
      //itr: v.begin()+2
      auto itr = v.emplace(make_pair(3,350));
      //v:[6,600] [1,100] [2,200] [3,350] [3,300] [4,400] [5,500]
      //itr: v.begin()
      itr = v.emplace(make_pair(6,600));
      iterator emplace_hint(iterator hintpos, Args ...arg))Constructs and attempts to insert an element at the hintpos using arg. It returns an iterator to the new element. Note that the hintpos is used as starting point to scout to add new element. It  will go round robin if the scout is exhausted.

      Example:
      unordered_multimap<int,int> v({{1,100},{2,200},{3,300},{4,400},{5,500}});
      
      //v:[1,100] [2,200] [3,350] [3,300] [4,400] [5,500] 
      //itr:v.begin()+2 auto itr = v.emplace_hint(v.begin(),make_pair(3,350)); //v:[6,600] [1,100] [2,200] [3,350] [3,300] [4,400] [5,500]
      //itr:v.begin()
      itr = v.emplace_hint(v.end(),make_pair(6,600));



      Buckets
      NameDescription
      size_type bucket_count() Returns the number of buckets in the unordered_multimap.

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
      //prints 7
      cout << v.bucket_count();
      size_type max_bucket_count() Returns maximum  possible number of buckets possible. A very large number.
      size_type  bucket_size(size_type  n)Returns number of elements in the current bucket.

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
      vector<int> v2; for_each(v.begin(), v.end(), [&v2](auto kv){v2.push_back(kv.first);}); unique(v2.begin(),v2.end()); cout << "bucket\tbucket_size" << endl; for ( auto i:v2) cout << v.bucket(i) << "\t" << v.bucket_size(v.bucket(i)) << endl; /* bucket bucket_size 5 1 4 1 3 2 2 1 1 1 1 1 */
      size_type  bucket(const key_type key)Returns the bucket number where the key is located.

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
      cout << "key\tbucket" << endl;
      for ( auto itr=v.begin(); itr !=  v.end(); ++itr)
          cout << setw(3) << setfill('0') <<  right <<  itr->first 
              << '\t' << dec << v.bucket(itr->first) << endl;
      /*
      key	bucket
      005	5
      004	4
      003	3
      003	3
      002	2
      001	1
      */

      Hash Policy
      NameDescription
      size_type load_factor() 
      Returns the current load factor. It needs to stay under  or equal to max_load_factor().

      Example:
      unordered_multimap<int,int> v{{1,100},{2,200},{3,300},{4,400},{5,500},{3,350}};
      cout << "size = \t\t" << v.size() << endl;
      cout << "bucket_count = \t" << v.bucket_count() << endl << endl;
      cout << "load_factor = size() / bucket_count()" << endl << endl;
      cout << "load_factor = \t\t" << v.load_factor() << endl;
      cout << "max_load_factor = \t" << v.max_load_factor() << endl;
      
      /*
      size = 		6
      bucket_count = 	7
      
      load_factor = size() / bucket_count()
      
      load_factor = 		0.857143
      max_load_factor = 	1
      */
      1. float max_load_factor() 
      2. void max_load_factor(float z)
      1. Returns current maximum  load factor 
      2. Sets current maximum  load factor 
       Example:
         float amlf[]{1.0f,0.5f};
          for (auto clf :amlf)
          {
              unordered_multimap<char,int> v;
              v.max_load_factor (clf);
              for (auto c='a'; c <= 'z'; ++c)
                  v.emplace(make_pair(c,c));
        
              cout << "max_load_factor: " << v.max_load_factor() << endl;
              cout << "size: " << v.size() << endl;
              cout << "bucket_count: " << v.bucket_count() << endl;
              cout << "load_factor: " << v.load_factor() << endl;
              cout << endl;
          }
      /*
      max_load_factor: 1
      size: 26
      bucket_count: 29
      load_factor: 0.896552
      
      max_load_factor: 0.5
      size: 26
      bucket_count: 97
      load_factor: 0.268041
      */
      void reserve(size_type  n)
      Sets the number of buckets in the container to hold at least  n elements.

      Example:
      unordered_multimap<int,int> v;
      cout <<   "bucket_count before rehash: "  << v.bucket_count() << endl;
      v.reserve(10);
      cout <<   "bucket_count after rehash: "  << v.bucket_count() << endl;
      
      /*
      output:
      bucket_count before reserve: 1
      bucket_count after reserve: 11
      */

      void rehash(size_type  n)Sets the number of buckets in the container to n or more.

      Example:
      unordered_multimap<int,int> v;
      
      cout <<   "bucket_count before rehash: "  << v.bucket_count() << endl;
      v.rehash(10);
      cout <<   "bucket_count after rehash: "  << v.bucket_count() << endl;
      
      /*
      output:
      bucket_count before rehash: 1
      bucket_count after rehash: 11
      */