【问题标题】:C++ - Efficient way to group double vectors following a certain criteriaC ++ - 按照特定标准对双向量进行分组的有效方法
【发布时间】:2016-01-24 17:37:08
【问题描述】:

我有一个使用以下方案保存在类似 CSV 文件中的对象列表:

[value11],...,[value1n],[label1]

[value21],...,[value2n],[label2]

...

[valuen1],...,[valuenn],[labeln]

(每一行都是一个对象,即一个双精度向量和相应的标签)。 我想用特定的自定义标准(即该组所有对象的第 n 和第(n + 1)位置的相同值)将它们收集在组中。我需要以最有效的方式做到这一点,因为文本文件包含数百个对象。我正在使用 C++ 编程语言。

为此,首先我将所有 CSV 行加载到一个简单的自定义容器中(使用 getObject、getLabel 和导入方法)。然后我使用下面的代码来阅读它们并进行分组。 “verifyGroupRequirements”是一个函数,如果满足组条件,则返回 true,否则返回 false。

for (size_t i = 0; i < ObjectsList.getSize(); ++i) {
  MyObject currentObj;
  currentObj.attributes = ObjectsList.getObject(i);
  currentObj.label = ObjectsList.getLabel(i);

  if (i == 0) {
    // Sequence initialization with the first object
    ObjectsGroup currentGroup = ObjectsGroup();

    currentGroup.objectsList.push_back(currentObj);
    tmpGroupList.push_back(currentGroup);
  } else {
    // if it is not the first pattern, then we check sequence conditions
    list<ObjectsGroup>::iterator it5;

    for (it5 = tmpGroupList.begin(); it5 != tmpGroupList.end(); ++it5) {
      bool AddObjectToGroupRequirements =
        verifyGroupRequirements(it5->objectsList.back(), currentObj) & 
        ( (it5->objectsList.size() < maxNumberOfObjectsPerGroup) |
        (maxNumberOfObjectsPerGroup == 0) );

      if (AddObjectToGroupRequirements) {
        // Object added to the group
        it5->objectsList.push_back(currentObj);

        break;
      } else {
        // If we can't find a group which satisfy those conditions and we
        // arrived at the end of the list of groups, then we create a new
        // group with that object.
        size_t gg = std::distance(it5, tmpGroupList.end());

        if (gg == 1) {
          ObjectsGroup tmp1 = ObjectsGroup();
          tmp1.objectsList.push_back(currentObj);

          tmpGroupList.push_back(tmp1);

          break;
        }
      }
    }
  }

  if (maxNumberOfObjectsPerGroup > 0) {
    // With a for loop we can take all the elements of 
    // tmpGroupList which have reached the maximum size
    list<ObjectsGroup>::iterator it2;

    for (it2 = tmpGroupList.begin(); it2 != tmpGroupList.end(); ++it2) {
      if (it2->objectsList.size() == maxNumberOfObjectsPerGroup)
        finalGroupList.push_back(*it2);
    }

    // Since tmpGroupList is a list we can use remove_if to remove them
    tmpGroupList.remove_if(rmCondition);
  }
}

if (maxNumberOfObjectsPerGroup == 0) 
  finalGroupList = vector<ObjectsGroup> (tmpGroupList.begin(), tmpGroupList.end());
else {
  list<ObjectsGroup>::iterator it6;

  for (it6 = tmpGroupList.begin(); it6 != tmpGroupList.end(); ++it6)
    finalGroupList.push_back(*it6);
}

其中 tmpGroupList 是 list&lt;MyObject&gt;,finalGroupList 是 vector&lt;MyObject&gt;,rmCondition 是一个布尔函数,如果 ObjectsGroup 的大小大于固定值,则返回 true。 MyObject 和 ObjectsGroup 是两个简单的数据结构,写法如下:

// Data structure of the single object
class MyObject {
  public:
    MyObject(
          unsigned short int &spaceToReserve,
          double &defaultContent,
          string &lab) {

      attributes = vector<double>(spaceToReserve, defaultContent);
      label = lab;
    }
    vector<double> attributes;
    string label;
};

// Data structure of a group of object
class ObjectsGroup {
  public:
    list<MyObject> objectsList;
    double health;
};

这段代码似乎可以工作,但确实很慢。因为,正如我之前所说,我必须将它应用于大量对象,有没有办法改进它并使其更快?谢谢。

[编辑] 我想要实现的是创建一组对象,其中每个对象都是 vector&lt;double&gt;(从 CSV 文件中获取)。所以我在这里要问的是,有没有比上面代码示例中公开的更有效的方法来分组收集这些对象?

[EDIT2] 我需要使用所有这些向量进行分组。

