Monday, June 17, 2019

Title: Leveraging C++ STL for Data Processing: A Deep Dive

The C++ Standard Library (STL) provides a set of common classes and interfaces, which includes a suite of algorithms, containers, and iterators that make data processing more efficient and readable. This blog post will walk you through two examples that showcase the power of STL and its different components like map, priority_queue, tuple, and pair.


Example 1: Word Frequency Count Using STL

In this example, we use STL to count the frequency of words in a vector of strings and print them in descending order.

The map STL is used to store each word (string) and its corresponding count (int). This container provides a convenient and efficient way to keep track of the frequency of each word. We then use a priority_queue to sort the words based on their frequency. The priority_queue is implemented as a heap where the top element is always the greatest, thus providing an efficient way to get the most frequent word. We use a tuple to store the count (frequency), word, and a pair of both for easier access.

The function wfreq(vector<string> &sv) takes a vector of strings as an argument, counts the frequency of each word using a map, and then stores the count and word in a priority_queue. The function finally returns a vector of pairs where each pair contains the word and its frequency.


#include<queue> 
#include<vector> 
#include<string> 
#include<map> 
#include<iostream> 
#include<tuple> 
 
 
using namespace std; 
 
vector<pair<int, string>> wfreq(vector<string> &sv)  
{ 
     vector<pair<int, string>> o; 
     map<string, int> m; 
     priority_queue<tuple<int, string, pair<int, string>>> pq; 
     for (auto x: sv)  m[x]++; 
     for (auto x: m) pq.push(make_tuple(x.second, x.first, make_pair(x.second, x.first))); 
     while(!pq.empty()) { 
        o.push_back(get<2>(pq.top())); pq.pop(); 
     } 
     return o; 
} 
 
int main(void) 
{ 
    vector<string> sv = { "abc", "bca","xyz", "xyz", "abc", "bcc", "abc" }; 
    vector<pair<int, string>> o = wfreq(sv); 
    for(auto x: o) cout << x.first <<","<<  x.second << endl; 
    return 0; 
}

Or Simply

 
vector<pair<int, string>> wfreq(vector<string> &sv)  
{ 
   map<string, int> mp;
   for ( auto &x: sv) mp[x] ++;
   vector<pair<string,int>> pv(mp.begin(), mp.end());
   sort(pv.begin(), pv.end(), 
        [](pair<string, int> &a, pair<string, int> &b)
        {     return a.second > b.second;            }
        );
    return pv;
}
     

Example 2: Student Ranking System Using STL and Functors

In this example, we create a student ranking system where we can sort students based on different fields like name and rank.

We create a Student class that contains information about the student's name, math score, physics score, total score, and rank. We then define a CmpFunctor class which is a virtual binary functor. This base functor is used to define two derived functors NameCmpFunctor and RankCmpFunctor that compare two Student objects based on the name and rank fields, respectively.

We then define two functions orderedbyNameField(vector<Student> &sc) and orderbyRankField(vector<Student> &sc, RankCmpFunctor &cf). The first function sorts the students based on their names, and the second function sorts the students based on their ranks. Both functions use a priority_queue to sort the students, and the functors defined earlier are used as the comparison function objects for the priority_queue.

In the main function, we create a vector of Student objects, sort them using the two functions defined earlier, and then print the sorted list of students.

#include<iostream>
#include<vector>
#include<string>
#include<queue>

using namespace std;

class Student{
    public:
 string name; 
 int math; 
 int phy; 
 int total;
 int rank; 
};

// Here we defined a virtual binary functor and its derived classes:
class CmpFunctor{
public:
       bool reverse = false;
       CmpFunctor(bool rev):reverse(rev){};
       virtual bool operator() (const Student &left, const Student &right) = 0;  
};

class NameCmpFunctor: public CmpFunctor{

public:
      NameCmpFunctor(bool rev=false):CmpFunctor(rev){};
      virtual bool operator() (const Student &left, const Student &right) {
          if (reverse == true) return left.name > right.name;  
          else return left.name < right.name;
      };
};

class RankCmpFunctor: public CmpFunctor{

public:
      RankCmpFunctor(bool rev=false):CmpFunctor(rev){};
      virtual bool operator() (const Student &left, const Student &right) {
          if (reverse == true ) return left.rank > right.rank;  
          else return left.rank < right.rank; 
      };
};

vector<Student> orderedbyNameField(vector<Student> &sc)
{
      vector<Student> st;
      // here to initialize functor with name reverse order is true
      priority_queue<Student, vector<Student>, NameCmpFunctor> pq(NameCmpFunctor(true));  
      for (auto x:  sc)   pq.push(x);
      while(!pq.empty()) {
          auto x = pq.top();
          st.push_back(x); 
          pq.pop();
      }
      return st;  
}

vector<Student> orderbyRankField(vector<Student> &sc, RankCmpFunctor &cf)
{
      vector<Student> st;
      priority_queue<Student, vector<Student>, RankCmpFunctor> pq(cf);
      for (auto x:  sc)   pq.push(x);
      while(!pq.empty()) {
          auto x = pq.top();
          st.push_back(x); 
          pq.pop();
      }
      return st;  
}


