代码之家  ›  专栏  ›  技术社区  ›  Tchami

使用STL/Boost查找和修改向量中的匹配元素

  •  4
  • Tchami  · 技术社区  · 16 年前

    假设我有一个向量声明如下:

    struct MYSTRUCT
    {
     float a;
     float b;
    };
    
    std::vector<MYSTRUCT> v;
    

    现在,我想找到v中所有共享相同a的元素,并求其b的平均值,即。

    之后,v将看起来像这样:{1,2},{1,2},{2,1.5},{1,2},{2,1.5}

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

    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);
    }
    
    9 回复  |  直到 16 年前
        1
  •  2
  •   me22    16 年前

    只要大声思考,这可能会变得相当愚蠢:

    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];
    }
    

    嗯,这并不完全愚蠢,但也许也不令人信服。..

        2
  •  2
  •   Steve Jessop    16 年前

    解决方案示意图:

    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_iterator或其他东西可能会给出一个更实用的风格答案。

        3
  •  1
  •   me22    16 年前

    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
  •  0
  •   Alan    16 年前

    也许是蛮力的方法?...

    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
  •  0
  •   Alexander Churanov Alexander Churanov    16 年前

    避免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
  •  0
  •   Dan    16 年前

    示例

    #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;
    }
    

      /**
       *  @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
  •  0
  •   MSalters    16 年前

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

    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
  •  0
  •   Maxim Kulkin    16 年前

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

    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
  •  0
  •   Jerry Coffin    16 年前
    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();
    }