在命令式语言中选择SQL-Like

时间:2009-07-01 22:49:31

标签: c++ sql

我正在使用C ++编写一些代码,我工作的很多东西都涉及分析数据集。我经常需要从STL容器中选择一些元素,而且我经常编写这样的代码:

using std::vector;
vector< int > numbers;
for ( int i = -10; i <= 10; ++i ) {
    numbers.push_back( i );
}

vector< int > positive_numbers;
for ( vector< int >::const_iterator it = numbers.begin(), end = numbers.end();
        it != end; ++it 
) {
    if ( number > 0 ) {
        positive_numbers.push_back( *it );
    }
}

随着时间的推移,这个for循环及其中包含的逻辑变得更加复杂和难以理解。像这样的代码不如SQL中的类似SELECT语句那么令人满意,假设我有一个名为numbers的表,其中一列名为“num”而不是std :: vector&lt; int&gt; :

SELECT * INTO positive_numbers FROM numbers WHERE num > 0

这对我来说更具可读性,并且还可以更好地扩展,随着时间的推移,我们的代码库中的许多if语句逻辑变得复杂,依赖于顺序且不可维护。如果我们可以在C ++中执行类似SQL的语句而不必去数据库,我认为代码的状态可能会更好。

有没有一种更简单的方法可以在C ++中实现类似SELECT语句的东西,我可以通过仅描述我想要的对象的特征来创建一个新的对象容器?我还是比较新的C ++,所以我希望模板元编程或聪明的迭代器能解决这个问题。谢谢!

根据前两个答案进行编辑。谢谢,我不知道LINQ实际上是什么。我主要在Linux和OSX系统上编程,并且对跨OSX,Linux和Windows的跨平台感兴趣。所以这个问题的一个受过更多教育的版本是 - 是否有像LINQ for C ++这样的跨平台实现?

7 个答案:

答案 0 :(得分:4)

你几乎完全描述了LINQ。它是.NET 3.5的一个特性,所以你应该可以在C ++中使用它。

答案 1 :(得分:4)

您所描述的功能通常出现在支持闭包,谓词,仿函数等概念的函数式语言中。

上面代码的问题在于它结合了:

  1. 迭代收集的逻辑(for循环)
  2. 要复制到另一个集合的元素必须满足的条件
  3. 将元素从一个集合复制到另一个集合的逻辑
  4. 实际上(1)和(3)是样板文件,只要每次需要迭代将某些元素复制到另一个集合的集合时,它可能只是每次都会改变的条件代码。支持函数式编程的语言消除了这种样板。例如,在Groovy中,您可以使用

    替换上面的for循环
    def positive_numbers = numbers.findAll{it > 0}
    

    尽管C ++不是一种函数式语言,但可能有一些库为STL集合提供功能式编程支持。例如,Apache commons集合(也可能是Google的集合库)为使用Java集合的函数式编程提供支持,即使Java本身不是函数式语言。

答案 2 :(得分:3)

我认为您已经描述了LINQ(C#和.NET 3.5功能)。你看过那个吗?

答案 3 :(得分:2)

LINQ是.NET(或非Windows平台上的Mono)的明显答案,但在C ++中,在STL中自己编写类似的内容并不困难。

使用Boost.Iterator库编写一个“select”迭代器,例如,跳过所有不满足给定谓词的元素。

我相信Boost在他们的文档中已经有一些相关的例子。 或者http://www.boost.org/doc/libs/1_39_0/libs/iterator/doc/filter_iterator.html实际上可以开箱即用。

在任何情况下,在C ++中,基本上可以通过分层迭代器来实现相同的效果。

如果你有一个常规迭代器,它访问序列中的每个元素,你可以将它包装在一个过滤器迭代器中,它会增加底层迭代器,直到找到满足条件的值。然后你甚至可以将它包装在一个“select”迭代器中,将值转换为所需的格式。

这似乎是一个相当明显的想法,但我不知道它的任何完整实现。

答案 4 :(得分:1)

您正在使用STL容器。我建议使用STL algorithms,这很大程度上是出于集合理论。 SQL select被转换为std::find_if的重复应用程序,或std::lower_boundstd::upper_bound的组合(在已排序的容器上)。性能与循环大致相同,但语法更具说明性。

LINQ将为您提供类似的语法和操作,但除非在IQueryable上使用(即数据库中的数据),否则您也不会获得任何性能提升。

之后你最好的选择就是将这些东西放入文件中。无论是BerkelyDBNetCDFHDF5STXXL等等。文件访问速度很慢,但这样做可以让您处理的数据量超过内存量。

答案 5 :(得分:1)

对于你所描述的内容,std :: vector不是一个非常好的选择。这是一个与没有索引的表等效的SQL。最重要的是,使用另一个容器的内容填充一个容器可能是一个合理的性能优化,但不是非常易读,也不是非常惯用。有许多方法可以解决这个问题(IE,不依赖于托管代码.net)。

首选是选择更好的容器。如果您不需要进行稳定的迭代,那么您应该使用std :: set或std :: multi_set。这些容器使用平衡搜索树按顺序存储值。这相当于所有列的简单SQL索引。

std::set< int > numbers;
for ( int i = -10; i <= 10; ++i ) {
    numbers.insert( i );
}

std::set::iterator first = numbers.find(1);
std::set::iterator end = numbers.end();

现在,您可以在{strong> O(n log(n))填充和 O(日志)上从first迭代到end而不浪费任何额外的努力(n))寻求。 I {std::set::iterator

O(1)

如果由于某种原因你必须使用向量,你可以使用std::find_if获得更多惯用的C ++(参见Max Lybbert's回答)

bool isPositive(int n) { return n > 0; }

std::vector< int > numbers;
for ( int i = -10; i <= 10; ++i ) {
    numbers.push_back( i );
}

for ( std::vector< int >::const_iterator end = numbers.end(), 
        iter = std::find_if(numbers.begin(), end, isPositive); // <- first positive value
      iter != end; 
      iter = std::find_if(iter, end, isPositive) // <- advance iter to the next positive
) {

    // iter is guaranteed to be positive here, do something with it!
}

如果你想在没有实际连接到数据库的情况下更加引人注目的SQL,你应该查看Boost,特别是boost::multi_index容器和增强迭代器。

答案 6 :(得分:0)

如果您想在Linux / OS X上试用LINQ,请查看Mono。它是.NET Framework的一个端口,我相信现在包含LINQ。