1

So recently I got into a debate about how to solve a problem, the problem specifically was: How do I find all the pallindromes between 1 and 1 million. I said, "Use atoi to make a string, use a for loop to reverse the string, the use strcmp to compare the string(s) in question.

A few minutes later someone asked "Why would you use a C-style solution in C++." I found myself confused of a simple, more "C++" way of solving this with code as direct and easy to understand. Anyone care to illuminate me on this one?

edit: itoa not atoi

HunderingThooves
  • 962
  • 8
  • 20
  • 33
  • 4
    `atoi` to make a string, isn't it the other way around? Don't you mean something like `sprint`? – Andreas Brinck Aug 23 '11 at 09:42
  • 7
    As an aside, creating a reversed duplicate of the string and comparint them is an **extremely** inefficient way of checking if a word is a palindrome – Andreas Brinck Aug 23 '11 at 09:45
  • 3
    It's very wasteful to reverse the string, just check in-place – David Heffernan Aug 23 '11 at 09:46
  • 10
    Instead of *checking* for palindromes, why not just *generate* all palindromes ? That can be done very easily by 'mirroring' all values between 0 and 999 eg. – Sander De Dycker Aug 23 '11 at 09:56
  • 3
    Checking for palindromicity is horrendously inefficient way of solving the problem. There are only 1800 of them in the range below 1000000, and it is very easy to generate them all. – n. m. could be an AI Aug 23 '11 at 10:00
  • @Sander De Dycker: be aware that that solution will not generate palindromes with an odd number of characters, e.g "12321" (it's trivially amended to make it generate those, so I still agree with the sentiment). – Sven Aug 23 '11 at 10:04
  • 1
    @Sven : 12321 is 123 mirrored. Each number between 1 and 999 needs to be mirrored twice. So, 123 becomes both 123321 and 12321. That was implied in my previous statement (but maybe should have been explicit). – Sander De Dycker Aug 23 '11 at 10:05
  • 1
    "Why would you use a C-style solution in C++" is a question designed to perpetrate a holy war. "Here is a solution using C++-only features, and it's better than your solution" is actual programming. – Steve Jessop Aug 23 '11 at 10:24
  • @Sander De Dycker: Fair enough. :) – Sven Aug 23 '11 at 10:28

5 Answers5

5

