У меня есть следующий фрагмент Python, который я хотел бы воспроизвести с помощью C ++:
from itertools import count, imap
source = count(1)
pipe1 = imap(lambda x: 2 * x, source)
pipe2 = imap(lambda x: x + 1, pipe1)
sink = imap(lambda x: 3 * x, pipe2)
for i in sink:
print i
Я слышал о Повысить Феникс, но я не смог найти пример ленивого transform
ведет себя так же, как в Python imap
,
Редактировать: чтобы прояснить мой вопрос, идея состоит не только в том, чтобы применять функции последовательно, используя for
, а скорее, чтобы иметь возможность использовать алгоритмы, такие как std::transform
на бесконечных генераторах. Способ составления функций (на более функциональном языке, таком как диалект) также важен, поскольку следующим шагом является составление функций.
Обновление: спасибо bradgonesurfing, David Brown и Xeo за потрясающие ответы! Я выбрал Xeo, потому что он самый лаконичный и дает мне то, что я хотел, но Дэвид был очень важен для реализации концепций. Кроме того, у Bradgonesurfing есть повышенный Boost :: Range :).
Использование Boost.Range:
int main(){
auto map = boost::adaptors::transformed; // shorten the name
auto sink = generate(1) | map([](int x){ return 2*x; })
| map([](int x){ return x+1; })
| map([](int x){ return 3*x; });
for(auto i : sink)
std::cout << i << "\n";
}
Я думаю, что самый идиоматический способ сделать это в C ++ — с помощью итераторов. Вот базовый класс итератора, который принимает итератор и применяет функцию к его результату:
template<class Iterator, class Function>
class LazyIterMap
{
private:
Iterator i;
Function f;
public:
LazyIterMap(Iterator i, Function f) : i(i), f(f) {}
decltype(f(*i)) operator* () { return f(*i); }
void operator++ () { ++i; }
};
template<class Iterator, class Function>
LazyIterMap<Iterator, Function> makeLazyIterMap(Iterator i, Function f)
{
return LazyIterMap<Iterator, Function>(i, f);
}
Это просто базовый пример, и он все еще не завершен, так как не может проверить, достиг ли ты конца итерируемой последовательности.
Вот воссоздание вашего примера кода Python (также определяющего простой класс бесконечного счетчика).
#include <iostream>
class Counter
{
public:
Counter (int start) : value(start) {}
int operator* () { return value; }
void operator++ () { ++value; }
private:
int value;
};
int main(int argc, char const *argv[])
{
Counter source(0);
auto pipe1 = makeLazyIterMap(source, [](int n) { return 2 * n; });
auto pipe2 = makeLazyIterMap(pipe1, [](int n) { return n + 1; });
auto sink = makeLazyIterMap(pipe2, [](int n) { return 3 * n; });
for (int i = 0; i < 10; ++i, ++sink)
{
std::cout << *sink << std::endl;
}
}
Помимо определений классов (которые просто воспроизводят то, что делают функции библиотеки Python), код примерно такой же длины, как и версия Python.
Я думаю, что библиотека boost :: rangex — это то, что вы ищете. Он должен хорошо работать с новым лямбда-синтаксисом c ++.
int pipe1(int val) {
return 2*val;
}
int pipe2(int val) {
return val+1;
}
int sink(int val) {
return val*3;
}
for(int i=0; i < SOME_MAX; ++i)
{
cout << sink(pipe2(pipe1(i))) << endl;
}
Я знаю, что это не совсем то, что вы ожидали, но оно определенно оценивает время, когда вы этого хотите, хотя и не с интерфейсом итератора. Очень похожая статья такая:
Компонентное программирование на D
Изменить 6 ноября / 12:
Альтернатива, все еще придерживающаяся чистого C ++, состоит в том, чтобы использовать указатели на функции и создавать собственный конвейер для вышеуказанных функций (вектор указателей на функции из SO q: Как мне сохранить указатель на функцию в векторе?):
typedef std::vector<int (*)(int)> funcVec;
int runPipe(funcVec funcs, int sinkVal) {
int running = sinkVal;
for(funcVec::iterator it = funcs.begin(); it != funcs.end(); ++it) {
running = (*(*it))(running); // not sure of the braces and asterisks here
}
return running;
}
Это предназначено для запуска всех функций в векторе таких и возврата полученного значения. Тогда ты можешь:
funcVec funcs;
funcs.pushback(&pipe1);
funcs.pushback(&pipe2);
funcs.pushback(&sink);
for(int i=0; i < SOME_MAX; ++i)
{
cout << runPipe(funcs, i) << endl;
}
Конечно, вы могли бы также создать оболочку для этого через структуру (я бы использовал замыкание, если бы C ++ сделал это …):
struct pipeWork {
funcVec funcs;
int run(int i);
};
int pipeWork::run(int i) {
//... guts as runPipe, or keep it separate and call:
return runPipe(funcs, i);
}
// later...
pipeWork kitchen;
kitchen.funcs = someFuncs;
int (*foo) = &kitchen.run();
cout << foo(5) << endl;
Или что-то типа того. Предостережение: не знаю, что это будет делать, если указатели будут передаваться между потоками.
Дополнительное предостережение: если вы хотите сделать это с различными интерфейсами функций, вам в конечном итоге придется загрузить void *(void *)(void *)
функции, чтобы они могли брать что угодно и излучать что угодно, или множество шаблонов, чтобы исправить тот тип канала, который у вас есть. Я полагаю, что в идеале вы должны создать разные типы каналов для разных интерфейсов между функциями, чтобы a | b | c
работает, даже когда они передают различные типы между ними. Но я собираюсь догадаться, что это во многом то, что делает Boost.
В зависимости от простоты функций:
#define pipe1(x) 2*x
#define pipe2(x) pipe1(x)+1
#define sink(x) pipe2(x)*3
int j = 1
while( ++j > 0 )
{
std::cout << sink(j) << std::endl;
}