int main(void)
{
vector<Student> sc;

sc.push_back({"Ammy", 91, 85, 176, 1});
sc.push_back({"Claus", 92, 83,  175, 2});
sc.push_back({"Ann", 90, 80, 170, 3});
sc.push_back({"Bob",85, 80, 165, 4});

vector<Student> scn = orderedbyNameField(sc);
for (auto x: scn) {
cout << x.name <<","<< x.math <<"," << x.phy <<"," << x.total <<","<< x.rank << endl;
}
cout << endl;

RankCmpFunctor cf(true); // create a functor with reverse rank order is true
vector<Student> ns = orderbyRankField(sc, cf);

for (auto x: ns) {
cout << x.name <<","<< x.math <<"," << x.phy <<"," << x.total <<","<< x.rank << endl;
}
cout << endl;

RankCmpFunctor cfa(false);
vector<Student> nsa = orderbyRankField(sc, cfa); // create a functor with reverse rank order is false

for (auto x: nsa) {
cout << x.name <<","<< x.math <<"," << x.phy <<"," << x.total <<","<< x.rank << endl;
}

return 0;
}

Key Takeaway

These examples illustrate the power and flexibility of C++ STL in data processing. Whether you're working with simple data types or complex user-defined objects, STL provides efficient and easy-to-use tools to manage and process your data. The use of functors further enhances this ability, giving you the power to define custom comparison or operation methods that can be used with STL components.

Stay tuned for more deep dives into the world of C++ and happy coding!

Wednesday, August 29, 2018

Understanding Smart Pointers and Custom Deleters in C++


In C++, smart pointers are a feature of the Standard Template Library (STL) that provide the functionality of pointers while also managing memory. They automatically deallocate memory when they're no longer needed, preventing memory leaks. Two common types of smart pointers are std::unique_ptr and std::shared_ptr.

In this article, we will delve into these two types of smart pointers, with a focus on the unique feature of custom deleters, using an investment management example.

Unique Pointers

The std::unique_ptr is a smart pointer that exclusively owns and manages another object through a pointer and disposes of that object when the unique_ptr is destroyed. A unique_ptr cannot be copied, but it can be moved to transfer ownership of the managed object.

In the context of our example, we have created several classes representing different types of investments (Investment, Stock, Bond, and RealEstate).

We have also defined lambda functions CancelInvestment, CancelStock, CancelBond, and CancelRealEstate as custom deleters.
In the uniqueMakeInvestment function, we create a unique_ptr that uses a custom deleter (CancelInvestment) to manage instances of Investment classes.

A unique_ptr with a custom deleter has the type of the deleter as part of its type. It's important to note that the deleter does not change the size of the unique_ptr.

Shared Pointers

A std::shared_ptr is a smart pointer that retains shared ownership of an object. Multiple shared_ptr pointers can point to the same object, and the object is destroyed when the last shared_ptr is destroyed or goes out of scope.

The sharedMakeInvestment function is similar to the uniqueMakeInvestment function, but it uses shared_ptr instead of unique_ptr.

However, unlike unique_ptr, the custom deleter is not part of the type of shared_ptr. This allows more flexibility, as different shared_ptr instances managing the same object can have different custom deleters.

In the main function, both types of smart pointers are used:

In this way, the C++ language provides powerful tools for managing memory and object lifetimes with smart pointers, and even allows customization of the deletion process with custom deleters. This is particularly useful in complex applications where memory management is crucial for performance and stability.

#include<iostream>
#include<memory>

using namespace std;

class Investment {
    public:
        Investment() { cout << "Investment( " << this << ")" <<endl; }
        virtual void Type() const { cout << "Investment Type " << endl; }
        virtual ~Investment() { cout << "~Investment( " << this << ")" <<endl; }

};    
class Stock:public Investment {
    public:
        Stock() { cout << "Stock( " << this << ")" <<endl; }
        virtual void Type() const override { cout << "Stock Type " << endl; }
        virtual ~Stock() { cout << "~Stock( " << this << ")" <<endl; }
};

class Bond:public Investment {
    public:
        Bond() { cout << "Bond( " << this << ")" <<endl; }
        virtual void Type() const override { cout << "Bond Type " << endl; }
        virtual ~Bond() { cout << "~Bond( " << this << ")" <<endl; }
};

class RealEstate: public Investment {
    public:
        RealEstate() { cout << "RealEstate( " << this << ")" <<endl; }
        virtual void Type() const override{ cout << "RealEstate Type " << endl; }
        virtual ~RealEstate() { cout << "~RealEstate( " << this << ")" <<endl; }
};

