Adding data to stl container without raw loops

I often saw that you can replace all handwritten / raw loops with stl algorithms. Just to improve my knowledge of C ++, I was just trying to do it.

To populate std :: vector with data, I use a for loop and a loop index.

unsigned int buffer_size = (format.getBytesPerSecond() * playlen) / 1000; // pcm data stored in a 'short type' vector vector<short> pcm_data; for (unsigned int i = 0; i < buffer_size; ++i) { pcm_data.push_back( static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)) ); } 

The above code works fine, because as you can see, I use the for loops index for the correct algorithm.

How can someone replace this with a standard one for a loop?

The only functions I've seen that almost let me do this are std :: transform and std :: generate, but both of them will not work, because I need an index value to increase the code.

EG:

 generate_n(begin(pcm_data), buffer_size, [] () { return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)); //what is i?? }); transform(begin(pcm_data), end(pcm_data), begin(pcm_data) [] (???) { return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)); //what is i?? }); 

Or did I just go too far into the idea of ​​"no raw cycles"?

+8
c ++ c ++ 11 stl
source share
5 answers

You can simply use a variable in the scope of your "generate_n" to declare your variable.

 unsigned int i = 0; generate_n(begin(pcm_data), buffer_size, [&] () { return static_cast<short>(amplitude * sin((2 * M_PI * (i++) * frequency) / format.SampleRate)); //what is i?? }); 
+5
source share

The real solution here would be to define the appropriate iterator, something like:

 class PcmIter : public std::iterator<std::forward_iterator_tag, short> { int myIndex; double myAmplitude; double myFrequency; short myValue; void calculate() { myValue = myAmplitude * std::sin( 2 * M_PI * myIndex * frequency ); } public: PcmIter( int index, amplitude = 0.0, frequency = 0.0 ) : myIndex( index ) , myAmplitude( amplitude ) , myFrequency( frequency ) { calculate(); } bool operator==( PcmIter const& other ) const { return myIndex == other.myIndex; } bool operator!=( PcmIter const& other ) const { return myIndex != other.myIndex; } const short& operator*() const { return myValue; } PcmIter& operator++() { ++ myIndex; calculate(); } PcmIter operator++( int ) { PcmIter results( *this ); operator++(); return results; } }; 

In practice, I suspect that you could operator* returns the value that you are calculating at this point and does not have a member of myValue .

For use:

 std::vector<short> pcmData( PcmIter( 0, amplitude, frequency), PcmIter( buffer_size ) ); 

(The amplitude and frequency do not matter for the end of the iterator, since it will never be dereferenced.)

Ideally, this would be random_access_iterator, so the constructor for the vector will calculate the number of elements and pre-distribute them. This involves the implementation of a much larger function.

If you are courageous and have to do such things a lot, you might consider creating a template iterator created in accordance with the function you are interested in.

And while I haven’t had a chance to play with them lately, if you use Boost, you can think of a chain of a transform_iterator and a counting_iterator . It's still a little verbal, but the people who did iterators in Boost are the best they could, given the somewhat disrupted design of STL iterators.

+7
source share

I would recommend counting_iterator in the Boost Library . A pair of countable iterators provides you with an integer. Obviously, there is no base container. It provides an integer "lazy." The library creates a factory function make_counting_iterator to create it.

back_insert_iterator (with factory back_inserter function) in the standard library ( iterator header) effectively calls the container's push_back element.

With these ingredients, you can use transform with an β€œindex”.

 #include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; #include <boost/iterator/counting_iterator.hpp> int main(int argc, char* argv[]) { // Create a pair of counting iterators auto first = boost::make_counting_iterator(0); auto last = boost::make_counting_iterator(10); vector<int> vi; // Construct a vector of a few even number, as an example. transform(first, last, back_inserter(vi), [](int i){ return 2 * i; }); // Print the result for check copy(vi.begin(), vi.end(), ostream_iterator<int>{cout, " "}); return 0; } 

Fingerprint:

 0 2 4 6 8 10 12 14 16 18 
+5
source share

not necessarily better, but solution with stl:

 struct generate_value { short operator() () const {return amplitude * sin((2 * M_PI * i++ * frequency) / format.SampleRate);} private: unsigned i = 0; }; generate_n(back_inserter(pcm_data), buffer_size, generate_value{}); 
+1
source share

I see a couple of opportunities that I have not seen yet. You can start with an iterator for a range of numbers:

 template <class T> class xrange_t { T start; T stop; public: xrange_t(T start, T stop) : start(start), stop(stop) {} class iterator : public std::iterator<std::forward_iterator_tag, T> { T current; public: iterator(T t) : current(t) {} T operator *() { return current; } iterator &operator++() { ++current; return *this; } bool operator!=(iterator const &other) const { return current != other.current; } bool operator==(iterator const &other) const { return current == other.current; } }; iterator begin() { return iterator(start); } iterator end() { return iterator(stop); } }; template <class T> xrange_t<T> xrange(T start, T stop) { return xrange_t<T>(start, stop); } 

Then you should use this with a range loop to do the real work:

 #include "xrange" for (auto i : xrange(0, buffer_size)) pcm_data.push_back( static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate)) ); 

Another possibility is to complete the work in a couple of steps:

 std::vector<short> pcm_data(buffer_size); std::iota(pcm_data.begin(), pcm_data.end(), 0); std::transform(pcm_data.begin(), pcm_data.end(), pcm_data.begin(), [](short i) { return static_cast<short>(amplitude * sin((2 * M_PI * i * frequency) / format.SampleRate))); } ); 

This starts with filling the array with sequential values ​​of i (i.e., inputs to the function), then converts each of these inputs to the corresponding output value.

This has two potential disadvantages:

  • If the value of i can exceed the value that can be stored in short , it can truncate the input value during the initial storage phase. It is unclear whether your use of int for i reflects the likelihood that it can be of a large value or simply use int by default.
  • It moves the result vector twice. If the vector is large (especially if it is too large to fit the cache), it can be significantly slower.
0
source share

All Articles