0

In my solution code for project euler problem 11, I got the following functions. Max_consecutive_prod is a class which calculates the max product of consecutive input()ed numbers, generalised from problem 8. The six functions calculate max product in different series of different directions and start from different edges of the grid.

The only difference in these functions is indexes in for statements, how to elimilate the obvious duplication? The situation here is somehow the opposite to the typical application of template method pattern: the operation is identical but the control framework is different, is there another design pattern for this?

Edit: all the modifications specified in comments are to the (two) for statements, and the loop body in each function is identical to the first.

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;
}

Based on random-hacker's code, which shows the real commonality and variability in control flows of the six function, I wrote my version and made the code more self-explaining and C++ idiomatic, using a stragegy class, defining local variables to clarify the code and improve effiency. I define a non-template version of process(), to avoid binary code bloat when instantizing for different size (see 'Effective C++', Item 44).

If you still get confused, please read random-hacker's answer for explanation. :)

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;
        }
    }
}
4

5 回答 5

0

虽然我认为按照 Adam Burry 和 Tony D 的建议将内部循环代码块粘贴到普通函数中之后,你已经拥有的东西会很好,但如果你愿意,你可以组合循环,使用表格来编码可能的移动方向。诀窍是使用数组p[2]而不是单独的iand j,以使外部循环中哪个索引不同的问题由表驱动。然后唯一棘手的事情是确保将在内部循环中变化的另一个索引需要从其最大值(而不是 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;
}
于 2013-10-30T04:58:31.677 回答
0

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

x y z 0 ...

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

除非您Max_consecutive_prod非常清楚它是如何被调用的,否则您还会从一行的末尾到下一行以及从一个process_...()调用到下一个调用得到错误的结果“包装”。

于 2013-10-30T03:09:53.437 回答
0

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

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; }
};

这样,相同的访问者类可以与您的各种网格元素迭代例程相结合。

于 2013-10-30T04:31:29.077 回答
0

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

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

对角线没有太大区别。在纸上试一试(网格纸很适合这种事情。)

还有一个提示:如果你找到四个元素的乘积,从左到右相乘,而不是相同的四个元素从​​右到左相乘,这有什么不同吗?

于 2013-10-30T03:14:50.567 回答
0

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

于 2013-10-30T01:52:22.617 回答