Треки
/
R
R
/
Вправи
/
Перетворення координат
Перетворення координат

Перетворення координат

Навчальна вправа

Вступ

Функції ми вже розглядали в концепції Основи, з такими, наприклад, прикладами:

squareit <- function(x) {
    x * x
}

squareit(3)
#> [1] 9

# shorter form
squareit_short <- function(x) x ^ 2

Придивившись ближче до визначення squareit, ми можемо виділити кілька частин:

  • Функція приймає формальний аргумент x.
  • Є тіло функції, зазвичай у фігурних дужках { }.
  • Щойно обʼєкт функції створено, його присвоюють змінній squareit.

У R функція є обʼєктом першого класу, так само як числа й рядки тексту (англ. string). Отже, squareit <- function... синтаксично є таким самим присвоєнням, як і x <- 42.

Аргументи

R не проводить чіткого розмежування між позиційними та іменованими аргументами, на відміну від інших скриптових мов, як-от Python і Julia.

Під час виклику функції значення можна передавати позиційно або за іменем. Другий спосіб стає в пригоді для складних функцій із багатьма аргументами, де важко запамʼятати їхній порядок.

f <- function(x, y) x / y

# call positionally
f(4, 2)
#> [1] 2

# call by name
f(y = 2, x = 4)
#> [1] 2

Необовʼязкові аргументи

Типові значення аргументів можна задати у визначенні функції, але вони повинні йти після всіх аргументів без типових значень.

Далі ми можемо вибрати: прийняти типове значення чи перевизначити його.

g <- function(x, y = 2) x / y

# default y value
g(6)
#> [1] 3

# explicit y value
g(6, 3)
#> [1] 2

Додаткові аргументи

Щоб приймати довільну кількість додаткових аргументів, використаймо ... (три крапки) у визначенні. Будь-які додаткові значення у виклику функції можна перетворити на вектор, але про альтернативний спосіб використати ці «крапкові аргументи» (у кількох інших мовах їх називають «varargs») читаймо далі.

var_f <- function(x, y, ...) {
  print(c(...))
}

var_f(2, 3, "opt1", "opt2")
#> [1] "opt1" "opt2"

Середовище функції

Раніше ми казали, що і формальні аргументи, і тіло є складниками функції.

Насправді є й третій складник: середовище, у якому визначено функцію.

Це можна проілюструвати на прикладі вкладених функцій:

outer_func <- function(x) {
  inner_func <- function(y) {
    x * y
  }
  
  inner_func(3)
}

outer_func(5)
#> [1] 15

Виклик функції передає x = 5 у зовнішню функцію, і це значення доступне в тілі цієї функції.

Внутрішня функція є частиною тіла зовнішньої функції й має доступ до значення x. Інакше кажучи, x = 5 перебуває в середовищі внутрішньої функції.

Технічно це називають замиканням.

Середовище особливо важливе для крапкових аргументів, адже будь-які значення, передані в такий спосіб, можна передати далі у виклики функцій усередині тіла функції. Зовнішній функції не потрібно знати чи перейматися тим, що означають крапкові аргументи.

f_var <- function(x, ...) {
  sum(x, ...)
}

x <- c(1, 2, NA, 6)

# for sum(), na.rm defaults to FALSE
f_var(x)
#> [1] NA

# pass through the na.rm value
f_var(x, na.rm = TRUE)
#> [1] 9

Цей прийом широко використовують бібліотеки Tidyverse, як-от stringr. Багато функцій stringr - це зручна обгортка навколо низькорівневих функцій із stringi та базового R.

Додаткові аргументи, передані функціям str_*(), просто передаються далі до цих низькорівневих функцій.

Функції як повернені значення

Зовнішня функція може визначити внутрішню функцію (іменовану або анонімну, як описано нижче) і використати її як повернене значення.

Повернена функція включатиме середовище, у якому її було визначено.

times_y <- function(x) {
  # anonymous function - see next section
  \(y) x * y
}

f <- times_y(3)
class(f)
#> [1] "function"

f(4)
#> [1] 12

Анонімні функції

Коли ми визначаємо функцію, то зазвичай привʼязуємо отриманий обʼєкт функції до змінної.

x <- 2

# function definition captures x from the environment
doubleit_short <- function(y) x * y

Це дає змогу легко використати функцію далі в скрипті, але таке привʼязування не обовʼязкове. Коротка функція, яку використовують лише раз, може стати в пригоді безпосередньо на місці. Без привʼязування до імені її називають анонімною функцією.

Анонімні функції використовують так часто, що (починаючи з R v4.1.0) існує скорочений синтаксис для їх визначення: замінімо слово function на зворотну косу риску \.

Приклад цього ми наводили в попередньому розділі, про повернені значення.

