【问题标题】:Using STL/Boost to find and modify matching elements in a vector使用 STL/Boost 查找和修改向量中的匹配元素
【发布时间】:2009-09-10 18:42:24
【问题描述】:

假设我有一个这样声明的向量:

struct MYSTRUCT
{
 float a;
 float b;
};

std::vector<MYSTRUCT> v;

现在,我想找到 v 中所有共享相同 a 的元素,并对它们的 b 取平均值,即

假设 v 包含这五个元素 {a, b}:{1, 1}, {1, 2}, {2, 1}, {1, 3}, {2, 2}

我想得到 v[0]、v[1]、v[3](其中 a 为 1)和平均 b:(1 + 2 + 3)/3 = 2,以及 v[2] 和 v [4](其中 a 为 2)和平均 b:(1+2)/2 = 1.5

之后 v 将如下所示:{1, 2}, {1, 2}, {2, 1.5}, {1, 2}, {2, 1.5}

我对 STL 或 Boost 不是很熟悉,所以我只能弄清楚如何在 C++ 中以“蛮力”方式执行此操作,但我猜测 STL(for_each?)和 Boost(lambda?)库可以更优雅地解决这个问题。

编辑仅供参考,这是我的(工作)蛮力方法:

for(int j = 0; j < tempV.size(); j++)
{
    MYSTRUCT v = tempV.at(j);
    int matchesFound = 0;

    for(int k = 0; k < tempV.size(); k++)
    {
        if(k != j && v.a == tempV.at(k).a)
        {
            v.b += tempV.at(k).b;
            matchesFound++;
        }
    }

    if(matchesFound > 0)
    {
        v.b = v.b/matchesFound;
    }

    finalV.push_back(v);
}

【问题讨论】:

标签: c++ stl boost vector predicate


【解决方案1】:

只是大声思考,这可能会很愚蠢:

struct Average {
    Average() : total(0), count(0) {}
    operator float() const { return total / count; }
    Average &operator+=(float f) {
        total += f;
        ++count;
    }
    float total;
    int count;
};

struct Counter {
    Counter (std::map<int, Average> &m) : averages(&m) {}
    Counter operator+(const MYSTRUCT &s) {
         (*averages)[s.a] += s.b;
         return *this;
    }
    std::map<int, Average> *averages;
};

std::map<int, Average> averages;
std::accumulate(v.begin(), v.end(), Counter(averages));
BOOST_FOREACH(MYSTRUCT &s, v) {
    s.b = averages[s.a];
}

嗯。不完全愚蠢,但也许也不引人注目......

【讨论】:

  • 我有点喜欢那个,但是我很喜欢用折叠和闭包实现的东西=)
  • 我刚刚注意到Average 类是可怕的:你可以将它们中的两个加在一起,得到一些完全没有意义的东西。不过,它不应该用于具有平均值的通用推理。
  • 没错,我肯定会摆脱运算符 float()。但除此之外,它似乎还不错
【解决方案2】:

解决方案示意图:

sort(v.begin(), v.end());
vector<MYSTRUCT>::iterator b = v.begin(), e = v.end();
while (b != e) {
    vector<MYSTRUCT>::iterator m = find_if(b, e, bind(&MYSTRUCT::a, _1) != b->a);
    float x = accumulate(b, m, 0.f, _1 + bind(&MYSTRUCT::b,_2)) / (m-b);
    for_each(b, m, bind(&MYSTRUCT::a, _1) = x);
    b = m;
}

不过,这不是一个很好的选择,因为它不完全符合要求(感谢排序),而且我仍然感觉不干净。我认为一些 filter_iterators 和 transform_iterators 或其他东西可能会给出更实用的答案。

【讨论】:

  • 您能解释一下为什么您认为这不是一个很好的解决方案吗?
  • @fbrereto,正如@me22 所说,主要问题是它改变了 v 的顺序(通过排序),因此它无法提供所请求的确切结果(其中 v 的顺序是完整的)。
  • std::find_if 不需要排序序列(sgi.com/tech/stl/find_if.html)。 std::find 也没有。
  • @Max:我正在排序,以便相同的 MYSTRUCT::a 相邻,让我运行累积。
  • 啊。谢谢。我错过了积累。
【解决方案3】:

另一种方法,这个方法不是就地的,尽管我认为它在时间复杂性方面渐近相同。

typedef map<float, vector<float>> map_type;
map_type m;
BOOST_FOREACH(MYSTRUCT const &s, v) {
    m[s.a].push_back(s.b);
}
BOOST_FOREACH(map_type::reference p, m) {
    float x = accumulate(p.second.begin(), p.second.end(), 0.0f) / p.second.size();
    p.second.assign(1, x);
}
BOOST_FOREACH(MYSTRUCT &s, v) {
    s.b = m[s.a].front();
}