Quite simply, C++ streams are guaranteed to be memory safe and exception safe, failure is distinct from any return value, and C++ strings are memory-safe and exception-safe. C-strings and atoi are hideously unsafe in pretty much every way known to man. Code written in that way is much more error-prone.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Can you elaborate in what ways they are unsafe and why? – HunderingThooves Aug 23 '11 at 10:38
  • 1
    @HunderingThooves: C-strings are \0-terminated, if they are terminated at all in case you forget. By forgetting some corner case (-> index out of range) it might happen that you r/w memory regions that you are supposed to r/w. As in every STL stuff, wrong usage usually results in thrown Exceptions and not in segfaults. Anyways, in some cases "C-style" makes sense when you need more performance and you know what you are doing. – user694971 Aug 23 '11 at 11:25
  • 2
    @user694971: Agree with your main analysis. But claiming the C has better performance is just not true. Use of C++ Strings is normally as good or better than C-Strings (The problem with C-Strings is you never know the size without traversing them which is a whole set of performance problems there). As the STL is all template(d) the compiler will generally remove any inefficiencies and the generated code will be as effecient as its C counterpart (just safer). – Martin York Aug 23 '11 at 14:30
  • @Martin - I'm glad someone challenged that "C-Style is faster" assumption. In my view if C++ style is slower (and it's actually a problem) then it's a compiler defect :) – Flexo Aug 23 '11 at 15:29
  • @awoodland: Assuming we have perfect compilers, I wonder why there is any difference between (ahead of time-)compiled languages anyway :P – user694971 Aug 23 '11 at 15:49
  • @user694971: If they did the same thing then yes it would be the same. But C++ (uses the standard CS technique of) swaps space for time. By using more space we save time (ie its faster). Look at C-String vs C++string the difference is we use more space to save extra information to make things faster and safer (the extra space we save is insignificant but the time that can be saved is). – Martin York Aug 24 '11 at 06:13
  • @Martin: No it wouldn't. A few reasons why: - most STL operations do bounds checking - at compile time you cannot know everything about the runtime behaviour, a thing you can actually proof. So it really depends on the programming language and your code, how far one can automatically optimize. Did you know that Java's JIT can be locally faster than C++? - C++ binaries have a much larger footprint in the real world... but register and cache sizes are limited... - modern Intel CPUs are optimized for function calls, not for static template-generated code – user694971 Aug 24 '11 at 09:20
  • @user694971: Your knowledge of C++ is obviously flawed 1) STL does practically no bounds checking as it is not required. 2) Template code is no different to normal code (so that comment is a non sequitur) 3) C++ binaries are bigger by what measure? All studies have found that C code that does the same as C++ has the same size binaries (unsurprisingly). But a side affect of the language is that the C++ source is significantly smaller. 4) Your attempt to bring the religious war on speed differences between Java/C++ is irrelevant and futile (which is why questions on the subject are auto closed). – Martin York Aug 24 '11 at 09:44
  • @user694971: The long and short is. Well written code in any language will be just as fast as well written code in another language (within normal constraints). **Badly** written code in any language will always be 'Bad'. The main difference is that C++ makes it easier to write safer/faster code than C. Thus to achieve the same speed/safety the C programmer must do more work to achieve the same results (which often does not happen). But badly written C++ is worse than badly written C as the common phrase "C may blow your foot off but C++ will take your whole leg" sort of suggest. – Martin York Aug 24 '11 at 09:52
  • @Martin: Have you ever seen any language benchmarks? http://shootout.alioth.debian.org/ My C++ knowledge might be flawed, but you ignore the hard facts. How do you explain that C is almost always faster than C++ in non-numerical code? C++ has safety mechanism but their cost is high. C++ will always be notorious for being an expert language because it's so easy to make hard to debug bugs that can even drive experts crazy. – user694971 Aug 25 '11 at 09:20
  • @user694971: Yes many times. That is just another flawed comparison (as trotted out by many inexperienced users that don't understand the language differences and meanings). There are no cold hard facts here (in your link) only flawed comparisons (and I can do a quick google and find the exact opposite results very easily). Also your final statement (is again flawed) I find that the general opinion is the exact opposite. C++ may be notorious for being hard to learn (because of its size) but in the long run is much easier to debug and maintain (than C). – Martin York Aug 25 '11 at 10:23
  • @Martin: "...and I can do a quick google and find the exact opposite results very easily..." Do it ;-) – user694971 Aug 25 '11 at 10:35
  • @user694971: This proves as much as your link (i.e. nothing). Link 1 after googling ['C Vs C++ performance'](http://unthought.net/c++/c_vs_c++.html). Comparing language speeds like this is pointless. Ask a question on SO about the performance difference of any language `X Vs Y` any you will quickly be laughed at (please feel free to try). Anybody can write a bad implementation of a problem in a language they don't understand. Just as you have shown anybody can spout incorrect and obviously flawed facts about a language they don't now. You obviously don't know C++ (well). – Martin York Aug 25 '11 at 10:58
3

Example C++ solution:

#include <algorithm>
#include <iterator>
#include <iostream>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/lexical_cast.hpp>

namespace {
   bool is_palindrome(unsigned int i) {
     const std::string& s = boost::lexical_cast<std::string>(i);
     return std::equal(s.begin(), s.end(), s.rbegin());
   }

   const unsigned int stop = 1000000;
}

int main() {
   std::remove_copy_if(boost::counting_iterator<unsigned int>(1),
                       boost::counting_iterator<unsigned int>(stop),
                       std::ostream_iterator<unsigned int>(std::cout, "\n"),
                       std::not1(std::ptr_fun(is_palindrome)));
}

(I used std::remove_copy_if to make up for the lack of std::copy_if which is in C++0x)

For completeness sake I implemented a version that generates the palindromes rather than testing candidates against a predicate:

#include <boost/lexical_cast.hpp>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <cassert>

namespace {
  template <int ver>
  unsigned int make_palindrome(unsigned int i) {
     std::string s = boost::lexical_cast<std::string>(i);
     assert(s.size());
     s.reserve(s.size()*2);
     std::reverse_copy(s.begin(), s.end()-ver, std::back_inserter(s));
     return boost::lexical_cast<unsigned int>(s);
  }
}

int main() {
   typedef boost::counting_iterator<unsigned int> counter;
   std::merge(boost::make_transform_iterator(counter(1), make_palindrome<1>),
              boost::make_transform_iterator(counter(999), make_palindrome<1>),
              boost::make_transform_iterator(counter(1), make_palindrome<0>),
              boost::make_transform_iterator(counter(999), make_palindrome<0>),
              std::ostream_iterator<unsigned int>(std::cout, "\n"));
}

(I could have used boost.range I think instead of std::merge for this)

The discussion point from this I guess then is "is this a better* way to write it?". The thing I like about writing problems like the palindrome in this style is you get the "if it compiles it's probably correct" heuristic on your side. Even if there is a bug it'll still get handled sensibly at run time (e.g. an exception from lexical_cast).

It's a markedly different way of thinking from C programming (but strangely similar to Haskell in some ways). It brings benefits in the form of lots of extra safety, but the compiler error messages can be terrible and shifting the way you think about problems is hard.

At the end of it all though what matters is "is it less work for less bugs?". I can't answer that without some metrics to help.

* For some definition of better.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • 1
    To be honest I think the last line goes a bit overboard compared with `for (unsigned int i = 1; i < stop; ++i) { if (is_palendrome(i)) { std::cout << i << '\n'; } }`. But it makes the point that these tools are available for cases where they do improve the code. – Steve Jessop Aug 23 '11 at 10:30
  • You're probably right with the `ostream_iterator` here, but if it was inserting into a list I might be more likely to use the 'stlified' form here. It doesn't help that the missing `std::copy_if` makes the example less intuitive too. – Flexo Aug 23 '11 at 10:34
  • 2
    It's quite a good illustration, I think, that doing it "the C++ way" is something to *consider* always, but not necessarily *do* always :-) That lack of `copy_if` is a specific defect in the standard that means "the C++03 way" is worse than it should be, and hence more likely to lose to a for loop than if it was present. – Steve Jessop Aug 23 '11 at 10:36
  • In your generator code, maybe a template function with `ver` as a template parameter would be a better way to share code than `boost::bind`. Certainly less verbose. – Steve Jessop Aug 23 '11 at 10:39
  • I like the template instead of `boost::bind` suggestion. I'm probably a bit too prone to reaching for `boost::bind` these days and I'd never really thought to pass `int` like that instead of using `boost::bind` before. – Flexo Aug 23 '11 at 10:44
  • The second version can be further simplified by using boost::merge and boost::counting_range. Personally I'd just use a for-loop and avoid the merge and duplicate function call entirely; this whole thing is a bit over-engineered imo. ;) – Sven Aug 23 '11 at 11:00
  • 1
    I also don't think your use of `std::reverse_copy` is safe as the insertion into s can invalidate the iterators (in practice it won't on VC++ at least, but it's not guaranteed behaviour), unless you reserve the size first. – Sven Aug 23 '11 at 11:08
  • A slight optimization to your `is_palindrome()` function: `return std::equal(s.begin(), s.begin() + s.size()/2, s.rbegin());` – Michael Burr Aug 23 '11 at 14:32
2

The C++ equivalent to your solution would be to:

  1. Use stringstream to turn the number into a std::string.
  2. Use std::reverse_copy to reverse the string.
  3. Use == to compare the strings.

1 is better than using itoa (which you probably meant) because you don't have to allocate the memory for the created string yourself and there's no chance of a buffer overrun.

2 is better because again you don't have to worry about allocating memory for the reversed string and you don't duplicate existing functionality.

3 is better because string1 == string2 reads better than using strcmp.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • 3
    I'd replace step 2 with "Use `std::reverse_copy` to reverse the string". If you're going to use C++ might as well go all the way. :) I'd also prefer to use `boost::lexical_cast` for the conversion if that was an option. – Sven Aug 23 '11 at 09:47
  • 1
    Instead of 2 and 3 do a loop that goes to `length / 2` and compare `string[i]` and `string[length - i - 1]`. **Much** more efficient – Andreas Brinck Aug 23 '11 at 09:48
  • 1
    @Andreas Brinck: agreed, I was sticking to the original parameters of the question with my suggestion. :) – Sven Aug 23 '11 at 09:49
  • 1
    @Andreas: But not equivalent to the OP's algorithm. The point was to compare the C and C++ versions of the same algorithm. – sepp2k Aug 23 '11 at 09:51
  • Instead of parts 2 and 3, I'd have a for loop with one forward iterator and one reverse iterator, comparing iterator values for equality. No need to reverse the string (and the associated memory allocations). – Skizz Aug 23 '11 at 09:51
  • I'd do 2 & 3 in a function: `bool is_palindrome(const std::string& s) { return std::equal(s.begin(), s.end(), s.rbegin()); }` – johnsyweb Aug 23 '11 at 09:55
  • 8
    @Andreas Brinck, @Skizz: how about this for a C++ version of the palindrome check: `std::equal(s.begin(), s.begin() + s.length() / 2, s.rbegin());` :) – Sven Aug 23 '11 at 09:56
  • @Sven: That's certainly more efficient than mine. – johnsyweb Aug 23 '11 at 09:58
  • stringstream is a terrible solution from performance point of view. For every number you will need to create new stream or do s.str(""); s.clear(); which creates temporary std::string and so on. Too many calls, too many memory allocations. snprintf() can reuse same char[10] buffer for every number without any overhead. – blaze Aug 23 '11 at 10:02
  • @blaze: Which is partly why I suggested `boost::lexical_cast` in my first comment, as it is much faster than `stringstream` and more convenient to use (and according to http://www.boost.org/doc/libs/1_47_0/libs/conversion/lexical_cast.htm, even faster than `sprintf` for int->string). – Sven Aug 23 '11 at 10:41
  • @Sven: yes, lexical_cast is much better. It requires construction of std::string, but it should be done anyway in most of C++ programs. Given this specific problem sprintf may still be a little faster but not too much. – blaze Aug 23 '11 at 11:58
  • @blaze: most implementations of `std::string` have optimizations for small strings. For example on VC++ `std::string` won't do any dynamic allocations for strings of less than 16 characters. Because of this and the return value optimization (copy elision), the practical impact of `lexical_cast`'s usage of `std::string` is almost nil. – Sven Aug 23 '11 at 12:02
1

atoi makes it impossible to detect input errors. While a stringstream can do the same job and can errors can be easily detected.

Alok Save
  • 202,538
  • 53
  • 430
  • 533
0

I think a more appropriate question would be why shouldn't you use C-based solutions in C++? If it is simpler and more readable than a "pure" C++ solution, I know I would opt for the C-style solution. Given the solution you came up with was clever and simple, a corresponding C++ solution may be overkill and given the simplicity of the problem I'm not sure what was being suggested that C++ can bring to the solution.

dlanod
  • 8,664
  • 8
  • 54
  • 96
  • there are a lot of assumptions in this answer "*if* it [the C solution] is simpler... a corresponding C++ solution *may* be overkill" with no real justification. – jk. Aug 23 '11 at 09:59
  • 1
    also as other answers point out, the original solution is not particularly clever and C++ will bring a lot of extra safety, which if you add code for in the C version will make it a lot less readable – jk. Aug 23 '11 at 10:05