Блог пользователя Aryansh_S

Автор Aryansh_S, история, 5 лет назад, По-английски

Hi y'all,

Input/output is by and far the most frequently used concept in competitive programming, so I thought it might be a good idea to cover an optimization I developed in my first blog post.

We all know cin/cout, although convenient to type up, tend to be jarringly slow in the context of CP. This is because cin and cout are tied together and synced to stdio. To evade this, many rely on the classical C methods of I/O: scanf/printf.

For example, when dealing with an int $$$a$$$, they may write the following to read and write:

int a; 

int main() {
  scanf("%d", &a); 
  printf("%d\n", a); 
}

But the syntax here is painful and definitely cannot be generalized to support a wider variety of types.

To that end, it is well-known that we can keep the convenience of cin/cout without having to sacrifice for performance insofar as we untie cin from cout and unsync from stdio. For instance, we may write the below:

int a; 

int main() {
  cin.tie(0)->sync_with_stdio(0); //make cin/cout as fast as scanf/printf
  
  cin >> a; 
  cout << a << "\n";
}

But even with such optimization, we ask the question of whether we can make the job less cumbersome. For instance, can we extend to different types, and can we make the syntax more elegant and simple?

We might write this to maybe obtain the convenience of in(.) and out(.) functions at ease:

void in(int& a) { cin >> a; }
void out(int& a) { cout << a << "\n"; }

int a; 

int main() {
  cin.tie(0)->sync_with_stdio(0); 

  in(a); 
  out(a); 
}

Unfortunately, however, with all the data structures in the STL that are used to keep information, we cannot effectively write such a function each time.

So how do we go about generalization. For starters, consider the following concept checking functions that utilize SFINAE as of C++11:

template<class T> class is_iterator { //check if iterator or pointer   
  static T makeT();
  typedef void * twoptrs[2];  
  static twoptrs & test(...); 
  template<class R> static typename R::iterator_category * test(R); // Iterator
  template<class R> static void * test(R *); // Pointer
  public: static const bool value = sizeof(test(makeT())) == sizeof(void *); 
};
template<class T> class is_streamable { //check if can be used with cin >>, cout << 
  template<typename SS, typename TT> static auto test(int)->
  decltype(declval<SS&>() << declval<TT>(),true_type());
  template<typename, typename> static auto test(...)->false_type;
  public: static const bool value = decltype(test<ostream, const T&>(0))::value;
};

The first function above allows us to ensure that we are dealing with iterators or pointers, while the second checks if we have a streamable type (that is, can we cin/cout the type using overloaded >> and << operators?).

Now, we can write the following case-by-case generalizations in our template:

#define NL cout << "\n"

template<typename T, typename = typename enable_if<is_streamable<T>::value>::type> 
inline void in(T& var1)
{cin >> var1;}
template<typename T1, typename T2> inline void in(pair<T1,T2>&pt)
{in(pt.f); in(pt.s);}
template<typename T, typename...Types> inline void in(T& var1, Types&...var2)
{in(var1); in(var2...);}
template<typename it, typename = typename enable_if<is_iterator<it>::value>::type> 
inline void in(it bg, it nd) 
{while(distance(bg,nd)) in(*bg), ++bg;}

inline void out(){NL;}
template<typename T, typename = typename enable_if<is_streamable<T>::value>::type> 
inline void out_(T var1)
{cout << var1 << " ";}
template<typename T1, typename T2> inline void out_(pair<T1,T2> pt)
{out_(pt.f); out_(pt.s);}
template<typename T, typename...Types> inline void out(T var1, Types...var2)
{out_(var1); out(var2...);}   
template<typename it, typename = typename enable_if<is_iterator<it>::value>::type> 
inline void out(it bg, it nd) 
{while(distance(bg,nd)) out_(*bg), ++bg; NL;}

inline void outln(){}
template<typename T, typename = typename enable_if<is_streamable<T>::value>::type> 
inline void out_ln(T var1)
{cout << var1; NL;}
template<typename T1, typename T2> inline void out_ln(pair<T1,T2> pt)
{out_(pt.f); out_(pt.s); NL;}
template<typename T, typename...Types> inline void outln(T var1, Types...var2)
{out_ln(var1); outln(var2...);}   
template<typename it, typename = typename enable_if<is_iterator<it>::value>::type> 
inline void outln(it bg, it nd)    
{while(distance(bg,nd)) outln(*bg), ++bg;}

TL;DR Essentially, our cases boil down to checking the characteristics of our template variables -- if they are iterators or streamble types -- and then we can use in(.) and out(.) with ease.

Now, suppose we want to read in and write out a vector of size 5. We can simply do the following:

vector<int> v; 

int main() {
  cin.tie(0)->sync_with_stdio(0); 

  v.resize(5); 
  in(begin(v),end(v)); 
  out(begin(v),end(v)); 
}

And this is easily streamlined with macros!

You can check for a more complete treatment on my GitHub here.

Hope this will can be useful to competitors!

Regards, Aryansh

  • Проголосовать: нравится
  • +11
  • Проголосовать: не нравится