Основы программирования: циклы

Копия loop-image-for-blog



Согласно Wiki, "цикл — разновидность управляющей конструкции в высокоуровневых языках программирования, предназначенная для организации многократного исполнения набора инструкций... Последовательность инструкций, предназначенная для многократного исполнения, называется телом цикла. Единичное выполнение тела цикла называется итерацией. Выражение определяющее, будет в очередной раз выполняться итерация, или цикл завершится, называется условием выхода или условием окончания цикла (либо условием продолжения в зависимости от того, как интерпретируется его истинность — как признак необходимости завершения или продолжения цикла). Переменная, хранящая текущий номер итерации, называется счётчиком итераций цикла или просто счётчиком цикла".
Таким образом, циклы позволяют запускать необходимую часть программного кода до тех пор, пока выполняются условия запуска. Они бывают двух видов -  for и while. Циклы for выполняют программный код последовательно для каждого значения в определенном множестве. Можно назвать такой способ запуска референтным контролем. Циклы while запускают код до тех пор, пока соблюдаются условия запуска. Этот способ можно назвать логическим контролем. Поэтому, в случае использования циклов while, следует внимательно подбирать логическое условие, чтобы он не выполнялся бесконечно.
Циклы for
Рассмотрим пример. Многие из вас знают притчу о том, как были придуманы шахматы. Самым интересным в этой истории является способ оплаты творения мудреца. Его суть заключалась в том, что на первую клетку шахматной доски падишах должен был положить одно зернышко пшеницы, на вторую - два, на третью - четыре и так далее. То есть на каждую следующую клетку нужно было положить в два раза больше зерна, чем лежало на предыдущей. В конце мудрец должен был забрать все зерно. Обычные калькуляторы не позволят вам посчитать сколько зерна будет лежать на шахматной доске (с определенной клетки числа станут слишком велики). Считать в ручную тоже не вариант.
А вот с помощью R (или другого языка программирования) сделать это очень просто (строки 1-5, далее другой пример):
1
2
3
4
5
  > x = 1
  > for (i in 1:63) {
      x <- x*2
      print(as.character(x))
    }
Сначала мы ложим первое зернышко на первую клетку (создаем скаляр x, равный единице). В следующей строке мы пишем for (так начинается цикл этого типа) и в скобках указываем счетчик цикла. В нашем случае (i in 1:63) означает лишь то, что действие будет повторено 63 раза. Далее в фигурных скобках мы прописываем тело цикла, указывающее что конкретно произойдет этих 63 раза. Так, сначала х будет умножено на два и записано само в себя, а потом - выведено на экран. При этом функция as.character() необходима для того, чтобы результаты выводились привычным для нас способом, а не в виде специальных математических сокращений (последнее бывает в тех случаях, когда числа или слишком малы или слишком велики). В результате в первой строке будет выведено "2", во второй - "4" и т.д. до шестдесяттретей в которой будет выведено "9223372036854775808". Короче мудрец улучшил свое благосостояние. А может его просто казнили...
Продолжим тему смертной казни - обратимся к примеру, демонстрирующему решение практической проблемы. Ниже приведен небольшой цикл, который я использовал совсем недавно при анализе отношения населения Украины к отмене смертной казни.
Перед тем, как создать сам цикл, я создал два хранилища данных (строки 6 и 7):
а) пустой список deathPenaltyList (более детально о списках мы поговорим в одной из следующих тем), необходимый для "складирования" основных результатов работы цикла;
б) вектор filt, который включает числа, соответствующие годам проведения социологического мониторинга (этот вектор будет использован в качестве набора элементов, с которым связан счетчик цикла).
 6
 7
 8
 9
10
11
12
  > deathPenaltyList <- list()
  > filt <- c(1992, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2008, 2010, 2012)
  > for (i in 1:length(filt)) {
      test <- indexes[which(indexes$godNew == filt[i]) , ]
      test$godNew <- NULL
      deathPenaltyList[[i]] <- test
    } 
