【问题标题】:How to loop through vectors for specific strings如何遍历特定字符串的向量
【发布时间】:2019-12-01 13:12:34
【问题描述】:

我正在努力声明一个接受向量字段的循环,检查它是第一次出现还是跳转到下一个向量,直到该字段包含新字符串。

我的输入文件 (.csvx) 类似于:

No.; ID; A; B; C;...;Z;
1;1_380; Value; Value; Value;...; Value;
2;1_380; Value; Value; Value;...; Value;
3;1_380; Value; Value; Value;...; Value;
...
41;2_380; Value; Value; Value;...; Value;
42;2_380; Value; Value; Value;...; Value;
...
400000; 6_392; Value; Value; Value;...; Value; 

注意:文件比较大....

我设法将我的文件解析为vector<vector<string> >,并在分号处拆分行以访问任何字段。 现在我想访问第一个“ID”,即 1_380 并从同一行存储参数,然后转到下一个 ID 2_380 并再次存储这些参数等等......

这是我目前的代码:

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <algorithm>
#include <boost/algorithm/string.hpp>

using namespace std;

/*
 * CSVX Reader defined to fetch data from 
 * CSVX file into vectors
 */
class CSVXReader
{
   string fileName, delimiter;
public:
   CSVXReader(string filename, string delm = ";") :
   fileName(filename), delimiter(delm)
   {}
   vector<vector<string> > getData();           //Function to fetch data 
   };                                           //from CSVX file 

/*
 * Parse through CSVX file line by line 
 * and return the data in vector of vector
 * of strings
 */
vector<vector<string> > CSVXReader::getData()
{
   ifstream file(fileName);
   vector<vector<string> > dataList;               //Vector of vector 
                                                   //contains all data

   string line = "";                              
   while (getline(file, line))                  //Iterate through each line 
                                                //and split the content 
                                                //using delimiter
   {
      vector<string> vec;                       //Vector contains a row from 
                                                //input file 
      boost::algorithm::split(vec, line, boost::is_any_of(delimiter));
      dataList.push_back(vec);
   }
file.close();
return dataList;
}


int main(int argc, char** argv) 
{
   CSVXReader reader("file.csvx");                     //Creating an object 
                                                       //of CSVXReader
   vector<vector<string> > dataList = reader.getData();//Get the data from 
                                                       //CSVX file
   for(vector<string> vec : datalist)                  //Loop to go through 
                                                       //each line of 
                                                       //dataList 
                                                       //(vec1,vec2;vec3...)
   if(vec[1] contains "_" && "appears for the first time")
   {store parameters...};
   else{go to next line};
return 0;
}

如您所见,我不知道如何正确声明我的循环... 为了清楚起见,我想检查每个向量“vec”的第二个字段:它是新的吗? -> 存储同一行的数据,如果不是 -> 跳转到下一行,即向量,直到出现新的 ID。

期待任何建议!

【问题讨论】:

  • 在某个地方,您真的应该使用std::unordered_set 来记录重复和/或帮助检测重复。
  • 不确定这是否适用于您的情况,但我会在数据库中导入数据,比如 sqlite,并使用标准数据库 API。
  • 或者至少使用现有的 csv 库(例如 libcs​​v)。
  • @sklott 我宁愿只使用一个 c++ 脚本...
  • @SanderDeDycker 必须先调查一下,但感谢您的提示。

标签: c++ loops vector


【解决方案1】:

因为你写的是伪代码,所以很难写出真正的代码。

但一般来说,如果你想检测一个项目是否已经发生,你可以利用std::unordered_set来实现“首次出现”。

使用你的伪代码:

#include <unordered_set>
//...
std::unordered_set<std::string> stringSet;
//...
for(vector<string>& vec : datalist)
{
    if(vec[1] contains "_" && !stringSet.count(vec[1]))
    {
         //...
         stringSet.insert(vec[1]);
    }
}

条件检查项目是否在 unordered_set 中。如果是,则跳过,如果不是,则处理该项目并将其添加到 unordered_set。

【讨论】:

  • 您的 for 循环正在对每个向量进行不必要的复制,并且您也不需要在 unordered_set 中进行 2 次查找。
  • 实际上,除了最后 5 行之外,我的代码是真实的......但它可能是垃圾;)这似乎是一个非常有前途的解决方案,谢谢!
【解决方案2】:

基本上,您不需要其他答案提供的所有代码。您只需一条语句即可将数据复制到您想要的位置。

假设您已经在dataList 中读取了您的数据。并且您定义了一个新的std::vector&lt;std::vector&lt;std::string&gt;&gt; parameter{};,您希望在其中存储唯一结果。

算法库有一个名为std:copy_if 的函数。如果谓词(条件)为真,这将仅复制数据。您的条件是一行与前一行不同。然后它是一个带有新数据的新行,您将复制它。如果一行等于它的前一行数据,则不要复制它。

所以,我们会记住最后一行的重要数据。然后在下一行将数据与存储的值进行比较。如果不同,则存储参数。如果不是,那么不是。每次检查后,我们将当前值分配给最后一个值。作为初始的“最后一个值”,我们将使用一个空字符串。所以第一行总是不同的。该语句将如下所示:

std::copy_if(dataList.begin(), dataList.end(), std::back_inserter(parameter),
    [lastID = std::string{}](const std::vector<std::string> & sv) mutable {
        bool result = (lastID != sv[1]);
        lastID = sv[1];
        return result;
    }
);

所以我们将所有数据从dataList 的开头到结尾复制到parameter 向量中,当且仅当源向量中的第二个字符串(索引=1)不同于我们旧的记忆值.

相当简单。

另一个优化是,立即整理出正确的参数,而不是首先存储包含所有数据的完整向量,而是只存储必要的数据。这将大大减少必要的内存。

