【问题标题】:computing column sums of matrix vector<vector<double> > with iterators?用迭代器计算矩阵向量<vector<double>>的列总和?
【发布时间】:2013-02-17 19:08:39
【问题描述】:

在之前的帖子column vector with row means -- with std::accumulate? 中,我询问是否可以使用 STL 功能来计算矩阵的行均值

vector< vector<double> > data ( rows, vector<double> ( columns ) );

@benjaminlindley 的最佳答案不仅是我在寻找的东西,而且是一件美丽的事情。永远充满希望,我认为计算列均值会很容易,所以 STL 相当于

vector<double> colmeans( data[0].size() );
    for ( int i=0; i<data.size(); i++ )
        for ( int j=0; j<data[i].size(); j++ )            
            colmeans[j] += data[i][j]/data.size();

平均值不是在每个 vector&lt;double&gt; 内计算,而是在所有向量中的相同索引中计算:

colmeans[0]       == ( data[0][0] + data[1][0] + ... data[rows][0] ) / rows
colmeans[1]       == ( data[0][1] + data[1][1] + ... data[rows][1] ) / rows
colmeans[2]       == ( data[0][2] + data[1][2] + ... data[rows][2] ) / rows
...
colmeans[columns] == ( data[0]   [columns] + 
                       data[1]   [columns] + 
                       ... 
                       data[rows][columns] ) / rows

结果完全不同——accumulate 不想处理向量的向量。是否可以通过 [] 运算符使用累积?我什至无法想出一个看起来不正确的中间形式(摆脱for ifor j 循环)。

accumulate[] 运算符?还是bind

【问题讨论】:

  • 如果您认为 Benjamin 的回答很好(顺便说一句,确实如此),您应该将其标记为已接受。
  • 老实说,这其中最困难的部分将是您的外向量向量中的潜在短向量。这是使用vector&lt;vector&lt;type&gt;&gt; 时的基本问题。不能保证每个内部向量的大小相同(当然,除非在您自己的代码中假设它,这首先会填充这个东西)。查找行均值时这不是问题,因为您并不真正关心有多少列。
  • 所以@WhozCraig 你是说找到列均值时有问题吗?顺便说一句,满足内部向量大小相同的保证,初始化后不会改变——data 基本上是rows x columns 的矩阵
  • @alle_meije 我的意思是选择的数据模型(向量的向量)允许任何给定行不一定具有与其他行相同的列数的意外可能性行。这是一个数据模型问题,而不是矩阵问题。只要您强制每行在代码中具有相同的列数(如注释),就可以了。
  • 要让std::accumulate 用于此目的,您必须实现自己的特殊迭代器,它可以跨向量跳转,这比简单地使用 for 循环要多得多。您可能可以在外部库(如 boost)中找到简单的解决方案,但在标准库中找不到。

标签: c++ vector stl mean accumulate


【解决方案1】:

这是我想出的,使用for_eachtransform

std::vector<std::vector<double>> data { {1,2,3}, {1,2,3}, {1,2,3} };

std::vector<double> colsums( data[0].size() ); // initialize the size
                                                // to number of columns

std::for_each(data.begin(), data.end(),

    [&](const std::vector<double>& row)
    {
        // Use transform overload that takes two input ranges.
        // Note that colsums is the second input range as well as the output range.
        // We take each element of the row and add it to the corresponding
        // element of colsums vector:
        std::transform(row.begin(), row.end(), colsums.begin(), colsums.begin(),
                       [](double d1, double d2) { return d1 + d2; });
    });

std::cout << "Column means: ";
std::transform(
    colsums.begin(), colsums.end(),
    std::ostream_iterator<double>(std::cout, " "),
    [&data](double d) { return d / data.size(); });

LWS Demo

【讨论】:

  • 我现在在https://github.com/amwink/bias/blob/master/cpp/fastecm/fastecm.cpp使用这个for_each解决方案
【解决方案2】:

首先让我声明你真的不应该嵌套 std::vectors。 除此之外,我得到了一些解决方案,它当然比您的初始代码更长,但从长远来看可以节省:

#include <vector>
#include <boost/iterator/iterator_adaptor.hpp>
#include <boost/iterator/counting_iterator.hpp>

typedef std::vector<std::vector<double> > Data;

struct ColumnElement : boost::iterator_adaptor<ColumnElement,
                                                Data::const_iterator,
                                                const double> {
        int col;

        ColumnElement(int col, const Data::const_iterator &iter)
        : iterator_adaptor(iter), col(col)
        {}
        const double& dereference()const { return (*base())[col]; }
};

struct Column {
        int col;
        const Data *data;

        Column(int col, const Data *data) : col(col), data(data) {}
        ColumnElement begin()const { return ColumnElement(col, data->begin()); }
        ColumnElement end()const { return ColumnElement(col, data->end()); }
        int size()const { return std::distance(begin(), end()); }
};

struct Columns : boost::iterator_adaptor<Columns, boost::counting_iterator<int>,
                                        Column, boost::use_default, Column> {
        const Data *data;

        Columns(int col, const Data *data): iterator_adaptor(col), data(data) {}

        Column dereference()const { return Column(*base(), data); }
};

Columns columnsBegin(const Data &data) { return Columns(0, &data); }
Columns columnsEnd(const Data &data) {
        return Columns(data.empty() ? 0 : data.front().size(), &data);
}

这可以简而言之:

double Mean(const Column &d) {
        return std::accumulate(d.begin(), d.end(), 0.0) / d.size();
}

int main() {
        Data data = {   {1, 2, 3},
                        {2, 2, 2},
                        {9, 8, 7}};
        std::vector<double> colMeans(data[0].size());
        std::transform(columnsBegin(data), columnsEnd(data), 
                       colMeans.begin(), Mean);
        std::copy(colMeans.begin(), colMeans.end(),
                  std::ostream_iterator<double>(std::cout, ","));
        std::cout << "\n";
}

我使用了一些 boost 功能来缩短它,但它可以在没有 boost 的情况下完成(但要长得多)。

这个想法是创建一个遍历所有列的迭代器(称为Columns,只是为了简短)和一个迭代一个列的所有元素的迭代器(ColumnElement,也缩短了,应该更好地命名为ColumnElementIterator)和Column 代表一列所有元素的范围。

【讨论】:

  • 谢谢,非常感谢。我对易于维护和易于将代码交给他人的 STL 解决方案感到好奇。不过,Boost 似乎很难被忽视。在我的情况下 - 在密集矩阵中相对较少的数量( 100,000 个元素),我不确定 2 倍的速度增益 [scicomp.stackexchange.com/questions/3159] 是否会影响额外的编程努力。如果您知道数据的维度 + 维度,那么嵌套向量真的是个坏主意吗?
  • 嵌套向量有几个缺点(分散的数据、大小不一致的可能性、更难访问)。通常的方法是将数据存储在std::vector&lt;double&gt; 中,并围绕它包装一个类,将二维(或更多)维索引映射到向量的一维索引范围。你应该看看矩阵的其他实现。如果您的代码不是用于学术目的(比如学习),我建议您查看一些满足您需求的库。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-19
  • 1970-01-01
  • 2017-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多