当唯一的区别是循环控制语句(在循环主体中具有相同的语句)时,避免代码重复

Avoiding code duplication when the only difference is loop control statements (with the same statements in loop bodies)?

本文关键字:循环 语句 代码 控制语句 区别 唯一 主体      更新时间:2023-10-16

在我的欧拉项目问题 11 的解决方案代码中,我得到了以下函数。 Max_consecutive_prod 是一个类,用于计算从问题 8 推广的连续 input() ed 数的最大乘积。这六个函数计算不同系列不同方向的最大乘积,并从网格的不同边开始。

这些函数的唯一区别是for语句中的索引,如何消除明显的重复?这里的情况在某种程度上与template method模式的典型应用相反:操作相同但控制框架不同,是否有另一种设计模式?

编辑:注释中指定的所有修改都是对(两个)for语句的修改,每个函数中的循环体与第一个相同。

template <size_t size> unsigned process_row(const unsigned (&grid)[size][size])
{
    unsigned prodMax = 0;
    for (int i = 0; i < size; ++i)
    {
        Max_consecutive_prod mcp;
        for (int j = 0; j < size; ++j)
        {
            mcp.input(grid[i][j]);
        }
        if (mcp.result() > prodMax)
        {
            prodMax = mcp.result();
        }
    }
    return prodMax;
}
// exchange i, j in process_row
template <size_t size> unsigned process_col(const unsigned (&grid)[size][size])
{
    // ...
}
template <size_t size> unsigned process_diag_lower(const unsigned (&grid)[size][size])
{
    unsigned prodMax = 0;
    for (int init = 0; init < size; ++init)
    {
        Max_consecutive_prod mcp;
        for (int i = init, j = 0; i < size && j < size; ++i, ++j)
            // ...
        // ...
    }
    return prodMax;
}
// exchange i, j in process_diag_lower
template <size_t size> unsigned process_diag_upper(const unsigned (&grid)[size][size])
{
    // ...
}
// flip j in process_diag_lower
template <size_t size> unsigned process_rev_diag_lower(const unsigned (&grid)[size][size])
{
    unsigned prodMax = 0;
    for (int init = 0; init < size; ++init)
    {
        Max_consecutive_prod mcp;
        for (int i = init, j = size-1; i < size && j >= 0; ++i, --j)
            // ...
        // ...
    }
    return prodMax;
}
// change ++j in process_diag_upper to --j
template <size_t size> unsigned process_rev_diag_upper(const unsigned (&grid)[size][size])
{
    unsigned prodMax = 0;
    for (int init = 0; init < size; ++init)
    {
        Max_consecutive_prod mcp;
        for (int j = init, i = 0; j >=0 && i < size; ++i, --j)
            // ...
        // ...
    }
    return prodMax;
}

基于随机黑客的代码,它显示了六个函数控制流的真实共性和可变性,我编写了我的版本,使代码更加自我解释和C++习惯,使用stragegy类,定义局部变量来澄清代码并提高效率。我定义了process()的非模板版本,以避免在实例化不同size时二进制代码膨胀(参见"有效C++",第 44 项)。

如果您仍然感到困惑,请阅读随机黑客的答案进行解释。 :)

namespace Grid_search
{
    enum Step { neg = -1, nul, pos };
    enum Index_t { i, j };
    struct Strategy
    {
        Step direction[2];
        Index_t varOuter;
    };
    const size_t typeCount = 6;
    const Strategy strategy[typeCount] = { {{pos, nul}, i}, {{nul, pos}, j}, {{pos, pos}, i}, {{pos, pos}, j}, {{pos, neg}, i}, {{pos, neg}, j} };
};
template <size_t size> inline unsigned process(const Grid_search::Strategy& strategy, const unsigned (&grid)[size][size])
{
    return process(strategy, reinterpret_cast<const unsigned*>(&grid), size);
}
unsigned process(const Grid_search::Strategy& strategy, const unsigned* grid, size_t size)
{
    using namespace Grid_search;
    const Index_t varOuter = strategy.varOuter, varInner = static_cast<Index_t>(!varOuter);
    const Step di = strategy.direction[i], dj = strategy.direction[j];
    const unsigned initInner = strategy.direction[varInner] == pos ? 0 : size -1;
    unsigned prodMax = 0;
    unsigned index[2];
    unsigned &indexI = index[i], &indexJ = index[j];
    for (unsigned initOuter = 0; initOuter < size; ++initOuter)
    {
        Max_consecutive_prod mcp;
        for (index[varOuter] = initOuter, index[varInner] = initInner;
            0 <= indexI && indexI < size && 0 <= indexJ && indexJ < size;
            indexI += di, indexJ += dj)
        {
            mcp.input(grid[indexI*size + indexJ]);
            if (mcp.result() > prodMax)
            {
                prodMax = mcp.result();
            }
        }
    }
    return prodMax;
}

int main()
{
    static const size_t N = 20;
    unsigned grid[N][N];
    std::ifstream input("d:/pro11.txt");
    for (int count = 0; input >> grid[count/N][count%N]; ++count)
    {
    }
    unsigned prodMax = 0;
    for (int i = 0; i < Grid_search::typeCount; ++i)
    {
        unsigned prod = process(Grid_search::strategy[i], grid);
        if (prod > prodMax)
        {
            prodMax = prod;
        }
    }
}

