在代码中有效地表达2×2逻辑网格
在事件处理程序中,我正在响应值的更改。 我可以访问旧值和新值,并希望根据更改的内容执行某些操作。
每个不同的结果将执行动作/函数X,Y或Z的某种组合.Z接受介于-1和1之间的参数。执行这些操作的顺序并不重要。
查看以下逻辑网格。 旧值是最左边的标签列,新值是标签的第一行:
New: 0 !=0 -------- ------- Old: 0 | nothing Y, Z(1) !=0 | X, Z(-1) X, Y -- Z(0) is okay but not required for this quadrant
什么是代表这个的好方法?
我在C#工作,但会接受任何语言的答案,因为它不是一个真正的语言问题 – 我可以翻译任何东西。
例:
if (oldvalue == 0 && newvalue == 0) return; if (oldvalue != 0) X(); if (newvalue != 0) Y(); Z(oldvalue != 0 ? -1 : 0 + newvalue != 0 ? 1 : 0);
我认为这看起来很不错,但还有其他方法可以做到。
int which = (oldvalue == 0 ? 0 : 1) + (newvalue == 0 ? 0 : 2) switch (which) { case 1: X(); Z(-1); break; case 2: Y(); Z(1); break; case 3: X(); Y(); break; }
这实际上比我正在处理的情况稍微简单一些。 如果oldvalue和newvalue非零并且彼此相等,则将newvalue视为0。
随意回答给定或附加约束。 还有一点点,但我认为开始时太多了。 如果事情看起来很有意思,我会在这里或在一个新问题中介绍其余部分。
我想我是在问这个问题,因为我经常会得到这些逻辑网格的东西,而且它们并不总是2×2,有时它们会更大。 很高兴地注意到我可以处理一些带有整个“条纹”的响应,就像注意到每次使用旧值时都要完成X!=但是好像我开始遇到一个需要处理一些表达逻辑的模式它更普遍而不是费力地将其转化为if then else语句。 我的意思是,如果我能提供一种逻辑网格并让编译器找出处理它的最佳方法,那将是非常酷的。
只是做一些疯狂的头脑风暴:
Conditions: oldvalue == 0 ? 0 : 1 newvalue == 0 ? 0 : 2 Actions: X = {false, true, false, true} Y = {false, false, true, true} Z(-1) = true where condition = 1 Z(1) = true where condition = 2
你有什么想法? 我将奖励任何物质参与。
让我们从另一个角度来看你的问题。 在设计代码体时,我尝试应用以下原则:
- 使它正确。
- 讲清楚。
- 简明扼要。
- 快一点。 … 以该顺序。
所有这些在某种程度上都是主观的。 然而,合理的人倾向于找到共同点 – 而且他们的对立面往往更加一致。 但是那边……
这里的首要任务是确保您的代码能够正常运行。 显然,有多种实现可以实现这一点 – 但我还要补充说,很容易certificate实现是正确的 。 实现此目的的一种方法是使代码读取更像规范(稍后将详细介绍)。
第二个优先事项是确保将来,当开发人员(包括原作者!)查看此代码时,他们将能够立即了解它正在做什么。 实现越复杂(读取: 花哨 ),开发人员就越难以立即理解代码正在做什么。
第三个优先事项 – 简短,简洁的代码,与前两个部分相反。 希望使代码更简洁,可能会导致您使用比实际解决问题所需的更复杂的构造。 虽然保持代码简短很重要,但我们不应该通过使其难以理解密集来实现这一点。
最后一个优先事项 – 表现 – 只有在重要时才重要。 这意味着,除非您已经执行了分析并将其识别为系统中的瓶颈,否则我不应该从性能的角度对实现进行复杂化。
现在我们已经看过应该推动我们决策的原则,让我们将它们应用到手头的问题上。 您已经提供了关于代码应该如何表现的非常清晰的规范。 让我们试着坚持下去:
void YourMethod( int oldValue, int newValue ) { bool oldValueNonZero = oldValue != 0; bool newValueNonZero = newValue != 0; if( oldValueNonZero ) { X(); } if( newValueNonZero ) { Y(); } if( oldValueNonZero && newValueNonZero ) { Z(); } }
那么为什么我喜欢这个特定的实现呢。 让我们分解吧。
首先 ,请注意我已选择创建临时布尔值来捕获测试旧/新值的结果,以确定它们是否为非零值。 通过捕获这些值,我避免多次执行计算,并且我还使代码更具可读性(见下文)。
其次 ,通过选择描述性名称oldValueNonZero
和newValueNonZero
我的实现清楚地表明了我的期望。 这既提高了代码的可读性,又清楚地向未来必须阅读它的开发人员传达了我的意图。
第三 ,请注意if()
测试的主体包含在{
和}
括号中 – 这有助于减少未来实现更改将破坏行为的可能性 – 例如,意外地包含新案例。 使用单行ifs
是未来问题的一个方法。
最后 ,我不会尝试将比较短路并尽早退出function。 如果性能非常重要,提前退出可能会有用。 但是,如果只有一个出口点(1) ,它会更容易理解方法的行为。
这段代码是否符合规范的要求? 我相信是这样。
是否易于阅读和理解。 至少对我而言,我会说是的。
这段代码是最紧凑或最复杂的逻辑短路方式吗? 几乎肯定不是……但在我看来,除了弥补这一点之外,还有其他特质。
无论你喜欢这种特殊的代码结构,在某种程度上都是品味和风格的问题。 但我希望我已经阐述的关于我如何选择组织它的原则可能会帮助你在将来做出这样的决定。
你已经表明你有时遇到类似的“逻辑网格”问题,但是案例数量更多的问题。 由于两个不同的原因,这些问题可能会变得复杂:
- 参数可以增加的值的数量 – 它们可以采用通用forms
MxN
。 - 维度数量增加 – 换句话说,规则中包含更多变量:
MxNxOxP...xZ
。
该问题的一个通用解决方案(如另一个响应所示)是将解决方案编码为多维矩阵 – 并为每个案例定义一组操作。 但是,规则完全可能重叠 – 为简单起见,可能需要将等效案例合并在一起。
我对处理一般案件的回应是……这取决于它。 如果条件可以减少到极少数情况,那么命令式if / else逻辑可能仍然是解决问题的最佳方法。 如果条件的数量非常大,那么使用声明性方法可能是有意义的,其中使用某种查找表或矩阵来编码案例。
1> – 仅从方法中获得单个退出点的原则的一个常见例外是前提条件。 通过首先检查所有/任何先决条件来避免嵌套和反转逻辑是更清洁的,并且如果它们被违反则失败(退出)该方法。
实际上,您可以使用多维数组来快速测试和分支进程。 如果查找的结果不是固定值,而是需要完成的某种工作,则可以使该数组成为委托数组,并使用方法或可执行代码(lambdas)填充它。
像这样:
private void Y() { } private void X() {} private void Z(int n) {} private void example(int oldvalue, int newvalue) { var array = new Action[2, 2]; array[0, 0] = () => { }; array[0, 1] = () => { Y(); Z(1); }; array[1, 0] = () => { X(); Z(-1); }; array[1, 1] = () => { X(); Y(); }; // invoke array[oldvalue == 0 ? 0 : 1, newvalue == 0 ? 0 : 1](); }
您还可以使用内联初始值设定程序初始化状态数组,但我发现在显式语句中突破单元格分配更容易阅读并跟踪其中的内容。
可以在包含此示例函数的类的构造函数中提前完成数组的初始化。 然后,示例函数中的执行将减少到
- 评估oldvalue == 0
- 评估newvalue == 0
- 索引到数组中以检索委托
- 执行委托以执行操作
这种技术导致非常非常少的执行开销(快速),可以很好地扩展到任意数量的维度,并保留代码和逻辑表之间的相关性,这使得后来与if语句的混乱相比更容易理解和修改到处都是。
额外信贷解决方案
这是一个非常有趣的主题,来自贡献者的一些很好的见解。 我认为你的问题已得到解答,所以我会专注于额外的功劳。 最简单的方法是更新您的表并将其应用于您最喜欢的解决方案。
让我们以完全描述每列条件的方式来表达问题。 如果新值等于零或旧值,我们将从第一列执行操作。 如果新值非零且不等于旧值,我们将从第二列执行操作。
New == 0 || New == Old New != 0 && New != Old ---------------------- ------------------------ Old == 0 | nothing Y, Z(1) Old != 0 | X, Z(-1) X, Y -- Z(0) is okay but not required for this quadrant
所以在dthorpe的解决方案中,我们用newValue == 0 || newValue == oldValue
替换newValue == 0
newValue == 0 || newValue == oldValue
:
private void example(int oldvalue, int newvalue) { var array = new Action[2, 2]; array[0, 0] = () => { }; array[0, 1] = () => { Y(); Z(1); }; array[1, 0] = () => { X(); Z(-1); }; array[1, 1] = () => { X(); Y(); }; array[oldvalue == 0 ? 0 : 1, newValue == 0 || newValue == oldValue ? 0 : 1](); }
在LBushkin的解决方案中,我们将newValue != 0 && newValue != oldValue
替换newValue != 0
并更新相应的变量名以保持可读性。 我也要把代码位混淆:
void YourMethod( int oldValue, int newValue ) { bool oldValueNonZero = oldValue != 0; bool newValueDifferentAndNonZero = newValue != 0 && newValue != oldValue; int zCondition = 0; if( oldValueNonZero ) { X(); zCondition--;} if( newValueDifferentAndNonZero ) { Y(); zCondition++;} if( zCondition != 0 ) { Z(zCondition); } }
田田!