المسارات
/
R
R
/
التمارين
/
تحويل الإحداثيات
تحويل الإحداثيات

تحويل الإحداثيات

تمرين تعلّمي

مقدمة

قُدّمت الدوال سابقًا في مفهوم الأساسيات، بأمثلة مثل هذا:

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

squareit(3)
#> [1] 9

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

إذا نظرنا عن قرب إلى تعريف squareit، يمكننا تمييز عدة أجزاء:

  • تأخذ الدالة وسيطًا رسميًا x.
  • هناك جسم للدالة، يكون عادةً بين الأقواس المعقوفة { }.
  • بمجرد إنشاء كائن الدالة، يُسند إلى متغير squareit.

الدالة في R كائن من الدرجة الأولى، تمامًا مثل الأعداد والسلاسل النصية. وبالتالي، فإن 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) Hadley Wickham:

الحلقات ليست بطيئة بطبيعتها، لكنها تجعل من السهل بشكل خطير تضمين عمليات بطيئة داخل الحلقة.

يمكن أن يساعدك التنفيذ المتجهي أو الدوال ذات الرتبة الأعلى على الوقاية من هذا النوع من قاتلات الأداء.

التعليمات

تعمل شركتك على بيانات الخرائط لبناء صفحات الويب. وبعد بعض النقاش، تقرَّر البدء في استخدام 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. تحويل نقطة ثنائية الأبعاد

بعض زملائك أقل خبرة بـ R، لذلك تقرر استخدام إغلاق دالة لإنشاء تحويلات قابلة لإعادة الاستخدام لأزواج الإحداثيات {x, y}.

نفّذ الدالة transform2d(dx, dy, s) التي تُرجع دالة تستفيد من إغلاق لتنفيذ نقل وتحجيم قابلين للتكرار لنقطة ثنائية الأبعاد.

يجب أن يكون معامل التحجيم s اختياريًا، بقيمة افتراضية تساوي 1.

ينبغي أن تأخذ الدالة المُرجعة نقطة ثنائية الأبعاد، ثم:

  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. تحويل نقطة ثلاثية الأبعاد

يمكن أن تحتوي بيانات الخرائط أيضًا على ارتفاعات، تُستخدم لإضافة التظليل والخطوط الكنتورية على الشاشة.

نفّذ الدالة transform3d(dx, dy, dz, s) التي تُرجع دالة.

يجب أن يكون معامل التحجيم s اختياريًا، بقيمة افتراضية تساوي 1.

ينبغي أن تأخذ الدالة المُرجعة نقطة ثلاثية الأبعاد، وتنقلها بالقيم المعرّفة مسبقًا، ثم تحجّمها باستخدام 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 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.