65.9K
CodeProject 正在变化。 阅读更多。
Home

清理冗长的嵌套循环

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (8投票s)

2013年8月8日

CPOL

3分钟阅读

viewsIcon

25201

downloadIcon

288

辅助库,用于用紧凑、清晰且易于管理的代码替换长嵌套 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)的序列,介于 minValuemaxValue 之间。 具有用于生成整数和双精度序列的重载。 等效于
    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

处理程序从上一步获取其参数; 生成新的参数并将其添加到列表中,然后将其传递给下一个处理程序。 如果参数是一个方法,则处理程序将方法委托传递给参数列表,并且在执行主方法之前立即获得一个实际值。 如果参数是一个迭代器,则为生成的枚举中的每个值调用下一个处理程序。 参数列表是不可变的,因此可以保证线程安全。 在链的末尾,方法执行器使用参数列表调用主方法委托,并在指定的情况下调用回调方法。

请注意,回调方法不是线程安全的。 如果您使用并行迭代器,则必须自己实现锁定和同步。 

© . All rights reserved.