Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.
#include <iostream>
#include <algorithm>
#include <vector>

class SieveHelper {
public:
    SieveHelper() : cur(2) {}

    bool operator()(unsigned int i) {
        return i != cur && i % cur == 0;
    }

    unsigned int cur;
};

void sieve(std::vector<unsigned int>& arr) {
    SieveHelper helper;
    auto it = arr.begin();

    while (it != arr.end()) {
        helper.cur = *it;

        auto last = std::remove_if(it, arr.end(), helper);
        arr.erase(last, arr.end());

        it++;
    }
}

int main() {
    int n;
    std::cin >> n;

    std::vector<unsigned int> arr(n);
    for (int i = 2; i <= n; i++)
        arr[i-2] = i;

    sieve(arr);

    std::for_each(arr.begin(), arr.end(), [](unsigned int i) {
        std::cout << i << " ";
    });
    return 0;
}

Please point out any and every problem you see with this code. Any fundamental flaws, naming conventions, design decisions, you name it.

And I appreciate you taking the time to read through this!

share|improve this question
2  
A classical prime sieve is probably not implemented in terms of erasing from a vector. ideone.com/8yKTK is an example, optimized for minimizing memory usage. sieve_vec is a table of booleans where indexes correspond to odd values 1, 3, 5, 7, ... – UncleBens Dec 29 '11 at 15:23
2  
As to difference in speed compare ideone.com/MB3VJ (primes up to 10,000,000 in 0.08 seconds) and ideone.com/BKQXd (up to 200,000 in 2.5 seconds). – UncleBens Dec 29 '11 at 15:31
@UncleBens: Wow! Thanks for showing me a MUCH more efficient solution! – kisplit Dec 29 '11 at 16:40

5 Answers

up vote 3 down vote accepted
  • I sort includes alphabetically, making it easier to keep track of which headers are included in long lists.

I would write:

#include <algorithm>
#include <iostream>
#include <vector>

In a long, unsorted list of includes it's hard to see if a header is included twice:

#include <vector>
#include <list>
#include <algorithm>
#include <new>
#include <fstream>
#include <set>
#include <stdexcept>
#include <list>
#include <cstdlib>

Imagine this problem increasing if you have a couple dozen includes!

  • arr is very undescriptive, and in this case even misleading. This isn't an array, it's a std::vector. vec would be slightly better, but try to describe what the data structure contains or does rather than what kind of data structure it is. Even something as simple as numbers is better than vec (or arr), and you can probably improve that even further. Naming is very important to ensure readability.
  • Consider using <cstdint> to control the size of numbers. This makes porting a whole lot easier.
  • In C++, it's a good habit to prefer prefix increment- and decrement-operators, because they are usually more efficient for non-built-in types. It doesn't really matter for built-in types, but consistency is nice.
  • Consider using using-declarations, i.e. using std::vector; and so on, to make the code more readable.
  • I'd usually make SieveHelper::cur private and control access to it, but I presume you know that and did it this way on purpose. It's fine for a small program, but remember that programs grow.
  • Use std::vector::at(), which is bounds-checked, whenever array indexing is not in the speed-critical path. The exception is when you're 110 % sure you can never, ever go out of bounds - but better safe than sorry!
  • Consider making sieve() take and return an argument, rather than just manipulate an input argument. This is definitely a trade-off, so use your own judgement, but input parameters are generally less readable.

On the plus side, this is mostly good. Extra points for using built-in algorithms, lambda and other juicy C++11 stuff :)

share|improve this answer
1  
nice suggestions! – Vinayak Garg Dec 29 '11 at 11:34
@Lstor: These are some excellent bullets, thanks a lot! I'm not quite sure what you mean by bullet 1 though. – kisplit Dec 29 '11 at 16:40
1  
@kisplit I edited the post a bit, hopefully it is clearer now :) – Lstor Dec 29 '11 at 19:52
1  
@Lstor: Thanks again for all the help :D – kisplit Dec 29 '11 at 19:57
1  
don't agree with using at() as default. I would prefer operator[] as default using at() only when data comes from user (unvalidated input). If you can't guarantee the values on validated code then you have bigger problems. – Loki Astari Dec 30 '11 at 2:36
show 1 more comment

Where you use while() I would prefer to use for(;;) (and use pre-increment).
Not a big deal but I like to keep all the loop control in one place

auto it = arr.begin();

while (it != arr.end()) {
    // STUFF

    it++;
}

// I prefer (though not much different)

for(auto it = arr.begin(); it != arr.end(); ++it)
{
    // STUFF
}

Rather than have SieveHelper being declared once and then updated each time through the loop. Just create a new one each iteration. The actual cost of construction will be optimized to zero and it becomes easier to read.

    helper.cur = *it;

    auto last = std::remove_if(it, arr.end(), helper);

