std::transform to append to a string

K

kvnil

Cheers,

I've been struggling all day today with this, maybe you could give me some
useful pointers.
I would like to (elegantly) concatenate a string out of all node values in
a map. I have to do it without using boost (as it is forbidden to use it
in our project).

(Forgive me for giving a non-compiling code.)

---- begin ----
map<int,string> m;
string s;

transform(m.begin(),m.end(),????, select2nd<int,string>());
---- end ----

What would you recommend placing instead of ???? ?
I've an ugly solution where I declare (something like):

vector<string> v;
transform(m.begin(),m.end(), back_inserterer(v), select2nd<int,string>());
and then of course I could build the string out of the vector.

Why can't I do something like:
transform(m.begin(),m.end(),back_inserter(s.begin()), select2nd<int,string>());

Regards
kev
 
K

Kai-Uwe Bux

kvnil said:
Cheers,

I've been struggling all day today with this, maybe you could give me some
useful pointers.
I would like to (elegantly) concatenate a string out of all node values in
a map. I have to do it without using boost (as it is forbidden to use it
in our project).

(Forgive me for giving a non-compiling code.)

---- begin ----
map<int,string> m;
string s;

transform(m.begin(),m.end(),????, select2nd<int,string>());
---- end ----

What's wrong with a for-loop?

for ( map<int,string>::const_iterator iter = m.begin();
iter != m.end(); ++iter ) {
s.append( iter->second );
}

What would you recommend placing instead of ???? ?

An append_iterator. You can easily write one. Look at some code for
back_inserter and modify it to use append() instead of push_back().

In fact, you could go all the way and write a generic command_iterator, that
takes a function object upon construction and calls then translates

*out_iter = value;

into

command( value );


I've an ugly solution where I declare (something like):

vector<string> v;
transform(m.begin(),m.end(), back_inserterer(v), select2nd<int,string>());
and then of course I could build the string out of the vector.

Why can't I do something like:
transform(m.begin(),m.end(),back_inserter(s.begin()),
select2nd<int,string>());

Because the elements in s are characters not strings?


Best

Kai-Uwe Bux
 
K

kvnil

... said:
What's wrong with a for-loop?

for ( map<int,string>::const_iterator iter = m.begin();
iter != m.end(); ++iter ) {
s.append( iter->second );
}
An append_iterator. You can easily write one. Look at some code for
back_inserter and modify it to use append() instead of push_back().
thanks! I'll try and keep everybody posted :)
In fact, you could go all the way and write a generic
command_iterator, that takes a function object upon construction and
calls then translates

*out_iter = value;

into

command( value );

Nice - but I do not see the merit (no offence in any way - you've tremendously
helped with your previous suggestion). Indeed transform would perform the
*outer=value call,
but won't I be able to achieve the same with std::foreach? It already calls
a functor for each object.
So I would need to implement an adaptable binary function (since the binary
function from <function> requirest const on functor arguments), bind1st it's
first argument to my appendee (the target string), and then std::foreach

Maybe it would be even possible to somehow adapt string::append (with mem_fun),
the only problem is that during std::foreach on a map I would get back a
pair<int,string> and not a string. Don't see yet how to overcome this. Maybe
with select2nd.

Cheers and thanks!
 
J

James Kanze

kvnil wrote:
What's wrong with a for-loop?
for ( map<int,string>::const_iterator iter = m.begin();
iter != m.end(); ++iter ) {
s.append( iter->second );
}
An append_iterator. You can easily write one. Look at some
code for back_inserter and modify it to use append() instead
of push_back().

Conceptually, I find that some sort of transform_iterator and
std::accumulate corresponds more to the problem statement. The
way I view it is that he wants to accumulate the values from
what is essentially a view of the original collection. (I'm not
sure that transform_iterator is a good name in this case, since
nothing is really transformed; the view is really just a
projection of the original container.) On the other hand, the
design of the standard library makes such mundain applications
particularly difficult, and unless you have a framework already
available to help (and he said he can't use Boost, so the Boost
transform_iterator is out), or the need occurs in several
places, I'd just go with the for loop.

FWIW: with the Boost transform iterator:

typedef select2nd< int, std::string >
Projection ;
std::string s
= std::accumulate(
boost::make_transform_iterator< Projection
( m.begin() ),
boost::make_transform_iterator< Projection
( m.end() ),
std::string() ) ;

Regretfully, performance won't be very good, since
std::accumulate does a lot of copying.

An alternative solution would be to define a special
"accumulator". (You can even use a clever, but ugly hack, to
avoid the copies!)

template< typename Pair >
class AccumulateSecond
{
class NotForPublicConsumption {} ;

public:
operator std::string() const
{
return myValue ;
}

AccumulateSecond& operator=( NotForPublicConsumption )
{
return *this ;
}

NotForPublicConsumption
operator+( Pair const& other )
{
myValue += other.second ;
return NotForPublicConsumption() ;
}

private:
std::string myValue ;
} ;

with:
s = std::accumulate( m.begin(), m.end(),
AccumulateSecond< Map::value_type >() ) ;

works well, for example. (Notice the somewhat unconventional
operator+ and assignment operator, to avoid the copy. In
practice, I prefer to define a separate functional object in
place of the operator+, and use the four argument form of
std::accumulate, rather than to abuse operator overloading like
this.) It also permits easily adding a separator, if that later
becomes necessary. (But again, unless the need occurs more or
less regularly in your code, it's better to just stick with the
for loop.)

And of course, you can easily do something similar with an
output decorator for Map::value_type and std::eek:stream_operator
to an std::eek:stringstream. (Which is also an established idiom
for "accumulating" text.)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,236
Members
46,822
Latest member
israfaceZa

Latest Threads

Top