我正在编写一个数学函数库。我最基本,最常用的函数之一生成一个低于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;
}
优点:
const std::vector<int>& primes = getPrimesBelow(n)
getPrimesBelow(1000000)
缺点:
如果客户端没有将返回值分配给引用,我们会
最后还是复制我们的列表:std::vector<int> makesCopy = getPrimesBelow(n)
如果我们拨打const std::vector<int>& firstList = getPrimesBelow(1000)
,然后再拨打getPrimesBelow(1000000)
,我们可能会破解代码:
for(int i=0; i<firstList.size(); ++i)
{
// Unexpectedly looping over way more primes
}
特别是,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
}
赞成
缺点
第三种方法:
返回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))
}
这最后一种方法似乎解决了我上面列出的所有缺点。但是,它有一个丑陋的函数签名和调用语法。
更喜欢哪种方法?为什么?
答案 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;
}
看到结果