将你的while循环修改为:

string line = "";                              
string oldValue{};
while (getline(file, line))                 //Iterate through each line 
                                            //and split the content 
                                            //using delimiter
{
    vector<string> vec;                       //Vector contains a row from 
                                                //input file 
    boost::algorithm::split(vec, line, boost::is_any_of(delimiter));

    if (oldValue != vec[1]) {
        dataList.push_back(vec);
    }
    oldValue = vec[1];
}

这样你就可以从一开始就做对了。

另一个解决方案如下所示

#include <vector>
#include <iostream>
#include <string>
#include <iterator>
#include <regex>
#include <fstream>
#include <sstream>
#include <algorithm>

std::istringstream testFile{R"(1;1_380; Value1; Value2; Value3; Value4
2;1_380; Value5; Value6; Value7; Value8
3;1_380; Value9 Value10 
41;2_380; Value11; Value12; Value13
42;2_380; Value15
42;2_380; Value16
500;3_380; Value99
400000; 6_392; Value17; Value18; Value19; Value20
400001; 6_392; Value21; Value22; Value23; Value24)"
};


class LineAsVector {    // Proxy for the input Iterator
public:
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, LineAsVector& lv) {

        // Read a line
        std::string line; lv.completeLine.clear();
        std::getline(is, line); 

        // The delimiter
        const std::regex re(";");

        // Split values and copy into resulting vector
        std::copy(  std::sregex_token_iterator(line.begin(), line.end(), re, -1),
                    std::sregex_token_iterator(),
                    std::back_inserter(lv.completeLine));
        return is; 
    }

    // Cast the type 'CompleteLine' to std::string
    operator std::vector<std::string>() const { return completeLine; }
protected:
    // Temporary to hold the read vector
    std::vector<std::string> completeLine{};
};

int main()
{

    // This is the resulting vector which will contain the result
    std::vector<std::vector<std::string>> parameter{};


    // One copy statement to copy all necessary data from the file to the parameter list
    std::copy_if (
        std::istream_iterator<LineAsVector>(testFile),
        std::istream_iterator<LineAsVector>(),
        std::back_inserter(parameter),
        [lastID = std::string{}](const std::vector<std::string> & sv) mutable {
            bool result = (lastID != sv[1]);
            lastID = sv[1];
            return result;
        }
    );


    // For debug purposes: Show result on screen
    std::for_each(parameter.begin(), parameter.end(), [](std::vector<std::string> & sv) {
        std::copy(sv.begin(), sv.end(), std::ostream_iterator<std::string>(std::cout, " "));
        std::cout << '\n';
        } 
    );
    return 0;
}

请注意:在 main 函数中,我们在一个语句中完成所有操作:std::copy_if。在这种情况下,源是std::istream,所以是std::ifstream(一个文件)或者你想要的任何东西。在 SO 我使用 std::istringstream 因为我不能在这里使用文件。但它是一样的。只需替换std::istream_iterator 中的变量即可。我们使用std::istream_iterator 遍历文件。

可惜没人会读这个。 . .

【讨论】:

  • 感谢您的回复。它看起来很合乎逻辑,但是当我自己尝试时,我无法使用我的示例文件运行它(你的字符串列表有效)。无论如何...我是个初学者,因此在阅读您的编码风格时迷路了。如果您能更详细地解释您的代码或告诉我为什么要使用 istream_iterator 两次(或重载提取器的原因),我将不胜感激!
  • 编辑:使用您的 if() 函数成功运行修改后的循环!现在我必须添加更多条件。非常感谢@Armin!
【解决方案3】:

好的,伙计们,我正在玩弄我的代码,并意识到@Armins 的第二个解决方案(修改的 while 循环)不考虑无序列表,即如果一个元素在很久以后再次出现,它会与前一个元素(oldValue ) 并插入,尽管它已经存在于我的容器中...

在阅读了一些内容后(显然还有更多内容),我倾向于@Paul 的unordered_set。我的第一个问题就出现在这里:你为什么不建议 set 呢?根据我的发现,unordered_set 对于搜索操作显然更快。在我个人非常有限的头脑中,这很难理解......但我不想在这里挖掘太深。 这是你的理由吗?还是我错过了其他优势?

尽管有你的建议,我还是尝试使用set,这在我的情况下似乎更好,因为更有序的方式。我的代码再次拒绝运行:

set<vector<string> > CSVReader::getData() {

ifstream file(fileName);

set<vector<string> > container;

string line = "";
string uniqueValue{};

while (getline(file, line))                          //Iterate through each line and split the content using delimiter
{
    //Vector contains a row from RAO file
    vector<string> vec;                        
    boost::algorithm::split(vec, line, boost::is_any_of(delimiter));

    uniqueValue = vec[2];

    //Line (or vector) is added to container if the uniqueValue, e.g. 1_380, appears for the first time                   

    if(!container.count(uniqueValue))
    {
        container.insert(vec);
    }

}

file.close();
return container;  
}

错误提示:

error: no matching function for call to 'std::set<std::vector<std::__cxx11::basic_string<char> > >::count(std::__cxx11::string&)'
     if(!localDetails.count(localDetail))

既然我效仿了你,那我做错了什么?

PS:只是阅读有关 SO 政策的信息...希望这个额外的问题是可以接受的

【讨论】:

  • 我想我发现了我的问题。由于container 包含 的向量,并且我正在我的集合中搜索string,这将不起作用...有没有人有想法解决这个问题?
猜你喜欢
  • 1970-01-01
  • 2018-05-14
  • 1970-01-01
  • 1970-01-01
  • 2021-01-11
  • 1970-01-01
  • 2017-12-29
  • 2015-05-29
  • 1970-01-01
相关资源
最近更新 更多