Вложенные циклы FOR: читаемость и производительность

Я понимаю вложенные циклы 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 "2 мерный" цикл. Но поскольку я добавляю все больше "измерений" к этому вложенному циклу, есть способ сделать код не ужасным беспорядком в гнездах внутри гнезд и глупостей переменных счетчика обратного слежения (i, x, y, z, и др.)?

Кроме того, добавляет ли дополнительная вложенность линейность в производительности, или добавочные FORs делают вещи все более и более неэффективными, когда вы вставляете их больше?

Ответ 1

Я думаю, что проблема, которая у вас здесь, - это меньше вложенных циклов 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

Ответ 2

Обычным решением является рефакторинг в методы, которые содержат один или два для циклов, и сохраняют рефакторинг, пока каждый метод не станет ясным и не слишком большим.

Другим решением, чтобы остановить отступ и отделить полученные от цикла данные от прикладной логики, является использование 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();

Ответ 3

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

Петли чрезмерно используются. Большинство циклов могут быть выражены в виде запросов. Это облегчает их запись и поддержку, и это делает их выражениями в отличие от операторов, которые легче перемещаются.

Производительность намного хуже, например, 3-10x. Неважно, будет ли это важно для вашего конкретного случая, зависит от того, сколько времени потрачено на это и каковы ваши цели эффективности.

Ответ 4

a) Обычно вы обнаружите, что вам не понадобится очень глубокая вложенность, поэтому это не будет проблемой.

b) Вы можете сделать вложенные циклы отдельным методом. (т.е. если вы размещаете d в c в b в a), вы можете сделать метод, который принимает a и b в качестве параметров, и c и d Вы можете даже позволить VS сделать это для вас, выбрав цикл c и нажав Edit- > Refactor- > Extract Method.)

Что касается производительности - очевидно, что больше вложенности означает больше итераций, но если они есть, они вам нужны. Простое изменение вложенного цикла, которое должно быть включено в исходный цикл (и вычисление, где вы находитесь внутри "фактического" кода), ИМХО обычно не помогает каким-либо заметным образом.

Ответ 5

Гнездо петли N-deep, скорее всего, будет больше, чем любая альтернатива, выражаемая на С#. Вместо этого рассмотрим использование языка более высокого уровня, который имеет векторную арифметику как примитив: например, в 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))

Ответ 6

Просто возитесь здесь, но как насчет пробелов, чтобы условия цикла совпадали? Вот код @Richard Everett немного переформатировал:

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);
            }
        }
    }
}