将一串数字解析为int向量的最快方法

Quickest Way to parse a string of numbers into a vector of ints

本文关键字:向量 int 方法 数字 一串      更新时间:2023-10-16

我想知道将一串数字解析为int向量的最快方法是什么。我的情况是,我将有数百万行数据,格式如下:

>Header-name
ID1    1    1   12
ID2    3    6   234
.
.
.
>Header-name
ID1    1    1   12
ID2    3    6   234
.
.
.

我想放弃"Header name"字段(或者稍后使用它进行排序),然后忽略ID字段,然后将剩余的三个int放入向量中。我意识到,我可以在几个带有逻辑的for循环中使用boost拆分和词法转换来忽略某些数据,但我不确定这是否会给我带来最快的解决方案。我看过boost spirit,但我真的不知道如何使用它。boost或STL都可以。

你必须使用boost吗?这个功能我已经用了一段时间了。我相信我是从Accelerated C++中得到的,并且从那以后一直在使用它。您的分隔符似乎是一个制表符或多个空格。如果将分隔符传递给",它可能会起作用。不过,我认为这将取决于实际情况。

std::vector<std::string> split( const std::string& line, const std::string& del )
{
        std::vector<std::string> ret;
        size_t i = 0;
        while ( i != line.size() ) {
                while ( ( i != line.size() ) && ( line.substr(i, 1) == del ) ) {
                        ++i;
                }
                size_t j = i;
                while ( ( j != line.size() ) && ( line.substr(j, 1) != del ) ) {
                        ++j;
                }
                if ( i != j ) {
                        ret.push_back( line.substr( i, j - i ) );
                        i = j;
                }
        }
        return ret;
}

你可以用这个得到每一行:

int main() {
    std::string line;
    std::vector<std::string> lines; 
    while ( std::getline( std::cin, line ) ) {
        lines.push_back( line );
    }
    for ( auto it = lines.begin(); it != lines.end(); it++ ) {
        std::vector<string> vec = split( (*it) );
        // Do something
    }
}

您可以通过快速修改使其返回std::vector。使用atoi(myString.c_str())将每个字符串设为int此外,您还需要签入以跳过标题。应该是琐碎的。

请注意,我没有在上面编译过。)

关于这个特定的问题,如果你想要最快的,我建议你一次手动解析1个字符。Boost Spirit可能会紧随其后,为您节省大量丑陋的代码。

一次手动解析一个字符是高速的关键,因为即使是像atoi和strtol这样优化良好的转换器也必须处理许多不同的数字表示,而您的示例似乎意味着您只对纯无符号整数感兴趣。格式化的IO(scanf、运算符<<等)非常慢。将行读入中间字符串可能会产生明显的成本。

您的问题很简单,可以手动解析,假设标题行不包含任何"\t"(并且假设没有任何IO或格式错误):

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
std::vector<unsigned> parse(std::istream &is)
{
    bool skipField = true;
    char c;
    unsigned value = 0;
    std::vector<unsigned> result;
    while (is.get(c))
    {
        if (('t' == c) || ('n' == c))
        {
            if (!skipField)
            {
                result.push_back(value);
            }
            skipField = ('n' == c);
            value = 0;
        }
        else if (!skipField)
        {
            value *= 10;
            value += (c - '0');
        }
    }
    return result;
}
int main()
{
    const std::string data = ">Header-namenID1t1t1t12nID2t3t6t234n";
    std::istringstream is(data);
    const std::vector<unsigned> v = parse(is);
    for (unsigned u: v)
    {
        std::cerr << u << std::endl;
    }
}

和往常一样,对于这样令人愉快的未指定问题,没有什么比展示做"一件事"的"方法"更多的了。在这种情况下,我使用了Boost Spirit(因为你提到了它):

解析为扁平容器

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted.hpp>
#include <map>
std::string const input(
    ">Header - name1n"
    "ID1    1    1   12n"
    "ID2    3    6   234n"
    ">Header - name2n"
    "ID3    3    3   14n"
    "ID4    5    8   345n"
);
using Header    = std::string;
using Container = std::vector<int>;
using Data      = std::map<Header, Container>;
int main()
{
    namespace qi = boost::spirit::qi;
    auto f(input.begin()), l(input.end());
    Data data;
    bool ok = qi::phrase_parse(f, l,
        *(
            '>' >> qi::raw[*(qi::char_ - qi::eol)] >> qi::eol
           >> *(!qi::char_('>') >> qi::omit[qi::lexeme[+qi::graph]] >> *qi::int_ >> qi::eol)
        ), qi::blank, data);
    if (ok)
    {
        std::cout << "Parse successn";
        for (auto const& entry : data)
        {
            std::cout << "Integers read with header '" << entry.first << "':n";
            for (auto i : entry.second)
                std::cout << i << " ";
            std::cout << "n";
        }
    }
    else
    {
        std::cout << "Parse failedn";
    }
    if (f != l)
        std::cout << "Remaining input: '" << std::string(f, l) << "'n";
}

打印

Parse success
Integers read with header 'Header - name1':
1 1 12 3 6 234
Integers read with header 'Header - name2':
3 3 14 5 8 345

正在解析到嵌套容器中

当然,如果你想为每一行单独的矢量(不要期望效率),那么你可以简单地替换typedef:

using Container = std::list<std::vector<int> >; // or any other nested container
// to make printing work without further change:
std::ostream& operator<<(std::ostream& os, std::vector<int> const& v)
{
    os << "[";
    std::copy(v.begin(), v.end(), std::ostream_iterator<int>(os, " "));
    return os << "]";
}

打印

Parse success
Integers read with header 'Header - name1':
[1 1 12 ] [3 6 234 ]
Integers read with header 'Header - name2':
[3 3 14 ] [5 8 345 ]

您只能使用以下内容,而不是我使用的字符串数组,您将从文件中获取字符串

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
int main() 
{
    std::string s[] = { "ID1    1    1   12", "ID2    3    6   234" };
    std::vector<int> v;
    for ( const std::string &t : s )
    {
        std::istringstream is( t );
        std::string tmp;
        is >> tmp;
        v.insert( v.end(), std::istream_iterator<int>( is ), 
                           std::istream_iterator<int>() );
    }                         
    for ( int x : v ) std::cout << x << ' ';
    std::cout << std::endl;
    return 0;
}

输出为

1 1 12 3 6 234 

至于标头,您可以检查tmp是否是标头,如果是,您将跳过此记录。

这是一个简化版

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
int main() 
{
    std::string s[] = 
    { 
        "ID1    1    1   12", 
        ">Header-name", 
        "ID2    3    6   234" 
    };
    std::vector<int> v;
    for ( const std::string &t : s )
    {
        std::istringstream is( t );
        std::string tmp;
        is >> tmp;
        if ( tmp[0] == '>' ) continue;
        v.insert( v.end(), std::istream_iterator<int>( is ), 
                           std::istream_iterator<int>() );
    }                         
    for ( int x : v ) std::cout << x << ' ';
    std::cout << std::endl;
    return 0;
}

输出将与上述相同。