Рекурсия и изменение глобальных объектов

DrawingHandsLg
Сегодняшняя статья имеет отношение к функциональному программированию и посвещена сразу двум темам: использованию рекурсивных функций и изменению внешних (т.н. глобальных) по отношению к функции объектов с помощью ее синтаксиса. Понятие рекурсии и глобальных/локальных переменных являются одними из базовых в функциональном программировании. Данные темы будут проиллюстрированы на основании создания функции, предназначенной для ввода данных непосредственно из анкет. Конечно же эту задачу можно решить и с помощью способов, описанных в главе "Создание массива данных в R": создания векторов (один респондент - один вектор) или с помощью функции fix(), которая вызывает привычное окно со строками и столбцами. Вместе с тем, эти способы являются неоправданно трудоемкими, когда речь идет о вводе больших массивов данных. Также они ведут к большему количеству ошибок ввода. Что касается специально созданной для этого функции, то она позволяет частично контролировать вводимые данные, а также автоматически сливает их в общий массив.
Рекурсия и рекурсивные функции
Согласно википедии, "реку́рсия — наличие в определении, описании, изображении какого-либо объекта или процесса самого этого объекта или процесса, то есть ситуация, когда объект является частью самого себя. Термин «рекурсия» используется в различных специальных областях знаний — от лингвистики до логики, но наиболее широкое применение находит в математике и информатике".
Если говорить о функциональном программировании, то суть рекурсии сводится к вызову функции из тела (синтаксиса) этой же функции. Прежде, чем рассмотреть это на примере, надо поговорить о такой функции как scan(). Прежде всего она предназначена для считывания данных из текстовых файлов. Кроме этого, она может быть использована и для считывания данных введенных с клавиатуры. Выглядит это так:
1
2
3
4
5
6
  > temp <- scan("") # аргумент пустых кавычек используется для считывания данных с клавиатуры
  1: 1               # ввод числового значения + Enter
  2: 1               # ввод числового значения + Enter
  3: 1               # ввод числового значения + Enter
  4:                 # Enter для окончания ввода данных
  Read 3 items
Как видно из первой строки, для ввода данных с клавиатуры необходимо использовать аргумент "". После этого сначала вводится числовое значение, а потом нажимается Enter. Далее этот процесс повторяется до тех пор пока Enter не будет нажат для пустого поля (т.е. без введения числового значения). В результате будет создан числовой вектор с соответствующим количеством элементов.
Как и любая другая функция, scan() имеет множество аргументов, которые вносят разнообразие в ее работу. В нашем случае будет интересен такой аргумент как nmax, ограничивающий величину вектора, в который вводятся данные.
 7
 8
 9
10
11
12
13
  > temp <- scan("", nmax=5)
  1: 5
  2: 4
  3: 3
  4: 2
  5: 1
  Read 5 items
После введения пятого элемента ввод данных завершился автоматически. Наконец, можно вводить данные через пробел, а Enter нажать только в конце:
14
15
16
17
18
  > temp <- scan("", nmax=5)
  1: 7 6 5 4 3 2 1
  Read 5 items
  > i
  [1] 7 6 5 4 3
В 15 строке все данные вводятся через пробел и только потом нажимается Enter. При этом количество вводимых элементов равно 7, а не 5. Поэтому два последних значения не были записаны.
Теперь перейдем к рекурсивной функции:
19
20
21
22
23
24
25
26
27
  > temporary10 <- function() {
      temp <- scan("",nmax=1)
      if (temp > 10) {
        print("ОШИБКА!!!")
        temporary10()
      } else {
        return(temp)
      }
    }
В функции temporary10() с помощью 20 строки одно числовое значение записывается в скаляр temp. Далее это значение проверяется (строка синтаксиса 21): если оно больше 10, то была допущена ошибка ввода. Это продиктовано тем, что эта функция предназначена для ввода ответов на вопросы со шкалой от 0 до 10. При ошибке на экран выводится надпись "ОШИБКА!!!" (строка синтаксиса 22) и опять запускается функция temporary10(), то есть опять повторяется ввод данных (строка синтаксиса 23). Это будет происходить до тех пор, пока не будет введно значение в диапазоне от 0 до 10. После чего введенное значение будет передано далее (об этом ниже).
Изменение глобальных объектов
Одной из основ функционального программирования является невозможность изменения внешних по отношению к функции объектов с помощью ее синтаксиса (или изнутри тела функции). При этом они могут быть использованы внутри функций.
Такого рода внешние объекты часто называются глобальными, в то время как объекты созданные непосредственно внутри самой функции называются локальными. Примером локального объекта в функции temporary10() является скаляр temp. Как видно он создается внутри функции. Также он передается во внешнюю по отношению к функции среду.
Рассмотрим пример:
28
29
30
31
32
33
34
35
36
  > test <- 3
  > fun <- function() {
      test <- test + 2
      print(test)
    }
  > fun()
  [1] 5
  > print(test)
  [1] 3
Сначала создается скаляр test. По отношению к функции fun() он является глобальным объектом. Внутри функции он используется для изменения своего собственного значения. Далее функция выводит новое значение для объекта test. Как видно, внутри функции финальное значение test равно 5 (что и выводится на экран в результате запуска функции). Но если после этого вывести значение test непосредственно, а не с помощью функции, то мы опять получим значение равное 3. Таким образом, реально значение test не изменилось.
Несмотря на это ключевое свойство функционального программирования, в каждом языке программирования есть свои способы изменения глобальных объектов из тела функции. В R это делается с помощью специального символа присваивания: <<- .
Модифицируем предыдущий пример:
37
38
39
40
41
42
43
44
45
  > test <- 3
  > fun <- function() {
      test <<- test + 2
      print(test)
    }
  > fun()
  [1] 5
  > print(test)
  [1] 5
Как из него видно, скаляр модифицируется не только внутри функции, но и меняется за ее пределами. Таким же способом можно изменять и другие объекты (векторы, списки, фреймы данных).

А теперь вместе: ввод данных
Используя функцию temporary10(), а также подобные ей, и применив простой цикл с условными операторами можно создать базовый скрипт для ввода данных непосредственно на основании анкет.
Соответствующая функция выглядит следующим образом:
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
  > vvod <- function() {
      for (i in 1:9) {
        if (i==1) {
          var <- temporary10()
          print("Переменная (a1) введена успешно")
          anketa[[i]] <<- var
        }
        if (i==2) {...}
        if (i==3) {...}
        if (i==4) {...}
        if (i==5) {...}
        if (i==6) {...}
        if (i==7) {...}
        if (i==8) {...}
        if (i==9) {
          var <- temporary3()
          print("Переменная (c4) введена успешно")
          anketa[[i]] <<- var
        }
      }
      massiv <<- rbind(massiv,as.vector(anketa,mode="numeric"))
      print("АНКЕТА ВВЕДЕНА!!!")
    }
Функция vvod() включает цикл из 9 итераций, каждая из которых связана с конкретным анкетным вопросом. Функция, используемая в каждой итерации, определяется количеством категорий вопроса. Таким образом, одни и те же функции будут использованы для разных вопросов с одинаковым количеством категорий. На каждом этапе введенные данные записываются в список anketa на соответствующую позицию (от 1 до 9, поскольку вопросов всего 9).
После завершения цикла полученный список преобразовывается в числовой вектор и записывается в заранее созданный фрейм данных massiv. Вот и все.

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

Вверх
blog comments powered by Disqus