Копіювання при зміні

R дозволяє присвоювати значення окремим елементам вектора. Якщо ми передаємо вектор як аргумент функції й змінюємо його в тілі функції перед поверненням, то отримуємо змінений вектор.

Але що сталося з початковим вектором?

f <- function(vec) {
  vec[1] <- 42
  vec
}

vals <- c(1, 3, 4)

# f() returns a modified vector
f(vals)
#> [1] 42  3  4  

# the original vector is unchanged.
vals
#> [1] 1 3 4

R - мова, призначена для науки про дані. Збір таких даних може коштувати багато часу, зусиль, а часом і приголомшливих грошей: важливо не пошкодити їх!

Загальний принцип (з кількома винятками) - копіювання при зміні. Якщо обʼєкт (наприклад, вектор) змінюють так, що це могло б спричинити проблеми згодом, R повертає змінену копію, а оригінал залишає недоторканим.

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

Caution

Остерігаймося операцій, які призводять до повторного копіювання одних і тих самих даних.

Перефразовуючи (автора Tidyverse) Гедлі Вікема:

Цикли самі собою не повільні, але з ними небезпечно легко додати повільні операції всередину циклу.

Векторизація або функції вищого порядку можуть допомогти захиститися від таких убивць продуктивності.

Вказівки

Наша компанія працювала з картографічними даними, щоб створювати вебсторінки. Після деяких обговорень було вирішено почати використовувати R, щоб виконувати частину обчислень динамічно.

Будь ласка, намагаймося працювати безпосередньо з векторними точками: розпаковувати їх на окремі координати x та y не потрібно.

1. Масштабування координат

Реалізуйте функцію scale(point, s), яка приймає точку з довільною кількістю вимірів і масштабує її на наперед визначений s.

# A 2D example
s <- c(0.5, 0.8)
point <- c(10, 5)
scale(point, s)
#> [1] 5 4

2. Переміщення координат

Реалізуйте функцію translate(point, ...), яка повертає нову точку, де кожну координату зсунуто на значення, передані через ....

Ця функція повинна вміти працювати з точками будь-якої розмірності, за умови що кількість аргументів, переданих через ..., відповідає цій розмірності.

point2d <- c(2, 3)

# supply dx, dy translations
translate(point2d, 0.5, 0.6)
#> [1] 2.5 3.6

point3d <- c(2, 3, 4)

# supply dx, dy, dz translations
translate(point3d, 0.5, 0.6, 0.7)
#> [1] 2.5 3.6 4.7

3. Перетворення 2D-точки

Деякі з наших колег мають менший досвід роботи з R, тож ми вирішуємо скористатися замиканням, щоб створювати перетворення для пар координат {x, y}, які можна застосовувати повторно.

Реалізуйте функцію transform2d(dx, dy, s), яка повертає функцію, що використовує замикання для багаторазового переміщення та масштабування точки.

Аргумент масштабування s має бути необовʼязковим, з типовим значенням 1.

Повернена функція повинна приймати 2D-точку, а потім:

  1. зсунути її на наперед визначені dx та dy
  2. масштабувати її на s
# scale all coordinates by the same amount (2) in this example
# your code needs to be able to handle a vector `s`

tf2d <- transform2d(0.5, 0.6, 2)
class(tf2d)
#> [1] "function"

tf2d(c(2, 3))
#> [1] 5.0 7.2

# Accept default for `s`
tf2d <- transform2d(0.5, 0.6)

tf2d(c(2, 3))
#> 2.5 3.6

Порядок операцій має значення: перенесення, а потім масштабування дає правильний результат, а масштабування, а потім перенесення ні. Технічною мовою кажучи, ці операції не комутують.

4. Перетворення 3D-точки

Дані картографування також можуть містити висоти, які використовують, щоб додавати затінення та контури на екрані.

Реалізуйте функцію transform3d(dx, dy, dz, s), яка повертає функцію.

Аргумент масштабування s має бути необовʼязковим, з типовим значенням 1.

Повернена функція повинна приймати 3D-точку, переносити її на наперед визначені значення, а потім масштабувати її на s.

# using default `s`
tf3d <- transform3d(0.5, 0.6, 0.7)
point <- c(2, 3, 4)
tf3d(point)
#> [1] 2.5 3.6 4.7

# using vector `s`
tf3d <- transform3d(0.5, 0.6, 0.7, c(0.5, 0.6, 0.7))
tf3d(point)
#> [1] 1.25 2.16 3.29
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
R Exercism

Час розпочати Перетворення координат?

Зареєструйтеся на Exercism, щоб вивчати й опановувати R, а також 21 концепція111 вправ та справжнє наставництво від людей, і все це безкоштовно.