Вложенные циклы 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);
}
}
}
}