Хорошая практика: оператор Loop And If

(Я копирую/вставляя тот же вопрос, который я разместил на Codereview здесь: https://codereview.stackexchange.com/info/1747/good-practice-loop-and-if-statement)

Я хотел бы знать, что лучше всего:

версия A:

loop1
  if condition1
    code1

  if condition2
    code2

  if condition3
    code3

Или, версия B:

if condition1
  loop1 with code1

if condition2
  loop1 with code2

if condition3
  loop1 with code3

Я реализовал версию B, потому что она более читаема для меня, и всегда проверяя одно и то же условие, кажется абсурдным.

Но цикл n раз один и тот же массив можно было бы считать абсурдным тоже:)

Ответ 1

Из того факта, что вы рассматриваете версию B, которая имеет условия вне циклов, я предполагаю, что значение истинности условий не меняется с элементами в массиве. В противном случае ваш вопрос не имеет смысла.

Заключение

Если условия сложны, так что они занимают много времени в оценке относительно размера массива, тогда версия B выполняется быстрее. Если это наоборот, то версия A работает быстрее.

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

Объяснение

Когда условие достаточно просто относительно размера массива, стоимость итерации перевешивает стоимость оценки состояния. Как показано ниже с рубином, версия B медленнее.

$a = (1..10000)

def versionA
  $a.each do
    nil if true
    nil if false
    nil if true
  end
end

def versionB
  $a.each {nil} if true
  $a.each {nil} if false
  $a.each {nil} if true
end

require 'benchmark'
n = 10000
Benchmark.bmbm do|b|
  b.report('A'){n.times{versionA}}
  b.report('B'){n.times{versionB}}
end

Rehearsal -------------------------------------
A   7.270000   0.010000   7.280000 (  7.277896)
B  13.510000   0.010000  13.520000 ( 13.515172)
--------------------------- total: 20.800000sec

        user     system      total        real
A   7.200000   0.020000   7.220000 (  7.219590)
B  13.580000   0.000000  13.580000 ( 13.605983)

С другой стороны, если оценка условий является более дорогостоящей относительно итерации по массиву, то эффект первого становится более важным, чем последний, и скорость будет наоборот.

$a = (1..100)

def versionA
  $a.each do
    nil if (1..10).each{nil} && true
    nil if (1..10).each{nil} && false
    nil if (1..10).each{nil} && true
  end
end

def versionB
  $a.each {nil} if (1..10).each{nil} && true
  $a.each {nil} if (1..10).each{nil} && false
  $a.each {nil} if (1..10).each{nil} && true
end

require 'benchmark'
n = 10000
Benchmark.bmbm do|b|
  b.report('A'){n.times{versionA}}
  b.report('B'){n.times{versionB}}
end

Rehearsal -------------------------------------
A   2.860000   0.000000   2.860000 (  2.862344)
B   0.160000   0.000000   0.160000 (  0.169304)
---------------------------- total: 3.020000sec

        user     system      total        real
A   2.830000   0.000000   2.830000 (  2.826170)
B   0.170000   0.000000   0.170000 (  0.168738)

Ответ 2

Версия B намного более заметна, так как вы обычно можете пожелать

 if condition1
     loop1 with code1
 if condition2
     loop2 with code2
 if condition3
     loop3 with code3

который не может быть быстро и просто рефакторирован из A.

Изменить: однако, если условие цикла "динамически управляется" его телом, вы должны использовать A. Дополнительная информация о семантике кода необходима.

Ответ 3

Хорошо, я думаю, что ответ более квалифицирован, чем казалось бы. Большинство методов кодирования предполагают, что блокирование блоков кода невелики, поэтому с этой точки зрения это действительно сводится к тому, "насколько велик весь блок"? Но я бы подумал об этом в соответствии с намерением кода. Например:

foreach (widgets)
    if (widget is red) put in left bin
    if (widget is blue) put in center bin
    if (widget is green) put in right bin

против

if (making widgets red) 
    foreach (widgets) put in left bin
if (making widgets blue)
    foreach (widgets) put in center bin
if (making widgets green) 
    foreach (widgets) put in right bin

Каждая структура рассказывает другую историю о ваших намерениях. В первом случае мы выполняем итерации над виджетами и принимаем решения по каждому из них, в то время как в последнем случае мы повторяем решения в развернутом цикле и внося изменения в виджеты. Это, конечно, имеет смысл для случаев else-if, но в целом, если вы можете легко сделать выражение о блоке кода (например, в этом цикле я сортирую виджеты по цвету) без необходимости писать абзац, это наиболее понятный код, и что цель наилучшей практики для начала.

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

Ответ 4

В сценарии A вы выполняете три проверки if для каждого повторения цикла. В сценарии B у вас есть только три контрольных суммы. Что касается временной сложности, вторая версия намного лучше.

Ответ 5

Как насчет того, если вы должны позволить себе 2 условия?

Если условие 1 и условие 2 подтверждены, что произойдет?

Я думаю, что лучше условие