在我工作的地方,我们仍然使用.Net 2.0,但我对3.0 / 3.5的东西有些熟悉。我想在C#中使用linq / lambda表达式进行一些练习,所以我使用了许多通用List<T>
和lambda表达式以及它提供的聚合方法编写了一个简单的Sudoku求解器。
在我的求解器中,我将它放在求解器算法的顶部:
private Puzzle RecursiveSolve(Puzzle p, int idx)
{
// start with simple filter to narrow down recursive paths
// puzzle is still solved without this line, but it takes at least 20x longer
while( p.board.Any(cell => cell.FilterPossibles()) ); // while(); <- empty while loop is intentional
正如你可能会说的那样,这是一个简单的递归算法,但我确实对它进行了一些优化,以使运行时间降到合理的水平(对于我在google上找到的最棘手的难题,可以达到3.6秒)。
要了解该代码段,Puzzle.board
为List<Cell>
,Cell.FilterPossibles()
会根据同一行,列和3x3框中其他单元格的值检查每个单元格的可能值,以便查看如果它可以消除任何。如果它变为一个可能的,它设置单元格的值并返回true。否则返回false。因此,只要板上的至少一个单元在前一次迭代中发生了变化(已解决),while循环就会运行。
我担心的是while循环是空的。这相当于代码味道,并告诉我,我可能错过了一些东西。有没有办法可以把它写成一个语句,而不是一个循环?
我实际上有一大堆问题是这个项目的结果:(我是否在不知不觉中实现了一些我可以告诉编译器的接口?我的函数选择通常是否合适?我有很多布尔方法来简化表达式:那怎么可能更好?我怎样才能更好地设计这个可能使用不可变的Puzzle / Cell对象?)但是这是最让我现在唠叨的那个。
答案 0 :(得分:1)
一个空的while循环暗示了一个bug或副作用 - 在这种情况下,它听起来像是副作用,改变了棋盘。
什么是“LINQ-like”(但不一定有效 - 它真的取决于)是让拼图产生板的迭代器 - 执行另一次迭代将返回 new 板(side-免费效应)。
我对你正在做的事情并没有真正“满意”,所以这可能不合适 - 但这将是我的第一线调查。
除此之外,我可能会考虑以某种方式重写它以使你更明显地做有副作用 - 但同样,在不知道你的代码的情况下很难确切地知道这是怎么回事会工作的。
答案 1 :(得分:1)
我并不是真的反对空循环,我在C ++中经常使用它们 - 虽然将身体限定为{ }
,这更加清晰恕我直言。
但是,我没有得到你的代码,似乎表明确实存在代码味道,或者至少可以用更清晰的方式表达。
我没有特别提到的事实是Any
返回一个布尔值,一次。这如何与循环结合在一起?这里的副作用真的不明显(你修改p.board
的内容?)......似乎太聪明,不知何故。
答案 2 :(得分:1)
我认为具有适当名称的Extract Method在这里可能很有用。它不会消除气味,但你可以将它从RecursiveSolver的第一行移开。 (我可以想象读它并马上撞到一堵砖墙)。
这样可以简化一个这样的衬里,但是使用一个名字很好的方法看起来会更好......
重命名FilterPossibles()可以创造奇迹......这种方法不仅仅是说它......