Salgar
Salgar

Reputation: 7775

Can we split, manipulate and rejoin a string in c++ in one statement?

This is a bit of a daft question, but out of curiousity would it be possibly to split a string on comma, perform a function on the string and then rejoin it on comma in one statement with C++?

This is what I have so far:

string dostuff(const string& a) {
  return string("Foo");
}

int main() {
  string s("a,b,c,d,e,f");

  vector<string> foobar(100);
  transform(boost::make_token_iterator<string>(s.begin(), s.end(), boost::char_separator<char>(",")),
            boost::make_token_iterator<string>(s.end(), s.end(), boost::char_separator<char>(",")),
            foobar.begin(),
            boost::bind(&dostuff, _1));
  string result = boost::algorithm::join(foobar, ",");
}

So this would result in turning "a,b,c,d,e,f" into "Foo,Foo,Foo,Foo,Foo,Foo"

I realise this is OTT, but was just looking to expand my boost wizardry.

Upvotes: 2

Views: 910

Answers (4)

Matthieu M.
Matthieu M.

Reputation: 299730

I am actually working on a library to allow writing code in a more readable fashion than iterators alone... don't know if I'll ever finish the project though, seems dead projects tend to accumulate on my computer...

Anyway the main reproach I have here is obviously the use of iterators. I tend to think of iterators as low-level implementation details, when coding you rarely want to use them at all.

So, let's assume that we have a proper library:

struct DoStuff { std::string operator()(std::string const&); };

int main(int argc, char* argv[])
{
  std::string const reference = "a,b,c,d,e,f";

  std::string const result = boost::join(
    view::transform(
      view::split(reference, ","),
      DoStuff()
    ),
    ","
  );
}

The idea of a view is to be a lightwrapper around another container:

  • from the user point of view it behaves like a container (minus the operations that actually modify the container structure)
  • from the implementation point of view, it's a lightweight object, containing as few data as possible --> the value is ephemeral here, and only lives as long as the iterator lives.

I already have the transform part working, I am wondering how the split could work (generally), but I think I'll get into it ;)

Upvotes: 1

Yakov Galka
Yakov Galka

Reputation: 72469

void dostuff(string& a) {
    a = "Foo";
}

int main()
{
    string s("a,b,c,d,e,f");
    vector<string> tmp;
    s = boost::join(
          (
            boost::for_each(
              boost::split(tmp, s, boost::is_any_of(",")),
              dostuff
            ),
            tmp
          ),
          ","
        );

    return 0;
}

Unfortunately I can't eliminate mentioning tmp twice. Maybe I'll think of something later.

Upvotes: 1

Cubbi
Cubbi

Reputation: 47408

First, note that your program writes "Foo,Foo,Foo,Foo,Foo,Foo,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," to your result string -- as already mentioned in comments, you wanted to use back_inserter there.

As for the answer, whenever there's a single value resulting from a range, I look at std::accumulate (since that is the C++ version of fold/reduce)

#include <string>
#include <iostream>
#include <numeric>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
std::string dostuff(const std::string& a) {
  return std::string("Foo");
}
int main() {
  std::string s("a,b,c,d,e,f");
  std::string result =
    accumulate(
     ++boost::make_token_iterator<std::string>(s.begin(), s.end(), boost::char_separator<char>(",")),
       boost::make_token_iterator<std::string>(s.end(), s.end(), boost::char_separator<char>(",")),
       dostuff(*boost::make_token_iterator<std::string>(s.begin(), s.end(), boost::char_separator<char>(","))),
       boost::bind(std::plus<std::string>(), _1,
         bind(std::plus<std::string>(), ",",
            bind(dostuff, _2)))); // or lambda, for slightly better readability
  std::cout << result << '\n';
}

Except now it's way over the top and repeats make_token_iterator twice. I guess boost.range wins.

Upvotes: 1

Billy ONeal
Billy ONeal

Reputation: 106530

Okay, I guess it's possible, but please please don't really do this in production code.

Much better would be something like

std::string MakeCommaEdFoo(std::string input)
{
    std::size_t commas = std::count_if(input.begin(), input.end(),
        std::bind2nd(std::equal_to<char>(), ','));
    std::string output("foo");
    output.reserve((commas+1)*4-1);
    for(std::size_t idx = 1; idx < commas; ++idx)
        output.append(",foo");
    return output;
}

Not only will it perform better, it will is much easier for the next guy to read and understand.

Upvotes: 0

Related Questions