虽然我认为按照 Adam Burry 和 Tony D 的建议将内部循环代码块粘贴到普通函数中后,您已经拥有的东西会很好,但如果您愿意,您可以组合循环,使用表格对可能的移动方向进行编码。 诀窍是使用数组p[2]而不是单独的ij,以便由表驱动外循环中哪个索引变化的问题。 然后唯一棘手的事情是确保另一个索引(将在内部循环中变化)需要从其最大值(而不是 0)开始,因为它会在每一步递减:

enum indices { I, J };       // Can just use 0 and 1 if you want
template <size_t size> unsigned process(const unsigned (&grid)[size][size]) {
    static int d[][2] = { {1, 0}, {0, 1}, {1, 1}, {1, -1}, {1, 1}, {1, -1} };
    static int w[]    = {      J,      I,      J,       J,      I,       I };
    unsigned prodMax = 0;    // Note: not 1
    for (int k = 0; k < sizeof d / sizeof d[0]; ++k) {  // For each direction
        for (int init = 0; init < size; ++init) {
            Max_consecutive_prod mcp;
            int p[2];        // p[I] is like i, p[J] is like j
            for (p[w[k]] = init, p[!w[k]] = (d[k][!w[k]] == -1 ? size - 1 : 0);
                 min(p[I], p[J]) >= 0 && max(p[I], p[J]) < size;
                 p[I] += d[k][I], p[J] += d[k][J])
            {
                mcp.input(grid[p[I]][p[J]]);
                prodMax = max(prodMax, mcp.result());
            }
        }
    }
    return prodMax;
}

您可以为不同的状态创建一个枚举,然后将其传递给函数。然后,您将创建一个 if 语句,该语句将根据传递的值设置值。

您的process_row()有一个错误:从链接中的示例来看,矩阵中允许零条目,因此如果一行以例如

x y z 0 ...

并且 X、XY 或 XYZ 中的任何一个都大于该行其余部分和矩阵中任何其他行上的所有其他 4 元素乘积,它将错误地报告这是最大的 4 元素乘积。 (我在这里假设Max_consecutive_prod计算input()提供的最后 4 个元素的滚动乘积)。

除非您的Max_consecutive_prod异常了解它的调用方式,否则您还将得到错误的结果,从一行末尾"包装"到下一行,以及从一个process_...()调用到下一个调用。

假设您将网格展平,使其连续只有 400 个数字,从左到右,然后从上到下阅读。最上面的行将包含前 20 个数字(即索引 0、...、19);接下来 20 个数字的第二个 RWO,依此类推。通常,行 i(从 0 开始)对应于索引i*20, i*20 + 1, i*20 + 2, ..., i*20 + 19

现在,列呢?最左边的列从位置 0 开始,就像最上面的行一样。它是位置 20 的下一个元素(第二行中的第一个元素),然后是 40,然后......因此,不难看出,列j的索引是j, j + 20, j + 40, ..., j + 19*20的。

对角线没有太大区别。在纸上尝试一下(网格规则的纸对这种事情很好。

还有一个提示:如果你找到四个元素的乘积,从左到右乘法

,与相同的四个元素从右到左乘法有什么区别吗?

首先,上下文对象方法 - 这只是将参数打包到我对你的问题的评论中提到的支持函数......它和问题一样有用;-]。

struct Context
{
    unsigned& proxMax;
    int i, j;
    Max_consecutive_prod mcp;
    Context(unsigned& prodMax) : prodMax(prodMax) { }
};
template <size_t size> unsigned process_diag_lower(const unsigned (&grid)[size][size])
{
    unsigned prodMax = 0;
    for (int init = 0; init < size; ++init)
    {
        Context context(prodMax);
        for (context.i = init, context.j = 0; context.i < size && context.j < size; ++context.i, ++context.j)
            loop_ij(context);
        loop_outer(context);
    }
    return prodMax;
}

访客模式。 现在,我在评论中说"你没有向我们展示足够的循环体来查看共同的需求",并且从那以后什么也没看到,所以基于我看到的一个体 - 即:

template <size_t size> unsigned process_row(const unsigned (&grid)[size][size])
{
    unsigned prodMax = 0;
    for (int i = 0; i < size; ++i)
    {
        Max_consecutive_prod mcp;
        for (int j = 0; j < size; ++j)
        {
            mcp.input(grid[i][j]);
        }
        if (mcp.result() > prodMax)
        {
            prodMax = mcp.result();
        }
    }
    return prodMax;
}

以上可以拆分:

template <size_t size, template Visitor>
unsigned visit_row(const unsigned (&grid)[size][size], Visitor& visitor)
{
    for (int i = 0; i < size; ++i)
    {
        for (int j = 0; j < size; ++j)
            visitor.inner{grid[i][j]);
        visitor.outer();
    }
    return visitor.result();
}
struct Visitor
{
    unsigned prodMax;
    Max_consecutive_prod mcp;
    Visitor() : prodMax(0) { }
    void inner(unsigned n) {  mcp.input(n); }
    void outer()
    {
        if (mcp.result() > prodMax) prodMax = mcp.result();
        mcp = Max_consecutive_prod();  // reset for next time...
    }
    unsigned result() const { return prodMax; }
};

这样,可以将相同的 Visitor 类与各种网格元素迭代例程结合使用。