【问题讨论】:

  • 在您的for ( it5... 循环中,您使用的是按位&amp;|,而不是布尔值&amp;&amp;||。这真的是你想要的吗?
  • @stefan 从理论上的 C++ 角度来看,我认为正确的方法是在这种情况下使用布尔运算符 &amp;&amp;||(如果我错了,请纠正我),但从从实际的角度来看,我看不出它们之间有任何性能差异(至少在这个例子中)。
  • 我的评论不是针对潜在的性能问题,而是针对一般代码质量。在我看来,代码应该始终表达意图。在这种情况下,不应该对布尔值进行按位运算。

标签: c++ list vector grouping


【解决方案1】:

所以,我正在阅读您的问题...

...我想按照一定的习惯分组收集 标准(即所有的第 n 个和第 (n+1) 个位置的相同值 该组的对象)...

好的,我读了这部分,并继续阅读......

...而且我需要以最有效的方式做到这一点,因为文本文件 包含数百个对象...

我仍然和你在一起,很有意义。

...为此,首先我加载所有 CSV 行...

{thud} {crash} {响亮的爆炸声}

好的,我在此处停止阅读,并没有过多关注问题的其余部分,包括大型代码示例。这是因为我们从一开始就有一个基本问题:

1) 您说您的意图通常是只阅读一小部分 这个巨大的 CSV 文件的一部分,以及...

2) ... 为此,您将整个 CSV 文件加载到一个相当复杂的数据结构中。

这两种说法相互矛盾。您正在从文件中读取大量值。您正在为每个值创建一个对象。根据您的问题的前提,您将拥有大量这些对象。但是,当一切都说完了,你只会看看其中的一小部分,然后把其余的扔掉吗?

您正在做大量工作,可能会占用大量内存和 CPU 周期,加载庞大的数据集,却忽略了其中的大部分。您想知道为什么会遇到性能问题?对我来说似乎很简单。

这样做的替代方法是什么?好吧,让我们把这整个问题翻过来,然后一点一点地解决它。让我们读取一个 CSV 文件,一次一行,解析 CSV 格式文件中的值,然后将生成的字符串传递给 lambda。

类似这样的:

template<typename Callback> void parse_csv_lines(std::ifstream &i,
                                                 Callback &&callback)
{
    std::string line;

    while (1)
    {
        line.clear();
        std::getline(i, line);

        // Deal with missing newline on the last line...

        if (i.eof() && line.empty())
             break;

        std::vector<std::string> words;

        // At this point, you'll take this "line", and split it apart, at
        // the commas, into the individual words. Parsing a CSV-
        // formatted file. Not very exciting, you're doing this
        // already, the algorithm is boring to implement, you know
        // how to do it, so let's say you replace this entire comment
        // with your boiler-plate CSV parsing logic from your existing
        // code

        callback(words);
    }
}

好的,现在我们已经完成了解析 CSV 文件的任务。现在,假设我们要完成您在问题开始时设定的任务,抓住每个第 n 和第 n+1 个位置。所以……

void do_something_with_n_and_nplus1_words(size_t n)
{
    std::ifstream input_file("input_file.csv");

    // Insert code to check if input_file.is_open(), and if not, do
    // whatever

    parse_csv_lines(input_file,
                    [n]
                    (const auto &words)
                    {
                       // So now, grab words[n] and words[n+1]
                       // (after checking, of course, for a malformed
                       // CSV file with fewer than "n+2" values)
                       // and do whatever you want with them.
                    });
}

就是这样。现在,您最终只需读取 CSV 文件,并完成从每个 CSV 文件中提取第 n 个和第 n+1 个值所需的绝对最少的工作量。想出一种工作量更少的方法将是相当困难的(当然,与 CSV 解析和字缓冲区相关的微优化除外;或者可能会放弃std::ifstream 的开销,而是映射整个文件,然后通过扫描其 mmap-ed 内容来解析它,类似的东西),我想。

对于其他类似的一次性任务,只需要 CSV 文件中的少量值,只需编写适当的 lambda 即可将它们提取出来。

也许,您需要从大型 CSV 文件中检索两个或更多值的子集,并且您想读取 CSV 文件一次,也许?好吧,很难给出最好的通用方法。这些情况中的每一种都需要单独分析,以选择最佳方法。

【讨论】:

  • 嗨,CSV 文件的每一行都是一个双向量,我需要按照一定的标准将它们全部收集起来。所以我没有加载整个数据集来处理它的一小部分。
  • 这与实际问题相冲突:“我加载了所有 CSV 行”。那是你写的。
  • 我谈到 CSV 只是为了让您了解我正在加载的数据的布局。因此,将这些向量视为完全加载到内存中,然后我想将它们全部组成组。例如:line1 在第一个和第二个属性中具有值 10 和 5,而 line7 在同一位置具有相同的值,所以我将它们放在一个容器中。我上面显示的代码示例有效,但我想它效率不高。
  • 在假设您的性能问题与访问数据有关之前,您应该实际验证这一点,并获取一些运行时分析数据:将整个 CSV 文件读入内存需要多长时间,与多长时间需要访问您正在查找的数据。
  • 您需要进行更多分析。例如:“MyObject currentObj;currentObj.attributes=ObjectsList.getObject(i);currentObj.label=ObjectsList.getLabel(i);”看起来这会复制几个对象。后来,整个事情都被 push_back() 编辑了——另一个副本发生了。复制大对象显然很昂贵。与其直接使用对象,不如考虑使用智能指针 (std::shared_ptr) 进行重新设计,因此类似的代码最终只是移动了一些智能指针,并增加了引用计数。我希望这会比复制大对象快得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-18
相关资源
最近更新 更多