Prime发电机:单身人士的好地方?

时间:2014-10-24 15:01:09

标签: c++ singleton

我正在编写一个数学函数库。我最基本,最常用的函数之一生成一个低于n的素数列表。我正在尝试选择此功能的界面。我想避免不必要的计算和不必要的复制,我想要一个漂亮干净的界面。首先,我考虑了以下内容:

Singleton:将const引用返回到本地静态向量

const std::vector<int>& getPrimesBelow(int n)
{
    static std::vector<int> primes;
    static int limit = 0;

    // Need to initialize, or resize, the prime list
    if(n > limit)
    {
        // ... add primes to list as necessary ...
        limit = n;
    }
    return primes;
}

优点:

  1. 我们只创建一个列表而不必复制它 - 如果客户端正确使用我们的界面:const std::vector<int>& primes = getPrimesBelow(n)
  2. 如果客户碰巧多次调用getPrimesBelow(1000000)
  3. ,我们可能会消除不必要的计算

    缺点:

    1. 如果客户端没有将返回值分配给引用,我们会 最后还是复制我们的列表:std::vector<int> makesCopy = getPrimesBelow(n)

    2. 如果我们拨打const std::vector<int>& firstList = getPrimesBelow(1000),然后再拨打getPrimesBelow(1000000),我们可能会破解代码:

      for(int i=0; i<firstList.size(); ++i)
      {
          // Unexpectedly looping over way more primes
      }
      
    3. 特别是,con#2感觉太邪恶了。所以,也许更好的选择是:

      返回副本

      std::vector<int> getPrimesBelow(int n)
      {
          static std::vector<int> masterPrimes;
          static int limit = 0; // limit for masterPrimes
          if(n > limit)
          {
              // add to masterPrimes as necessary 
          }
          // std::copy primes into return value
      }
      

      赞成

      1. 仍然避免对素数进行不必要的重新计算。
      2. 返回值绝不会意外扩大。
      3. 缺点

        1. 会导致主要列表的多个副本存在于内存中。在我们只请求一次主要列表的常见情况下,这尤其令人讨厌。
        2. 第三种方法: 返回std::pair<>个迭代器

          std::pair<std::vector<int>::const_iterator, std::vector<int>::const_iterator > 
          getPrimesBelow(int n)
          {
              static std::vector<int> masterPrimes;
              static int limit = 0; // limit for masterPrimes
              if(n > limit)
              {
                  // add to masterPrimes as necessary
                  limit = n;
              }
              return std::make_pair(masterPrimes.begin(),
                                    masterPrimes.lower_bound(masterPrimes.begin(), masterPrimes.end(), n))
          }
          

          这最后一种方法似乎解决了我上面列出的所有缺点。但是,它有一个丑陋的函数签名和调用语法。

          更喜欢哪种方法?为什么?

1 个答案:

答案 0 :(得分:1)

创建自定义迭代器类。你可以使用这样的东西:

for (prime_iterator p = prime_iterator(), end = prime_iterator(1000000); p != end; ++p)
    // do something with *p

这封装了对静态素数表的访问并防止出现问题。它也是一个非常简单的界面。

实施将是这样的:

class prime_iterator
{
public:
    prime_iterator() { m_index = 0; }
    prime_iterator(int n)
    {
        if (!m_table.empty() && n <= m_table.back())
        {
            std::vector<int>::iterator it = std::upper_bound(m_table.begin(), m_table.end(), n);
            m_index = it - table.begin();
        }
        else
        {
            extend_prime_table(n);
            m_index = m_table.size();
        }
    }
    int operator*() const { return m_table[m_index]; }  // optionally throw an exception if m_index is out of bounds
    prime_iterator & operator++() { ++m_index; return *this; }
    bool operator==(const prime_iterator & pi) const { return m_index == pi.m_index; }
private:
    void extend_prime_table(int n);

    size_t m_index;
    static std::vector<int> m_table;
};

编辑:这是一个非常有趣的问题,我决定将其充实并制作一个正常的素数生成器。我保留了所有奇数的布尔向量,而不是保留一个素数表,以便我可以直接在其上实现Sieve of Eratosthenes。这使operator++稍微复杂一些,但我认为这是值得的。

end迭代器实现起来有点棘手。由于end迭代器应该指向序列末尾之后的下一个值,因此正确执行它会要求它在您要求的最大值之后包含下一个素数。这是不切实际的。我决定让它包含一个可达到的最大值,并修改operator==,以便任何大于或等于max的迭代器都比较相等。

#include <algorithm>
#include <assert.h>
#include <iostream>
#include <limits.h>
#include <math.h>
#include <vector>

class prime_iterator
{
public:
    prime_iterator() : m_next(2), m_max(INT_MAX)
    {
    }
    prime_iterator(int n) : m_next(INT_MAX), m_max(n)
    {
        if ((size_t)n/2 >= m_sieve.size())
        {
            int base = (int) sqrt((double)n);
            prime_iterator end(base);
            m_sieve.resize(n/2 + 1, true);
            prime_iterator p;
            for (++p;  p != end;  ++p)
            {
                int first = std::max(*p, (base + *p - 1) / *p);
                first = (first & ~1) + 1;
                for (int i = first * *p;  i <= n;  i += 2 * *p)
                {
                    m_sieve[i/2] = false;
                }
            }
        }
    }
    int operator*() const
    {
        assert((size_t)m_next/2 < m_sieve.size() && m_sieve[m_next/2]);
        return m_next;
    }
    prime_iterator & operator++()
    {
        size_t i, end = m_sieve.size();
        for (i = (m_next - 1)/2 + 1;  i < end && !m_sieve[i];  ++i)
            ;
        m_next = i*2 + 1;
        return *this;
    }
    bool operator==(const prime_iterator & pi) const
    {
        return m_next == pi.m_next || m_next > pi.m_max || pi.m_next > m_max;
    }
    bool operator!=(const prime_iterator & pi) const
    {
        return !operator==(pi);
    }
private:
    int m_next;
    int m_max;

    static std::vector<bool> m_sieve;
};

//                          0/1    2/3   5     7     9
static bool sieve_init[] = {false, true, true, true, false};
std::vector<bool> prime_iterator::m_sieve = std::vector<bool>(sieve_init, sieve_init + sizeof(sieve_init) / sizeof(sieve_init[0]));

int main(int argc, char* argv[])
{
    for (prime_iterator p, end = prime_iterator(1000);  p != end;  ++p)
        std::cout << *p << std::endl;
    return 0;
}

您可以在http://ideone.com/47QbXW

看到结果