// I would do this (note it requires slight modifications to SieveHelper (see below)).

    auto last = std::remove_if(it, arr.end(), SieveHelper(*it));

Keeping const correct is a good habit to get into. You need to apply this to your SieveHelper. Also for simple helper functions like this I like to make them structs

struct SieveHelper
{
    bool operator()(unsigned int i) const  // object not changed by method => const
    //                              ^^^^^
    {
        return i != cur && i % cur == 0;
    }

    // Use a reference (as we don't need to store state in the helper)
    SieveHelper(unsigned int const& c) : cur(c) {}
    private:
    unsigned int const&  cur;
};

Since you are using C++11 we can even simplify this to a lambda

auto last = std::remove_if(it, arr.end(), 
                           [&it](unsigned int i){ return i != (*it) && i % (*it) == 0;}
                          );

As an optimization (probably not a big one) you don't need to call erase after each iteration:

void sieve(std::vector<unsigned int>& arr)
{
    SieveHelper helper;
    auto it   = arr.begin();
    auto last = arr.end()

    while (it != arr.end()) {
        helper.cur = *it;

        last = std::remove_if(it, last, helper);

        it++;
    }
    arr.erase(last, arr.end());
}

Now if we apply all these (apart from lambda)

void sieve(std::vector<unsigned int>& arr)
{
    auto last = arr.end()

    for(auto it = arr.begin(); it != arr.end(); ++it)
    {
        last = std::remove_if(it, last, SieveHelper(*it));
    }
    arr.erase(last, arr.end());
}
share|improve this answer
Good comments, and I also prefer for over while. While while may be more readable, it spills a variable into the surrounding scope, and with while it's easier to neglect the incrementing statement. – Lstor Dec 30 '11 at 8:37
Wow, I'm really glad you came in and replied even after I already chose an answer because this is really great! Any chance you could point me to an article about the particular compiler optimization on constructing each iteration? – kisplit Dec 30 '11 at 17:43
Sorry I don;t have an article. You should be able to see it by generating and examining the assembly produced. – Loki Astari Dec 30 '11 at 19:03

Why are you adding 1 to your array?

I can give a simple performance hint. You know that every even number except 2 is composite, so in initialization of arr do not add even numbers (and of course you should not check for 2 in your sieve function). Something like this:

std::vector<unsigned int> arr(n/2+1);
arr[0] = 2;
for (int i = 3; i <= n; i+=2)
    arr[i/2] = i;

or you can go further and do not add multiples of 2 and 3. For walking over numbers which are not multiples of 2 and 3, you should first take a step of size 2 and then take a step of size 4 and again 2 and again 4 and ...

std::vector<unsigned int> arr(n/3+1);
arr[0] = 2;
arr[1] = 3;
int t = 2;
for (int i = 5; i <= n; i+=t, t=6-t)
    arr[i/3+1] = i;
share|improve this answer
Thank you for pointing out the 1. I removed that from the above code. – kisplit Dec 29 '11 at 16:39

I want to point out that it is good that you don't use "using" as that will only make your code harder to read not like lstor said better to read, as one knows exactly where a function comes from and doesnt have to look anything up.

See: http://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-a-bad-practice-in-c/1453605#1453605

Some c++0x features:

-replace the helper class with a lamba

-replace vec.begin with std::begin(vec), the same for std::end

share|improve this answer
3  
Please note that I said a using declaration, not a using directive. I mean using std::vector and so on, which rather explicitly states where each symbol is from, and leaves out the cluttering namespace specifiers in the code itself (i.e. vector instead of std::vector). That said, for std inside relatively small code blocks, there is nothing wrong with using using namespace std either, as most C++ programmers are very well acquainted with where vector etc. is from. – Lstor Dec 29 '11 at 19:45
Agree with Lstor. You are acusing him of the wrong thing. His usage of using std::vector is OK. I disagree with his comment about using using namespace std in small blocks of code. For consistency you should write the code the same everywhere. Thus prefer the form std::vector as you get used to using explicitly scoped types thus when you don;t do it on purpose it stands out. – Loki Astari Dec 30 '11 at 19:06
I agree that using namespace std; should be avoided, even in small blocks. As a result, I'm so used to seeing std::vector that when I see vector without the std::, I interpret it as MyVector and start looking for where it is defined and how it differs from std::vector. End result: using std::vector; will slow me down when reading code. However, I won't object against namespace xyz { using verylong::name::space::id; } to create a shorthand xyz::id. – Sjoerd Dec 31 '11 at 4:14

I like closing for and if one liners just to prevent accidental addition of code that doesn't get executed or put the entire thing on one line, but that may just be me.

for (int i = 2; i <= n; i++) {arr[i-2] = i;}

or

for (int i = 2; i <= n; i++) {
  arr[i-2] = i;
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.