auto CancelInvestment = [] (Investment *iv) { cout << "Transfer Investment: (" << iv << ")"  << endl; 
    delete iv;
};
auto CancelStock = [] (Investment *iv) { cout << "Sale Stock: (" << iv << ")"  << endl; 
    delete iv;
};
auto CancelBond = [] (Investment *iv) { cout << "Sale Bond: (" << iv << ")"  << endl; 
    delete iv;
};
auto CancelRealEstate = [] (Investment *iv) { cout << " Rent RealEstate: (" << iv << ")"  << endl; 
    delete iv;
};

template<typename T>
unique_ptr<Investment, decltype(CancelInvestment)>  uniqueMakeInvestment( const T type)
{
    switch (type)
    {
        case 1: {
                    unique_ptr<Investment, decltype(CancelInvestment)>Inv(new Stock, CancelInvestment);
                    return Inv;
                }
        case 2: {
                    unique_ptr<Investment, decltype(CancelInvestment)> Inv(new Bond, CancelInvestment);
                    return Inv;
                }
        case 3: {
                    unique_ptr<Investment, decltype(CancelInvestment)> Inv(new RealEstate, CancelInvestment);
                    return Inv;
                }
        default: {
                     unique_ptr<Investment, decltype(CancelInvestment)> Inv(new Investment, CancelInvestment);
                     return Inv;
                 }
    }
}



template<typename T>
shared_ptr<Investment> sharedMakeInvestment( const T type)
{
    switch (type)
    {
        case 1: {
                    shared_ptr<Investment>Inv(new Stock, CancelStock);
                    return Inv;
                }
        case 2: {
                    shared_ptr<Investment> Inv(new Bond, CancelBond);
                    return Inv;
                }
        case 3: {
                    shared_ptr<Investment> Inv(new RealEstate, CancelRealEstate);
                    return Inv;
                }
        default: {
                     shared_ptr<Investment> Inv(new Investment, CancelInvestment);
                     return Inv;
                 }
    }
}


int main(void)
{
    auto ptrA = move(uniqueMakeInvestment<int>(1));
    auto ptrB = sharedMakeInvestment<int>(3);
}












Thursday, August 23, 2018

Understanding C++ Type Names with a Handy Function

C++ is a strongly-typed language. This means that every variable and expression has a type, and that type is known at compile-time. There are many times when we need to know the type of a variable or expression, especially when debugging or dealing with templates.

The C++ standard provides the typeid operator for this purpose. It gives us the type of an expression in the form of a std::type_info object, but it might not always give us the type in a human-readable format. For example, for the type int, it might return i instead of int.

To solve this problem, we can use a handy function, type_name(), which takes a template parameter T and returns a string containing the human-readable name of T. The code snippet above presents a definition of this function. Let's break it down:

Understanding the type_name() Function

The function type_name() is a template function that accepts a type T and returns its name as a std::string.

template <class T>
std::string type_name()
{
   //...
}

It first removes any reference qualifiers from T using std::remove_reference<T>::type:
typedef typename std::remove_reference<T>::type TR;

Then it demangles the name of TR using abi::__cxa_demangle if available. The function then constructs a string r with the demangled name, or typeid(TR).name() if demangling is not available.

Finally, it appends qualifiers such as const, volatile, and reference symbols (& or &&), if they exist:

#include <type_traits>
#include <typeinfo>
#ifndef _MSC_VER
#   include <cxxabi.h>
#endif
#include <memory>
#include <string>
#include <cstdlib>

template <class T>
std::string
type_name()
{
    typedef typename std::remove_reference<T>::type TR;
    std::unique_ptr<char, void(*)(void*)> own
           (
#ifndef _MSC_VER
                abi::__cxa_demangle(typeid(TR).name(), nullptr,
                                           nullptr, nullptr),
#else
                nullptr,
#endif
                std::free
           );
    std::string r = own != nullptr ? own.get() : typeid(TR).name();
    if (std::is_const<TR>::value)
        r += " const";
    if (std::is_volatile<TR>::value)
        r += " volatile";
    if (std::is_lvalue_reference<T>::value)
        r += "&";
    else if (std::is_rvalue_reference<T>::value)
        r += "&&";
    return r;
};


Using the type_name() Function

The type_name() function can be used like the following.

In this code, decltype is used to fetch the type of a variable or expression, and type_name() is used to print its human-readable name.


class Widget {
   public:
        int x, y;
};

auto f()
{
     int a = 9;
     cout << type_name<decltype(a)>() << endl;
     cout << type_name<decltype((a))>() << endl;
     return a;
}

int main(void)
{   
    Widget w;
    Widget &rw = w;
    auto aw = rw;
    auto daw = rw;
    cout << type_name<decltype(aw)>() << endl;
    cout << type_name<decltype(daw)>() << endl;
    f();
    return 0;
}

Running this code will output:

Widget
Widget&
int
int&

This output shows the type names of the variables aw, daw, a, and (a), respectively.

In conclusion, the type_name() function is a helpful tool for debugging and understanding C++ type information. It provides a human-readable type name and handles const, volatile, and reference qualifiers. It's a great addition to any C++ developer's toolkit.