مفاهيم البرمجة الوظيفية

مقدمة حول البرمجة الوظيفية

تستخدم Earth Engine نظام معالجة متوازية لتنفيذ العمليات الحسابية على عدد كبير من الأجهزة. ولإتاحة هذه المعالجة، تستفيد Earth Engine من التقنيات العادية التي تستخدمها عادةً اللغات الوظيفية، مثل الشفافية المرجعية والتقييم الكسول، وذلك لتحقيق تحسينات كبيرة وزيادة الكفاءة.

المفهوم الرئيسي الذي يميّز البرمجة الوظيفية عن البرمجة الإجرائية هو عدم وجود آثار جانبية. ويعني ذلك أنّ الدالات التي تكتبها لا تعتمد على البيانات خارج الدالة أو تعدّلها. كما سترى في الأمثلة أدناه، من الممكن إعادة هيكلة مشكلتك بحيث يمكن حلّها باستخدام دوال بدون آثار جانبية، وهي دوال أنسب بكثير للتنفيذ بالتوازي.

حلقات التكرار For

يُنصح بتجنُّب استخدام حلقات for في Earth Engine. يمكن الحصول على النتائج نفسها باستخدام عملية map() حيث تحدّد دالة يمكن تطبيقها بشكل مستقل على كل عنصر. يتيح ذلك للنظام توزيع عملية المعالجة على أجهزة مختلفة.

يوضّح المثال أدناه كيف يمكنك أخذ قائمة أرقام وإنشاء قائمة أخرى تتضمّن مربّعات كل رقم باستخدام map():

محرّر الرموز البرمجية (JavaScript)

// This generates a list of numbers from 1 to 10.
var myList = ee.List.sequence(1, 10);

// The map() operation takes a function that works on each element independently
// and returns a value. You define a function that can be applied to the input.
var computeSquares = function(number) {
  // We define the operation using the EE API.
  return ee.Number(number).pow(2);
};

// Apply your function to each item in the list by using the map() function.
var squares = myList.map(computeSquares);
print(squares);  // [1, 4, 9, 16, 25, 36, 49, 64, 81]

شروط If/Else

من المشاكل الشائعة الأخرى التي يواجهها المستخدمون الجدد الذين اعتادوا على نموذج البرمجة الإجرائية، الاستخدام السليم لعوامل التشغيل الشرطية if/else في Earth Engine. على الرغم من أنّ واجهة برمجة التطبيقات توفّر خوارزمية ee.Algorithms.If()، فإنّنا ننصح بشدة بعدم استخدامها واستخدام map() والفلاتر بدلاً منها. تستخدم Earth Engine التنفيذ المؤجّل، ما يعني أنّه يتم تأخير تقييم التعبير إلى أن تصبح القيمة المحقّقة مطلوبة فعلاً. في بعض الحالات، سيقيّم هذا النوع من نماذج التنفيذ كلاً من البديلين الصحيح والخاطئ لعبارة ee.Algorithms.If(). ويمكن أن يؤدي ذلك إلى زيادة في عمليات الحساب واستخدام الذاكرة، وذلك حسب التعبيرات والموارد المطلوبة لتنفيذها.

لنفترض أنّك تريد حلّ صيغة مختلفة من المثال أعلاه، حيث تكون المهمة هي حساب مربّعات الأعداد الفردية فقط. في ما يلي مثال على طريقة وظيفية لحلّ هذه المشكلة بدون استخدام شرطَي if/else:

محرّر الرموز البرمجية (JavaScript)

// The following function determines if a number is even or odd.  The mod(2)
// function returns 0 if the number is even and 1 if it is odd (the remainder
// after dividing by 2).  The input is multiplied by this remainder so even
// numbers get set to 0 and odd numbers are left unchanged.
var getOddNumbers = function(number) {
  number = ee.Number(number);   // Cast the input to a Number so we can use mod.
  var remainder = number.mod(2);
  return number.multiply(remainder);
};

var newList = myList.map(getOddNumbers);

// Remove the 0 values.
var oddNumbers = newList.removeAll([0]);

var squares = oddNumbers.map(computeSquares);
print(squares);  // [1, 9, 25, 49, 81]

وينطبق هذا النموذج بشكل خاص عند العمل مع المجموعات. إذا أردت تطبيق خوارزمية مختلفة على المجموعة استنادًا إلى بعض الشروط، فإنّ الطريقة المفضّلة هي فلترة المجموعة أولاً استنادًا إلى الشرط، ثم تطبيق دالة مختلفة على كل مجموعة فرعية.map() يتيح ذلك للنظام تنفيذ العملية بالتوازي. على سبيل المثال:

محرّر الرموز البرمجية (JavaScript)

// Import Landsat 8 TOA collection and filter to 2018 images.
var collection = ee.ImageCollection('LANDSAT/LC08/C02/T1_TOA')
  .filterDate('2018-01-01', '2019-01-01');

// Divide the collection into 2 subsets and apply a different algorithm on them.
var subset1 = collection.filter(ee.Filter.lt('SUN_ELEVATION', 40));
var subset2 = collection.filter(ee.Filter.gte('SUN_ELEVATION', 40));

// Multiply all images in subset1 collection by 2;
// do nothing to subset2 collection.
var processed1 = subset1.map(function(image) {
  return image.multiply(2);
});
var processed2 = subset2;

// Merge the collections to get a single collection.
var final = processed1.merge(processed2);
print('Original collection size', collection.size());
print('Processed collection size', final.size());

التكرار التراكمي

قد تحتاج إلى إجراء عملية تسلسلية، حيث يتم استخدام نتيجة كل تكرار في التكرار اللاحق. توفّر Earth Engine طريقة iterate() لإجراء هذه المهام. تذكَّر أنّ iterate() يتم تنفيذه بشكل تسلسلي، وبالتالي سيكون بطيئًا بالنسبة إلى العمليات الكبيرة. استخدِمها فقط عندما يتعذّر عليك استخدام map() والفلاتر للحصول على النتيجة المطلوبة.

من الأمثلة الجيدة على استخدام iterate() إنشاء تسلسل أرقام فيبوناتشي. في هذا المثال، كل رقم في السلسلة هو مجموع الرقمين السابقين. تأخذ الدالة iterate() وسيطتَين، وهما دالة (خوارزمية) وقيمة بدء. يتم تمرير قيمتَين إلى الدالة نفسها، وهما القيمة الحالية في التكرار ونتيجة التكرار السابق. يوضّح المثال التالي كيفية تنفيذ تسلسل فيبوناتشي في Earth Engine.

محرّر الرموز البرمجية (JavaScript)

var algorithm = function(current, previous) {
  previous = ee.List(previous);
  var n1 = ee.Number(previous.get(-1));
  var n2 = ee.Number(previous.get(-2));
  return previous.add(n1.add(n2));
};

// Compute 10 iterations.
var numIteration = ee.List.repeat(1, 10);
var start = [0, 1];
var sequence = numIteration.iterate(algorithm, start);
print(sequence);  // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

بعد أن أصبحت لديك معرفة جيدة بمفاهيم JavaScript، يمكنك الاطّلاع على البرنامج التعليمي الخاص بواجهة برمجة التطبيقات للحصول على مقدّمة حول وظائف المعلومات الجغرافية المكانية في Earth Engine API.