tags:

views:

262

answers:

5

I'm trying to implement the Sieve of Eratosthene in C++. However after several attempts at this I always get runtime errors. I'm thinking this has to do with the state of iterators being used get corrupted somewhere. I can't put my finger on it though. Here is my code:

    //Sieves all multiples of  the current sequence element
    bool multiple_sieve(std::list<int>& num_list)
    {
        std::list<int>::iterator list_iter(num_list.begin());
        std::list<int>::reverse_iterator last_element_iter(num_list.rbegin());

        for(std::list<int>::iterator elements_iter(++list_iter);
           elements_iter !=  num_list.end();)
        {
            if((*elements_iter % *list_iter == 0) &&
             (*elements_iter <= *last_element_iter) && (*list_iter != 1))
                num_list.erase(elements_iter);
            else ++elements_iter;
        }
        return true;
    }

    std::list<int>& prime_sieve(std::list<int>& num_list)
    {
        for(std::list<int>::iterator list_iter(num_list.begin());
          list_iter != num_list.end(); ++list_iter)
            multiple_sieve(num_list);
        return num_list;
    }

What I'm doing wrong? What's generating the runtime errors?

Update: When I run this in my tests I get an error with the message "list iterators are not compatible".

+2  A: 
num_list.erase(elements_iter)

I don't think you should do this, this deletes the number from your list completely, you rather want to mark it as a non-prime.

If you really want to use STL for this instead of a bool array, better use std::bitset like in this implementation (well, there might also be better ones).

schnaader
lists dynamically adjust on deleting an element in the sequence therefore the iterators should still be valid. Tell me how you'd go about marking the sequence numbers as non-prime.
gogole
See the other answers, you're right about the iterators are adjusted and returned, but you don't use the return value, so your iterator is not valid anymore.
schnaader
@gogole - He's saying, algorithmically you should not delete elements from the list .. it has nothing to do with the implementation of your container.
eduffy
on point, thanks Schnaader. I'll go ahead and correct that. I'm still interested in how you'd go about marking the sequence elements as non-primes. Please tell me how you'd do that. thank you.
gogole
@gogole - instead of making it a list of ints, make it a list of std::pair<int,int> with the first int being the number and the second one being either zero or one (using that to indicate if the number is prime or not)
Eric Petroelje
+5  A: 

This line:

num_list.erase(elements_iter);

Is going to cause you problems since you are modifying the list while iterating it. You could do this to avoid that problem:

elements_iter = num_list.erase(elements_iter);

ETA: Removed stuff about erase() invalidating other iterators (looks like they are safe in this case) - just set elements_iter to the return value from erase() and you should be good to go.

Eric Petroelje
This might be interesting, though it might not change the fact that deleting elements in the list messy: "Lists have the important property that insertion and splicing do not invalidate iterators to list elements, and that even removal invalidates only the iterators that point to the elements that are removed." From http://www.sgi.com/tech/stl/List.html
Jonas
@Jonas - Yeah, I was looking at the other iterators and assuming that one of them could be pointing to the same element as elements_iter. However, It looks like last_element_iter is safe since it should never point to the same element as elements_iter. It also looks like the list_iter would be safe as well, but I'm not 100% sure there.
Eric Petroelje
+3  A: 

I think I see one problem and it's std::list.erase(). erase() invalidates erased iterator only - all iterators to other parts are valid. After you erased it, you keep using it in for statement - "elements_iter != num_list.end()".

To solve you can use the fact that erase() returns iterator next after erased one or end if erased oiterator was last one. So replace line:

num_list.erase(elements_iter);

by

elements_iter = num_list.erase(elements_iter);

If you still have a problem I advise you to debug your algo under Visual Studio - it has degub version of STL, so in case of error debugger will stop on line causing problem.

dimba
+4  A: 

I don't know why you're using a list here. It's easier to run sieve on vector and save primes in list.

std::list<int> primes(int MAXN){
  std::list<int> result;
  std::vector<bool> sieve(MAXN+1,true);
  for(int i=2;i<=MAXN;i++){
    if(sieve[i]==true){
      result.push_back(i);
      if((long long)i*i<=MAXN)  //prevent integer overflow
        for(int j=i*i;j<=MAXN;j+=i)
          sieve[j]=false;
    }
  }
  return result;
}
niteria
With the minor assumptions of infinite memory and no cache misses, using a list will make the algorithm faster. Ok, those aren't minor assumptions :)
Brian
note that you only need to cross off multiples of primes that are less than or equal to the sqrt(MAXN), and you start the crossing off not at 2*i but at i*i
Greg Rogers
I don't know why I had 2 there :)
niteria
+1, wonderful easy, short and efficient solution (except that the vector might take 8 bits instead of 1 bit for each bool).
schnaader
Edited so loop only goes up to sqrt(MAXN).
schnaader
@schnaader: rollbacked, you wouldn't get all primes with that limit. Notice that condition in innermost for is sufficient.
niteria
If I understand documentation correctly, on "good" compilers this will work like bit_vector. ( http://www.sgi.com/tech/stl/bit_vector.html )
niteria
vector<bool> is not working with STL (see http://stackoverflow.com/questions/670308/alternative-to-vectorbool)
dimba
for C++0x you can replace std::vector<bool> with std::dynamic_bitset<>
niteria
@niteria: Ah, now I understand. Sorry, haven't thought about that push_back...
schnaader
+2  A: 

I've posted a more efficient sieve previously.


This works for me. Notable modifications from your code: use .erase(i++) otherwise the iterator gets invalidated, and start multiple_sieve from successive locations in the list rather than always from the start.

#include <iostream>
#include <list>

template<typename T, typename L>
void multiple_sieve(L &nums,
        const typename L::iterator &begin, const typename L::iterator &end) {
    T first = *begin;
    typename L::iterator i(begin);
    ++i;
    while (i != end)
        if (*i % first == 0) nums.erase(i++);
        else ++i;
}

template<typename T, typename L>
void prime_sieve(L &nums) {
    typename L::iterator end = nums.end();
    for (typename L::iterator i(nums.begin()); i != end; ++i)
        multiple_sieve<T, L>(nums, i, end);
}

int main(int argc, char **argv) {
    std::list<int> list;
    for (int i = 2; i < 1000; i++)
        list.push_back(i);
    prime_sieve<int, std::list<int> >(list);
    for (std::list<int>::iterator i(list.begin());
            i != list.end(); i++)
        std::cout << *i << std::endl;
}
ephemient