50

I would like to be able to write something like

char f(char);
vector<char> bar;
vector<char> foo = map(f, bar);

The transform function appears to be similar, but it will not autogenerate the size of the resultant collection.

TrebledJ
  • 8,713
  • 7
  • 26
  • 48
Paul Nathan
  • 39,638
  • 28
  • 112
  • 212
  • I think somebody had too much Perl. I have tried to implement something like that in C++ once, but very soon it became highly inefficient: unlike C++, Perl has high-level optimizer which knows what array or hash or string are and optimizes operations on them correspondently. – Dummy00001 Aug 27 '10 at 00:48
  • The STL has `std::map` as a collection. You can have your own `map` in any other namespace. – MSalters Aug 27 '10 at 08:56
  • 2
    @Dummy: `map` is present in Perl, Python, and Lisps; it's a convenient high-level construct to simply describe a common operation. – Paul Nathan Aug 27 '10 at 14:38
  • 7
    @MSalters: It's more a question of *searching* for the map operation in the STL than the actual name. :-) – Paul Nathan Aug 27 '10 at 15:33
  • What you’re looking for nowadays is called “ranges”, and exists as external libraries, and will end up in the language standard itself at some point. – Kuba hasn't forgotten Monica Oct 21 '18 at 19:22

5 Answers5

51

You can use std::back_inserter in <iterator>, although providing the size in front is more efficient. For example:

string str = "hello world!", result;
transform(str.begin(), str.end(), back_inserter(result), ::toupper);
// result == "HELLO WORLD!"
Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
26

This question was asked before the C++11 standard went into effect... nowadays we have std::transform() as the (ugly) equivalent of a functional programming 'map'. Here's how to use it:

auto f(char) -> char; // or if you like: char f(char)
vector<char> bar;
vector<char> foo;
// ... initialize bar somehow ...
std::transform(bar.begin(), bar.end(), std::back_inserter(foo), f);
einpoklum
  • 118,144
  • 57
  • 340
  • 684
4

To make this work, you'll need the following observations:

  1. To make the assignment efficient, the map function should not do the work. Instead, it should save its arguments in a temporary object (in your case, that would be an instance of class map::result<char(*)(char), vector<char> >)
  2. This map::result temporary should have an template <typename T> operator T conversion.
  3. When the map::result is assigned to a std::vector<char>, this conversion is the only viable.
  4. In the conversion operator class map::result<char(*)(char), vector<char> >::operator vector<char> you have the input and return type, and the mapping function. At this point you can effectively transform the inputs.

<edit>

Code

template<typename CONT, typename FUNC>
class mapresult {
    CONT const& in;
    FUNC f;
public:
    template<typename RESULT> RESULT to() const
    {
        RESULT out;
        for (auto const& e : in) { out.push_back(f(e)); }
        return out;
    }
    template<typename RESULT> operator RESULT() const
    {
        return this->to<RESULT>();
    }
    mapresult(CONT const& in, FUNC f) : in(in), f(std::move(f)) { }
};

template<typename CONT, typename FUNC>
auto map(CONT const& in, FUNC f) -> mapresult<CONT, FUNC>
{
    return mapresult<CONT, FUNC>(in, f);
}

Use like this:

using namespace std;
char foo(char c) { return c | ('A' ^ 'a'); }
std::string in = "Test";

int main(int argc, char* argv[])
{
    string out = map(in, &foo);
    cout << out << endl;

    char replace = 'e';
    cout << map(in, [replace](char c){return c == replace ? '?' : c; }).to<string>();
}
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 1
    @JohannesGerer: The `map` function doesn't know the target container. it would be inefficient to store the mapped output to a `std::deque` only to discover that it was really needed in a `std::vector`. – MSalters Jul 29 '14 at 13:45
  • An obvious improvement would be to SFINAE in a call to `.reserve(in.size()`. The main problem is assigning the result to a class with an overloaded assignment. – MSalters Jul 29 '14 at 14:31
3

You can mimic the map syntax above with something like

template<typename T, typename A>
T map(A(*f)(A), T & container) {
    T output;
    std::transform(container.begin(), container.end(), std::back_inserter(output), f);
    return output;
}
Don F
  • 151
  • 1
  • 4
1

The std::transform function does the job, but isn't performant in some cases. I would suggest using a while loop and reserving the size before hand. This function can easily be changed to be used with strings or any thing mappable for that matter.

template<typename T, typename C>
std::vector<T> map(const std::vector<C> &array, auto iteratee) {
  int index = -1;
  int length = array.size();
  std::vector<T> v(length);
  while(++index < length) {
    v[index] = iteratee(array[index], index);
  }
  return v;
}

Calling the function where array is the std::vector you want to map.

auto result = map<int, int>(array, [](int elem, int index) {
  return elem + 10;
});

Running map on 100 000 000 with std::transform took ~6.15s

the while loop version took ~3.90s

Dany Gagnon
  • 102
  • 1
  • 9