清理冗长的嵌套循环






4.75/5 (8投票s)
辅助库,用于用紧凑、清晰且易于管理的代码替换长嵌套 for 循环
引言
我最近一直忙于一个机器验证码识别模块。 实现起来并不难,但找到不同参数、系数和权重以获得最佳结果需要花费时间(主要是 CPU 时间)。
我选择了最简单的方法,没有使用通用算法或神经网络,而是使用了常用的 for
语句,这种方法往往非常详尽。 最终,我实现了大约 75% 的准确率,这对于我的要求来说绰绰有余。
最佳解决方案涉及一个相当丑陋的嵌套 for 循环,不是最优雅的。
for (int lowMaskLevel = 0; lowMaskLevel <= 90; lowMaskLevel++)
{
for (int topMaskLevel = lowMaskLevel; topMaskLevel <= 255; topMaskLevel++)
{
for (int positiveFinalKoef = 0; positiveFinalKoef <= 100; positiveFinalKoef++)
{
for (int negativeFinalKoef = 0; negativeFinalKoef <= 100; negativeFinalKoef++)
{
for (int finePositiveKoef = 0; finePositiveKoef <= 30; finePositiveKoef++)
{
for (int fineNegativeKoef = 0; fineNegativeKoef <= 20; fineNegativeKoef++)
{
for (int hightLightKoef = 1; hightLightKoef <= 3; hightLightKoef++)
{
//…
var value = TestParemeters(lowMaskLevel,
topMaskLevel,
positiveFinalKoef,
negativeFinalKoef,
finePositiveKoef,
fineNegativeKoef,
hightLightKoef,
//…
);
if (value >= bestValue)
{
bestLowMaskLevel = lowMaskLevel;
bestTopMaskLevel = topMaskLevel;
bestPositiveFinalKoef = positiveFinalKoef;
bestNegativeFinalKoef = negativeFinalKoef;
bestFinePositiveKoef = finePositiveKoef;
bestFineNegativeKoef = fineNegativeKoef;
bestHightLightKoef = hightLightKoef;
//…
}
}
}
}
}
}
}
}
太糟糕了,不是吗?
参数、规则和限制的数量可能会改变,这一事实使得这种方法难以维护。 我放弃了这种方法,转而采用一种更易于管理的解决方案,以辅助类的形式处理迭代,从而使事情变得清晰且可维护。
示例
现在我可以像常规方法参数一样内联定义迭代器参数,这看起来很不错,很小心,并且在支持或任何更改方面都更好。
int bestValue = 0;
object[] bestArguments = null;
Iterator.Execute(() => TestParemeters(
Iterator.Range(0, 90),
Iterator.Range(args => (int)args[0], args => 255),
Iterator.Range(0, 100, 10),
Iterator.Range(0, 100),
Iterator.Range(0, 30),
Iterator.Range(0, 20),
Iterator.Range(0, 3)
), invocationResult =>
{
if (invocationResult.Value >= bestValue)
{
bestValue = invocationResult.Value;
bestArguments = invocationResult.Arguments;
}
});
使用代码
要使用它,您必须添加对 WestWayConsulting.Tools.Iterator.dll
程序集的引用
注意!此程序集引用了 Immutable Collections,因此它是使用 .NET Framework 4.5 编译的。
该工具具有外观类 Iterator
来定义和执行迭代。 要执行迭代,您必须调用静态 Execute
方法,该方法有两种重载 - 一种用于调用返回值的方法,另一种用于调用 action 方法。 Execute
方法接受以下参数
- expression:定义方法调用的表达式。 可以是静态方法、实例方法或类型构造函数。 如果方法返回值,则
Execute
方法将返回执行结果列表; - callbackAction(可选):将在每次方法执行后执行的方法委托。 方法接受
InvocationResult
类的实例,该实例具有方法执行的结果、参数数组、异常; - throwExceptionOnError(可选,默认为 true):定义如果方法调用中发生任何异常,是否应抛出异常或忽略异常;
当您在迭代器中定义方法时,您仍然可以像往常一样使用任何类型的参数,并将它们与迭代器定义混合使用。 因此,以下语句将有效
Iterator.Execute(() => MyMethod(
1,
DateTime.Now,
Iterator.Range(0, 100, 10),
Iterator.RangeParallel(0, 100),
new WebClient()
});
注意!您不能在主方法调用之外定义迭代器。 以下语句将抛出 InvalidOperationException
异常
Iterator.Execute(() => MyMethod(
new DateTime(2013, 1, Iterator.Range(0, 100, 10))
});
在当前版本中,可以使用以下迭代器
- Range(from, to, step:生成序列迭代器,从 from 值到 to 值,步长为指定的 step。 具有用于生成整数和双精度序列的重载。 等效于
for(int value = from; value<=to; value+=step) { //... }
- RangeParallel(from, to, step):与 Range 相同,但执行并行运行。 等效于
Parallel.ForEach(CreateIterator(from, to, step), (value) => { //... });
- Random(minValue, maxValue, count):生成具有指定数量的随机数(count)的序列,介于 minValue 和 maxValue 之间。 具有用于生成整数和双精度序列的重载。 等效于
var random = new Random(); for(int i = 0; i<=count; value++) { var value = random.Next(minValue, maxValue); //... }
- RandomParallel(minValue, maxValue, count):与 Random 相同,但执行并行运行。 等效于
var random = new Random(); Parallel.For(0, count, (i) => { var value = random.Next(minValue, maxValue); //... });
- Enumerator(enumerable):从 enumerable 执行迭代。 等效于
foreach(var value in enumerable) { //... }
- EnumeratorParallel(enumerable):与 Enumerator 相同,但执行并行运行。 等效于
Parallel.ForEach(enumerable, (value) => { //... });
每个定义都具有带有动态参数的重载。 通过使用它们,您可以使用复杂的规则定义迭代参数。
定义动态参数的函数接受来自上面参数的值数组。
Iterator.Execute(() => MyMethod(
1, //arg[0]
DateTime.Now, //arg[1]
Iterator.Range(args => (int)args[0], args => 255), //arg[2]
Iterator.RandomParallel(args => (int)args[2], args => DateTime.Now.Seconds+255), //arg[3]
new WebClient()
});
它是如何工作的
Execute
方法从 Labmda 表达式解析顶层参数,并生成参数处理程序的链接链。 每个处理程序都知道要执行的下一个处理程序。
Iterator.Execute(() => TestParemeters( executor {}
Iterator.Range(0, 90), |_iterator {arg[0]}
Iterator.Range(args => (int)args[0], |_iterator {arg[0], arg[1]}
args => 255), |
1, |_constant {arg[0], arg[1], arg[2]}
DateTime.Now, |_method as argument {arg[0], arg[1],
| arg[2], arg[3]}
); |_method executor
处理程序从上一步获取其参数; 生成新的参数并将其添加到列表中,然后将其传递给下一个处理程序。 如果参数是一个方法,则处理程序将方法委托传递给参数列表,并且在执行主方法之前立即获得一个实际值。 如果参数是一个迭代器,则为生成的枚举中的每个值调用下一个处理程序。 参数列表是不可变的,因此可以保证线程安全。 在链的末尾,方法执行器使用参数列表调用主方法委托,并在指定的情况下调用回调方法。
请注意,回调方法不是线程安全的。 如果您使用并行迭代器,则必须自己实现锁定和同步。