Friday, January 15, 2016

Python is Over 9000!

In my last post I asked you, my dear reader, to try and flex your programming muscles solving some tasks in C++ language. Now is the time to compare the solutions of the aforementioned exercises in both Python (written by me, please take a note that I have learnt Python in the last year) and C++ (solutions from my friends who know how to program). Big shoutouts to them, as without their work this post will not be that long. You can check their profiles on github - przemkovv and MichalNowicki).

The code

1) lets assume we have two collections and would like to print values from them in the following format:
value_0_from_first_collection, value_0_from_second_collection
value_1_from_first_collection, value_1_from_second_collection
etc.

So for the warm-up we are doing something rather easy, that in Python looks really straightforward:

for n1, n2 in zip(numbers_1, numbers_2):
    print(n1, n2)

For those not familiar with Python - you can check zip function here. In this case it is returning elements from both collections which are assigned to names n1 and n2.

In C++ it looks a little bit more cumbersome (what are this magical * sign? :P):

for (auto it = kolekcjaA.begin(), it2 = kolekcjaB.begin(); 
     it != kolekcjaA.end() && it2 != kolekcjaB.end(); ++it, ++it2) {
    std::cout << *it << " " << *it2 << std::endl;
}

Or if you use some dark magic you can get something similar to what Python is offering:

vector<tuple<char="", int="">> out_zipped;

transform(vec.begin(), vec.end(), str.begin(), back_inserter(out_zipped), [](auto x, auto y){ return make_tuple(x, y);});

for_each(out_zipped.begin(), out_zipped.end(), [](auto t) {
    cout << get<0>(t) << ", " << get<1>(t) << endl;
});

It gets the job done but I would say that Python solution is much neater.

2) lets assume we have a text and we would like to print it without the first and the last sign. In addition we would like to print the text length.

Python:
print(text[1:-1], ", " + str(len(text)))

C++:
// solution 1
std::cout << text.substr(1, text.size() - 2) << " " << text.length() << std::endl;

// solution 2
string cut_from_both_sides(str.begin() + 1, str.end() - 1);
cout << cut_from_both_sides.length() << " " << cut_from_both_sides << endl;

All of them looks similar, but look at the next task to see the advantage of Python solution!

3) we have a collection of values and we would like to print it without the first and last values. In addition we would like to print the size of the collection.

Python:
print(numbers_1[1:-1], ", " + str(len(numbers_1)))

C++:
// solution 1
for (auto it = kolekcjaA.begin() + 1; it + 1 != kolekcjaA.end(); ++it) {
    std::cout << *it << std::endl;
}

// solution 2
vector<int> vec2(begin(vec) + 1, end(vec) - 1);
cout << vec2 << endl;

As you can see the Python is still using the same way, while C++ solution 1 evolved to using iterators, while the C++ solution 2 is the same but will not work for lists (and other collections that do not offer [] operator)!

4) we have a function that has to return:
a) two values,
b) three values.
How would you implement that?

Python:
def increment_all(a, b, c):
    return a+1, b+1, c+1

x, y, z = increment_all(x, y, z)
print(x, y, z)

C++:
auto triple_return() {
    return make_tuple(5, "good", 3.5);
}

auto gotcha3 = triple_return();
cout << get<0>(gotcha3) << " " << get<1>(gotcha3) << " " << get<2>(gotcha3) << endl;

To save space I left just triple return. At first glance all the solutions look similar, but take a closer look how you print the returned values and you will appreciate Python simplicity. Also it is possible to swap values without additional buffers:

a, b = b, a

5) for collection of your choice do:
a) print all values,

Python:
for v in values:
    print(v)

C++:
for (auto value : vec3) {
    std::cout << value << std::endl;
}

Nothing extraordinary. C++ has similar construction for getting elements from collections.

b) increase each value by 10,

Python:
values = [v+10 for v in values]

C++:
// solution 1
for (auto& x : vec3) {
    x += 10;
}

// solution 2
transform(begin(vec3), end(vec3), begin(vec3), [](auto x) { return x + 10; });

This is where things start to be interesting. In Python you use thing called list comprehension, which is general and elegant way to work on containers. In C++ you can use solution similar to the last one but you have to remember to use another special C++ operator - &. Solution 2 is a way to mimic Python behaviour.

c) remove last element,

Python:
values.pop()

C++:
// solution 1
kolekcjaA.resize(kolekcjaA.size() - 1);

// solution 2
vec3.pop_back();

Nothing special, but C++ guys offered two different solutions :)

d) remove n-th element,

Python:
n = 2
values.pop(n)

C++:
// solution 1
int n = 4;
kolekcjaA.erase(kolekcjaA.begin() + n);
}
// solution 2
const int nth = 10;
vec3.erase(begin(vec3) + nth);

