嵌套的 FOR 循环:可读性和性能

本文关键字:可读性 性能 循环 FOR 嵌套 | 更新日期: 2023-09-27 17:56:23

我理解嵌套的 FOR 循环。我了解他们做什么,以及他们是如何做到的。但我的问题是,它们对我来说似乎非常不可读。

举个例子:

for (int i = 0, y = 0; y <= ySize; y++) {
    for (int x = 0; x <= xSize; x++, i++) {
        vertices[i] = new Vector3(x, y);
    }
}

现在,这个循环非常简单。这只是一个 x/y "二维"循环。但是,随着我向这个嵌套循环添加越来越多的"维度",有没有办法使代码不会在嵌套中出现可怕的嵌套混乱和愚蠢数量的回溯计数器变量(i,x,y,z等)?

此外,额外的嵌套是否以线性方式影响性能,或者附加 FOR 是否随着嵌套更多的 FOR 而使事情变得越来越低效?

嵌套的 FOR 循环:可读性和性能

我认为您在这里遇到的问题不是嵌套的for循环,而是循环中变量的不寻常使用。

左大括号前的换行符也有助于提高可读性(尽管这是主观的)。

这个怎么样:

int i = 0;
for (int y = 0; y <= ySize; y++)
{
    for (int x = 0; x <= xSize; x++)
    {
        vertices[i++] = new Vector3(x, y);
    }
}

对于其他维度,这种方法也应该保持相对可读性(在此示例中,我已将i的递增移到其自己的行中,如 usr 所建议的那样)。

int i = 0;
for (int y = 0; y <= ySize; y++)
{
    for (int x = 0; x <= xSize; x++)
    {
        for (int a = 0; a <= aSize; a++)
        {
            for (int b = 0; b <= bSize; b++)
            {
                vertices[i] = new Vector3(x, y, a, b);
                i++;
            }
        }
    }
}
关于性能,

我建议首先专注于确保代码是人类可读和可理解的,然后测量运行时性能,可能使用RedGate ANTS等工具。

通常的解决方案是重构为包含一个或两个 for 循环的方法,并不断重构,直到每个方法都清晰且不会太大。

停止缩进并将循环结果数据与应用逻辑分离的另一种解决方案是使用 Linq。

int i = 0;
var coordinates = from y in Enumerable.Range(0, ySize + 1)
                  from x in Enumerable.Range(0, xSize + 1)
                  select new { x, y, i = i++ };
foreach (var coordinate in coordinates) {
    vertices[coordinate.i] = new Vector3(coordinate.x, coordinate.y);
}

仅当已声明vertices数组时,才会执行此操作。如果您可以创建一个新数组,那么您可以简单地执行以下操作:

var vertices = (from y in Enumerable.Range(0, ySize + 1)
                from x in Enumerable.Range(0, xSize + 1)
                select new Vector3(coordinate.x, coordinate.y)
               ).ToArray();
var vertices =
 (from y in Enumerable.Range(0, ySize)
  from x in Enumerable.Range(0, xSize)
  select new Vector3(x, y)).ToList();

循环被过度使用。大多数循环可以表示为查询。这使得它们更容易编写和维护,并且使它们成为表达式,而不是更容易移动的语句。

性能要差得多,比如这里的 3-10 倍。这对您的具体情况是否重要取决于在这里花费了多少时间以及您的绩效目标是什么。

a) 通常你会发现你不需要很深的嵌套,所以这不会有问题。

b) 您可以将嵌套循环转换为单独的方法。(即,如果您在c中嵌套d b a - 您可以创建一个接受ab作为参数并执行cd的方法。您甚至可以让 VS 通过选择c循环并单击编辑>重构>提取方法)来为您执行此操作。

至于性能 - 显然更多的嵌套意味着更多的迭代,但如果你有它们 - 你需要它们。恕我直言,仅将嵌套循环更改为包含在原始循环中(并计算您在"实际"代码中的位置)通常不会以任何明显的方式提供帮助。

N 深度循环嵌套可能比 C# 中可表达的任何替代方法更具可读性。 相反,请考虑使用具有向量算术作为原语的高级语言:例如,在 NumPy 中,代码的直接等价物是

xSize = 10
ySize = 20
vertices = np.meshgrid(
    np.linspace(0, xSize, xSize),
    np.linspace(0, ySize, ySize))

N维泛化为

sizes = [10, 20, 30, 40, 10] # as many array entries as you like
vertices = np.meshgrid(*(
    np.linspace(0, kSize, kSize)
    for kSize in sizes))

只是在这里乱搞,但是填充空格以使循环条件对齐怎么样? 以下是 Everett 的代码重新格式化@Richard:

int i = 0;
for             (int y = 0; y <= ySize; y++) {
    for         (int x = 0; x <= xSize; x++) {
        for     (int a = 0; a <= aSize; a++) {
            for (int b = 0; b <= bSize; b++) {
                vertices[i++] = new Vector3(x, y, a, b);
            }
        }
    }
}