不过,这只是一种稍微优雅的方式来编写蛮力解决方案,而不是一种很好的函数式方式。

【讨论】:

  • 功能代码,顾名思义,不会修改现有的数据结构,这里的问题规范就是修改现有的数据结构,所以我认为这段代码很好.
【解决方案4】:

也许是蛮力方法?...

struct MYAVG
{
    int count;
    float avg;  
};

// first pass - calculate averages
for ( vector < MYSTRUCT >::iterator first = v.begin(); 
      first != v.end(); ++first )
{
    MYAVG myAvg;
    myAvg.count = 1;
    myAvg.avg = first->b;

    if ( mapAvg.find( first->a ) == mapAvg.end() )
        mapAvg[ first->a ] = myAvg;
    else
    {
        mapAvg[ first->a ].count++;
        mapAvg[ first->a ].avg = 
            ( ( mapAvg[ first->a ].avg * ( mapAvg[ first->a ].count - 1 ) ) 
                + myAvg.avg ) / mapAvg[ first->a ].count;
    }
}

// second pass - update average values
for ( vector < MYSTRUCT >::iterator second = v.begin(); 
      second != v.end(); ++second )
    second->b = mapAvg[ second->a ].avg;

我已经用您提供的值对此进行了测试,并获得了所需的向量 - 它并不是完全最优的,但我认为它很容易遵循(可能比复杂的算法更可取)。

