使用枚举指定的函数填充矢量

Fill vector using function specified by enum

本文关键字:函数 填充 枚举      更新时间:2023-10-16

我想要的功能如下:

std::vector<float> GetFuncVec(int N, FuncType type)
{
    std::vector<float> fn(N);
    float tmp = (N - 1) / 2.0;
    switch (type) {
    case SIN:
        for (int i=0; i<N; ++i)
            fn[i] = sin(M_PI * i / tmp);
        break;
    case SINC:
        for (int i=0; i<N; ++i)
            fn[i] = sin(M_PI * i / tmp) / (M_PI * i / tmp);
        break;
    ...
    }
    return fn;
}

我觉得这并不令人满意,因为有很多代码重复。环顾四周,我发现了STL算法std::generate(),它可以使用函子填充向量,函子可以有一个增量成员来扮演i的角色。

我看到了两条潜在的路线。第一种是使用工厂来初始化函子。这种方法的问题是代码分离(如上所述,不同的情况很好地保持在一起),并且需要多个新类来增加开销。

第二个是使用lambda函数(我对它的经验很少)。这很好,因为我可以在switch语句中的一行中定义每个函数。但我不知道如何避免作用域问题(lambda函数在switch语句的作用域之外是不可访问的)。

有没有使用lambda函数的解决方案?从效率和可读性的角度来看,什么是最好的选择?

也许你想要这样的东西。。。?(点击此处查看

#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
enum Func { Sin, Sinc };
std::vector<float> f(int n, Func func)
{
    std::vector<float> results(n);
    float tmp = (n - 1) / 2.0;
    int i;
    std::function<float()> fns[] = {
        [&] { return sin(M_PI * i / tmp); },
        [&] { return sin(M_PI * i / tmp) / (M_PI * i / tmp); }
    };
    auto& fn = fns[func];
    for (i=0; i<n; ++i)
        results[i] = fn();
    return results;
}
int main()
{
    std::vector<float> x = f(10, Sin);
    for (auto& v : x) std::cout << v << ' '; std::cout << 'n';
    std::vector<float> y = f(10, Sinc);
    for (auto& v : y) std::cout << v << ' '; std::cout << 'n';
}

输出:

0 0.642788 0.984808 0.866025 0.34202 -0.34202 -0.866025 -0.984808 -0.642788 -2.44929e-16 
-nan 0.920725 0.705317 0.413497 0.122477 -0.0979816 -0.206748 -0.201519 -0.115091 -3.89817e-17 

一个可能不快(每个函数调用都有间接寻址)但更灵活的选项是创建std::map<FuncType, std::function<float(int,float)>>。你不能使用std::generate(),因为你需要参数i来计算结果,但写自己的并不难:

template <typename Iterator, typename Generator, typename Index, typename... Args>
void generate_i(Iterator first, Iterator last, Generator gen, Index i, Args... args)
{
    while (first != last) {
        *first = gen(i, args...);
        ++i;
        ++first;
    }
}

现在我们有了这个,我们需要填充一个函子映射:

using FuncTypeFunction = std::function<float(int,float)>;
using FuncTypeFunctionMap = std::map<FuncType, FuncTypeFunction>;
FuncTypeFunctionMap create_functype_map()
{
    FuncTypeFunctionMap functions;
    functions[SIN]  = [] (int i, float tmp) {
        return sin(M_PI * i / tmp);
    };
    functions[SINC] = [] (int i, float tmp) {
        return sin(M_PI * i / tmp) / (M_PI * i / tmp);
    };
    // ...
    return functions;
}
FuncTypeFunctionMap const FuncTypeFunctions = create_functype_map();

(如果你喜欢,你可以使用boost.assign来提高这个比特的可读性。)

最后,我们可以使用这个地图:

std::vector<float> GetFuncVec(int N, FuncType type)
{
    std::vector<float> fn(N);
    float tmp = (N - 1) / 2.0;
    auto func = FuncTypeFunctions.find(type);
    if (func != FuncTypeFunctions.end()) {
        generate_i(fn.begin(), fn.end(), func->second, 0, tmp);
    }
    return fn;
}

添加新函数只需要在create_functype_map()中填充映射。请注意,generate_i()循环中的每个迭代都将调用std::function上的operator(),这将需要一定程度的间接性来解析调用,类似于虚拟方法调用的开销。这将在性能方面花费一些成本,但对您来说可能不是问题。

(参见演示)

您可以编写一个通用类,用于标准算法std::iota

例如

#include <iostream>
#include <functional>
#include <vector>
#include <numeric>
class Value
{
public:
    Value() : i( 0 ), fn( []( size_t i ) { return ( float )i; } ) {}
    Value & operator ++() { ++i; return *this; }
    operator float () const { return fn( i ); }
    Value & operator =( std::function<float( size_t )> fn )
    {
        this->fn = fn;
        return *this;
    }
private:
    size_t i;
    std::function<float( size_t )> fn;
};

enum E { First, Second };
std::vector<float> f( size_t N, E e )
{
    Value value;
    float tmp = N / 2.0f;
    switch( e )
    {
    case First:
        value = [tmp] ( size_t i ) { return i * tmp; };
        break;
    case Second:
        value = [tmp] ( size_t i ) { return i * tmp + tmp; };
        break;
    }
    std::vector<float> v( N );
    std::iota( v.begin(), v.end(), value );
    return v;
}
int main() 
{
    for ( float x : f( 10, First ) ) std::cout << x << ' ';
    std::cout << std::endl;
    for ( float x : f( 10, Second ) ) std::cout << x << ' ';
    std::cout << std::endl;
    return 0;
}

输出为

0 5 10 15 20 25 30 35 40 45 
5 10 15 20 25 30 35 40 45 50 

当然,您可以使用自己的lambda表达式,其中包括一些数学函数,如sin