В счетчике цикла (строка 8) я указываю, что действия необходимо повторить для каждого i в наборе от 1 до значения, соответствующего длине вектора filt (функция length() возвращает длину объекта). Длина вектора равна количеству элеметов, которые в него входят. Следовательно выражение "1:length(filt)" равнозначно выражению "1:17". Тело же цикла заключается в следующем:
Строка 9: записать в test наблюдения фрема indexes, которые соответствуют указанным условиям; так, для первого этапа (или итерации) цикла (i = 1) в соответствии с логическим условием будут отобраны все наблюдения для 1992 года (так как filt[1] = 1992), для втого (i = 2) - наблюдения для 1994 года (так как filt[2] = 1994) и так далее;
Строка 10: в созданном на этом этапе фрейме данных test удалить переменную про год проведения исследования (в дальнейшем она не дает никакой полезной информации, поскольку в массив записываются данные только одного года);
Строка 11: массив данных test записать в список deathPenaltyList на место соответствующее этапу (итерации) цикла.
Что это даст? На каждом этапе будет создан фрейм данных test для соответствующего года (то есть на каждом новом этапе этот фрейм будет переписываться), из него будет удалена ненужная переменная и он будет размещен в список deathPenaltyList на свое собственное место (оно определяется этапом цикла). Следовательно к концу цикла, список будет содержать 17-ть фреймов с данными по каждому из годов. Первоначально же данные за все года размещались в массиве indexes, что не позволяло работать с каждым годом исследования по отдельности.
Этот цикл позволил сократить программный код почти в 17 раз и это хорошо :)
Циклы while
В целом главное отличие циклов while от циклов for заключается в свойствах счетчиков. Если в циклах for счетчик ссылается на некое множество, то в случае циклов while счетчик проверяет логическое условие. Если это условие не соблюдается в рамках текущей итерации, выполняется тело цикла и он переходит к следующей итерации. Если же условие соблюдается, то цикл завершает свою работу.
Рассмотрим следующий пример:
13
14
15
16
17
  > i <- 5
  > while (i > 0) {
      print("R!!!")
      i <- i - 1
    }
Выглядит примитивно, не так ли? Суть этого цикла заключается в том, что пока i > 0, будет выполняться тело цикла, а именно - выводиться на экран надпись "R!!!" и вычитаться единица из i (последнее необходимо для того, чтобы цикл завершился после пятого этапа).
Не самый воодушевляющий пример. Насколько мне известно, такие циклы хороши для моделирования развития естественных процессов. И поскольку ничем таким я не занимался, мне придется выдумать что-нибудь... что-нибудь фантастическое :)
Итак, жили были хоббиты и было их 100 особей. За год для каждых десяти хоббитов прирост вследствие рождаемости составлял 1 дополнительного хоббита. Вместе с тем, в результате набегов гоблинов и смертности потери хоббитов составляли 5... штук. Теперь с помощью цикла while посмотрим сколько хоббитов будет через 10 лет. При этом (для того, чтобы мне было легче) будем считать, что каждый год хоббиты сначала рождались, а уже потом умирали и становились жертвами гоблинов.
18
19
20
21
22
23
24
25
  > i = 100
  > j = 1
  > while (j < 11) {
      i = floor(i * 1.1)
      i = i - 5
      print(i)
      j = j + 1
    }
Итак, i задает начальное количество хоббитов, j - обозначает первый год. Что касается тела цикла, проанализируйте его самостоятельно :) Единственное замечание по поводу функции floor(). Она округляет десятичную дробь к меньшему целому числу (например 9,99 будет округлено к 9). А использую я ее для того, чтобы хоббиты получались целыми. Кстати, через 10 лет хоббитов станет 174.

статью подготовил кандидат социологических наук Сергей Дембицкий

Вверх
blog comments powered by Disqus