【讨论】:

    【解决方案5】:

    避免 C 风格!这不是 C++ 的设计目的。我想强调清晰性和可读性。

    #include <algorithm>
    #include <iostream>
    #include <map>
    #include <numeric>
    #include <vector>
    
    #include <boost/assign/list_of.hpp>
    
    using namespace std;
    using namespace boost::assign;
    
    struct mystruct
    {
      mystruct(float a, float b)
        : a(a), b(b)
      { }
    
      float a;
      float b;
    };
    
    vector <mystruct> v =
      list_of ( mystruct(1, 1) ) (1, 2) (2, 1) (1, 3) (2, 2);
    
    ostream& operator<<(
      ostream& out, mystruct const& data)
    {
      out << "{" << data.a << ", " << data.b << "}";
      return out;
    }
    
    ostream& operator<<(
      ostream& out, vector <mystruct> const& v)
    {
      copy(v.begin(), v.end(),
           ostream_iterator <mystruct> (out, " "));
      return out;
    }
    
    struct average_b
    {
      map <float, float> sum;
      map <float, int> count;
    
      float operator[] (float a) const
      {
        return sum.find(a)->second / count.find(a)->second;
      }
    };
    
    average_b operator+ (
      average_b const& average,
      mystruct const& s)
    {
      average_b result( average );
    
      result.sum[s.a] += s.b;
      ++result.count[s.a];
    
      return result;
    }
    
    struct set_b_to_average
    {
      set_b_to_average(average_b const& average)
        : average(average)
      { }
    
      mystruct operator()(mystruct const& s) const
      {
        return mystruct(s.a, average[s.a]);
      }
    
      average_b const& average;
    };
    
    int main()
    {
      cout << "before:" << endl << v << endl << endl;
    
      transform(v.begin(), v.end(),
                v.begin(), set_b_to_average(
                  accumulate(v.begin(), v.end(), average_b())
                ));
    
      cout << "after:" << endl << v << endl << endl;
    }
    

    【讨论】:

      【解决方案6】:

      您可以将“分区”算法与“累积”一起使用。

      示例

      #include <iostream>
      #include <vector>
      #include <algorithm>
      #include <numeric>
      
      struct test
      {
          float a;
          float b;
      
          test(const float one, const float two)
              : a(one), b(two)
          {
          }
      };
      
      struct get_test_a {
          float interesting;
      
          get_test_a(const float i)
              : interesting(i)
          {
          }
      
          bool operator()(const test &value) const
          {
              static const float epi = 1e-6;
              return value.a < interesting + epi &&
                  value.a > interesting - epi;
          }
      };
      
      struct add_test_b {
          float operator()(const float init, const test &value) const
          {
              return init + value.b;
          }
      };
      
      int main(int argc, char **argv)
      {
          using std::partition;
          using std::accumulate;
          using std::distance;
          typedef std::vector<test> container;
      
          container myContainer;
      
          // Say 'myVector' contains these five elements {a, b}:
          // {1, 1}, {1, 2}, {2, 1}, {1, 3}, {2, 2}
          myContainer.push_back(test(1, 1));
          myContainer.push_back(test(1, 2));
          myContainer.push_back(test(2, 1));
          myContainer.push_back(test(1, 3));
          myContainer.push_back(test(2, 2));
      
          // I want to get v[0], v[1], v[3] (where a is 1) and
          // average b: (1 + 2 + 3)/3 = 2,
          // and v[2] and v[4] (where a is 2) and average b: (1+2)/2 = 1.5
          const container::iterator split = 
              partition(myContainer.begin(), myContainer.end(),
                        get_test_a(1));
      
          const float avg_of_one =
              accumulate(myContainer.begin(), split, 0.0f, add_test_b())
              / distance(myContainer.begin(), split);
      
          const float avg_of_others =
              accumulate(split, myContainer.end(), 0.0f, add_test_b())
              / distance(split, myContainer.end());
      
          std::cout << "The 'b' average of test values where a = 1 is "
                    << avg_of_one << std::endl;
      
          std::cout << "The 'b' average of the remaining test values is "
                    << avg_of_others << std::endl;
      
          return 0;
      }
      

      gcc 头文件中的文档

        /**
         *  @brief Move elements for which a predicate is true to the beginning
         *         of a sequence.
         *  @ingroup mutating_algorithms
         *  @param  first   A forward iterator.
         *  @param  last    A forward iterator.
         *  @param  pred    A predicate functor.
         *  @return  An iterator @p middle such that @p pred(i) is true for each
         *  iterator @p i in the range @p [first,middle) and false for each @p i
         *  in the range @p [middle,last).
         *
         *  @p pred must not modify its operand. @p partition() does not preserve
         *  the relative ordering of elements in each group, use
         *  @p stable_partition() if this is needed.
        */
        template<typename _ForwardIterator, typename _Predicate>
          inline _ForwardIterator
          partition(_ForwardIterator __first, _ForwardIterator __last,
                _Predicate   __pred)
      
        /**
         *  @brief  Accumulate values in a range with operation.
         *
         *  Accumulates the values in the range [first,last) using the function
         *  object @a binary_op.  The initial value is @a init.  The values are
         *  processed in order.
         *
         *  @param  first  Start of range.
         *  @param  last  End of range.
         *  @param  init  Starting value to add other values to.
         *  @param  binary_op  Function object to accumulate with.
         *  @return  The final sum.
         */
        template<typename _InputIterator, typename _Tp, typename _BinaryOperation>
          inline _Tp
          accumulate(_InputIterator __first, _InputIterator __last, _Tp __init,
                 _BinaryOperation __binary_op)
      

      【讨论】:

        【解决方案7】:

        似乎最简单的方法是在集合上运行一个中等复杂的函子:

        struct CountAllAverages {
            typedef std::pair<float, unsigned> average_t;
            std::map<float, average_t> averages;
            void operator()(mystruct& ms) {
                average_t& average = averages[ms.a];
                average.second++;
                average.first += ms.b;
            }
            float getAverage(float a) { return averages[a].first/averages[a].second; }
        };
        

        【讨论】:

          【解决方案8】:

          编写 C++ 时,您应该在可重用性(例如重用现有算法和数据结构)和可读性之间保持平衡。 onebyone 很接近,但他的解决方案可以进一步改进:

          template<class T>
          struct average {
            T total;
            int count;
            mutable bool calculated;
            mutable T average_value;
          
            average & operator+=(T const & value) {
              total += value;
              ++count;
              calculated = false;
            }
          
            T value() const {
              if(!calculated) {
                calculated = true;
                average_value = total / count;
              }
              return average_value;
            }
          };
          
          
          std::map< float, average<float> > averages;
          BOOST_FOREACH(MYSTRUCT &element, v) {
            averages[element.a] += element.b;
          }
          
          BOOST_FOREACH(MYSTRUCT &element, v) {
            element.b = averages[element.a].value();
          }
          

          具有可重复使用的“平均”类型的奖励积分。

          【讨论】:

            【解决方案9】:
            struct MYSTRUCT { 
                float x;
                float y;
            
                operator float() const { return y; }
            };
            
            class cmp { 
                float val;
            public:
                cmp(float v) : val(v) {}      
                bool operator()(MYSTRUCT const &a) { return a.x != val; }
            };
            
            float masked_mean(std::vector<MYSTRUCT> const &in, MYSTRUCT const &mask) { 
                std::vector<float> temp;
                std::remove_copy_if(in.begin(), in.end(), std::back_inserter(temp), cmp(mask.x));
                return std::accumulate(temp.begin(), temp.end(), 0.0f) / temp.size();
            }
            

            【讨论】:

              猜你喜欢
              • 2014-08-15
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-05-15
              • 2011-09-11
              相关资源
              最近更新 更多