Once again C++ solutions does not work for lists.

e) print value alongside its index,

Python:
for i, v in enumerate(values):
    print(i, v)

C++:
// solution 1
for (int i = 0; i < kolekcjaA.size(); i++) {
     std::cout << "Index = " << i << " " << kolekcjaA[i] << std::endl;
}

// solution 2
int i=0;
for (auto it = begin(vec3); it != end(vec3); ++i, ++it) {
    cout << i << " " << *it << endl;
}
// solution 3
i=0;
for (auto x : vec3){
    cout << i << " " << x << endl;
    ++i;
}
// solution 4
for (auto p = make_pair(0, begin(vec3)) ; p.second != end(vec3); ++p.first, ++p.second) {
    cout << p.first << " " << *(p.second)  << endl;
}

In Python this is straightforward - enumerate and done! C++ offers a lot of solutions, that have some pros and cons. I prefer to have one main solution - it helps when more than one person is working on with the code.

f) check if value x is in collection,

Python:
if 27 in values:
    print("27 is in the collection")

C++:
// solution 1
bool contains_25 = find(begin(vec3), end(vec3), 25) != end(vec3);
cout << contains_25 << endl;
// solution 2
auto contains_x = [](auto C, auto x) { return find(begin(C), end(C), x) != end(C); };
cout << contains_x(vec3, 25) << endl;

Once again you can witness how elegant Python is. It is the most straightforward way and even non-programers would understand what this code do.

g) check if all values are smaller than y.

Python:
if all(v < 30 for v in values):
    print("All values are smaller than 30!")

C++:
// solution 1
int twojaWartosc = 4;
auto wart = std::find_if(kolekcjaA.begin(), kolekcjaA.end(), [&twojaWartosc](auto const &x) { return x < twojaWartosc; });
std::cout << "Wartosc : " << *wart << std::endl;

// solution 2
int y = 50;
cout << all_of(begin(vec3), end(vec3), [=](auto x) { return x < y; }) << endl;
// solution 3
auto less_y = [](auto y) { return [=](auto x) { return x < y;  }; };
cout << all_of(begin(vec3), end(vec3), less_y(20)) << endl;

Python is clean and simple. In C++ you can use different ways, but even this simple task can cause problems - solution 1 throws exception when the value is not in the collection as you try to access (dereference) an iterator that point to the end of collection.

6) having a text count how many letters are lower-case and how many are upper-case

Python:
number_of_uppercase_case = sum(1 for c in text if c.isupper())
number_of_lowercase_case = sum(1 for c in text if c.islower())

C++:
// solution 1
int male = std::count_if(podanyTekst.begin(), podanyTekst.end(), [](unsigned char x) { return islower(x); });
int duze = std::count_if(podanyTekst.begin(), podanyTekst.end(), [](unsigned char x) { return isupper(x); });

// solution 2
cout << count_if(begin(s), end(s), islower) << endl;
cout << count_if(begin(s), end(s), isupper) << endl;

All the solutions are very similar - they use built-in functions. The thing with Python is that you can use almost the same code for other things like e.g. extraction of lower-case letters:

lower_case_letters = [c for c in text if c.islower()]

7) is there any situations that you do not use indentation?

We all have agreed that apart of code golfing everybody should use indentation. So if it is obligatory lets resign from { and } brackets. Their are unneeded and deprecated. Lets use Python!

8) are you using standard arrays (static or dynamic) at all (like: int a[5] or int* b = new int[5])?

Once again we had a consensus - dynamic tables should be avoided as they can be dangerous.
When I think about them two quotes comes to my mind:

C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.

-- Bjarne Stroustrup

With great power comes great responsibility.

-- Voltaire

Other

On a side note - this is probably the longest most in my career.
If anybody is interested I used Syntax highlighter to post the code. It offers nice looking code but embedding small portions of code is cumbersome - you have to edit html code and doing it through the browser is a pain.

Summary

I hope you enjoyed this long, over 9000 signs comparison. By no mean I was trying to defame C++. I think it is an important programming language, that has its uses (C for microcontrollers, some high performance routines and libraries). Just in my opinion the new additions to C++ (C11, C14) that tries to convert it to modern programming are a failure. They change many things upside-down, make the code much different to understand for people that are not familiar with new standards while the code is still not as elegant and readable as Python. It is just my two cents, opinion that is probably heavy influenced by my experience - I did a lot of programming for embedded systems (microcontroller based).



If, by some accident, you are still not convinced about Python greatness you are not alone. Apparently some people think Python is overrated ;)

TL; DR

This post describes why I think Python is a great language, especially when you are starting learning how to program. Different simple exercises were done in both Python and C++ and the code was compared. Python rulez.