فهم القوائم

يُعدّ بناء القوائم بنيةً نحويةً متوفرةً في بعض لغات البرمجة لإنشاء قائمةٍ بناءً على قوائم موجودة. وهو يتبع شكل ترميز بناء المجموعات الرياضية ( بناء المجموعات )، على عكس استخدام دوال map و filter .

ملخص

ضع في اعتبارك المثال التالي في تدوين بناء المجموعات الرياضية .

S={2x|xشمال، x2>3}{\displaystyle S=\{2\cdot x\mid x\in \mathbb {N} ,\ x^{2}>3\}}

أو غالباً

S={2x:xشمال، x2>3}{\displaystyle S=\{2\cdot x:x\in \mathbb {N} ,\ x^{2}>3\}}

يمكن قراءة هذا على النحو التالي: "S{\displaystyle S}هي مجموعة جميع الأرقام "مرتين"x{\displaystyle x}"بحيثx{\displaystyle x}هو عنصر أو عضو في مجموعة الأعداد الطبيعية (شمال{\displaystyle \mathbb {N} })، وx{\displaystyle x}مربعه أكبر من3{\displaystyle 3}"

أصغر عدد طبيعي، x = 1، لا يحقق الشرط x² > 3 (الشرط > 3 غير صحيح)، لذا فإن 2 × 1 غير موجود في المجموعة S. أما العدد الطبيعي التالي، 2، فيحقق الشرط (2² > 3)، وكذلك جميع الأعداد الطبيعية الأخرى. بالتالي، تتكون المجموعة x من 2، 3، 4، 5، ... . بما أن المجموعة S تتكون من جميع الأعداد التي تساوي ضعف x، فإنها تُعطى بالصيغة S = {4، 6، 8، 10، ...}. بعبارة أخرى، S هي مجموعة جميع الأعداد الزوجية الأكبر من 2.

في هذه النسخة المشروحة من المثال:

S={2xتعبير الإخراج|xعاملشمالمجموعة المدخلات، x2>3مسند}{\displaystyle S=\{\underbrace {2\cdot x} _{\color {Violet}{\text{output expression}}}\mid \underbrace {x} _{\color {Violet}{\text{variable}}}\in \underbrace {\mathbb {N} } _{\color {Violet}{\text{input set}}},\ \underbrace {x^{2}>3} _{\color {Violet}{\text{predicate}}}\}}
  • x{\displaystyle x}المتغير الذي يمثل عناصر مجموعة الإدخال.
  • شمال{\displaystyle \mathbb {N} }يمثل مجموعة المدخلات، وهي في هذا المثال مجموعة الأعداد الطبيعية
  • x2>3{\displaystyle x^{2}>3}هو تعبير شرطي يعمل كمرشح على عناصر مجموعة الإدخال.
  • 2x{\displaystyle 2\cdot x}هو تعبير إخراج ينتج عناصر المجموعة الجديدة من عناصر مجموعة الإدخال التي تحقق تعبير الشرط.
  • {}{\displaystyle \{\}}تشير الأقواس إلى أن النتيجة عبارة عن مجموعة
  • |{\displaystyle \mid }،{\displaystyle ,}يُقرأ الخط العمودي على أنه "بحيث". ويُستخدم الخط والنقطتان الرأسيتان ":" بشكل متبادل.
  • تفصل الفواصل بين المسندات ويمكن قراءتها على أنها "و".

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

  • متغير يمثل عناصر قائمة الإدخال.
  • قائمة إدخال (أو مُكرِّر).
  • تعبير شرطي اختياري.
  • وتعبير إخراج ينتج عناصر قائمة الإخراج من عناصر الإدخال القابلة للتكرار التي تحقق الشرط.

يعتمد ترتيب إنشاء أعضاء قائمة الإخراج على ترتيب العناصر في المدخلات.

في صيغة بناء القوائم في لغة هاسكل ، يُكتب بناء المجموعة هذا بشكل مشابه، كما يلي:

s = [ 2 * x | x <- [ 0 .. ], x ^ 2 > 3 ]

[0..]هنا، تمثل القائمةشمال{\displaystyle \mathbb {N} }، x^2>3يمثل المسند، و 2*xيمثل تعبير الإخراج.

تعطي عمليات فهم القوائم نتائج بترتيب محدد (على عكس أعضاء المجموعات)؛ ويمكن لعمليات فهم القوائم أن تولد أعضاء القائمة بالترتيب، بدلاً من إنتاج القائمة بأكملها، مما يسمح، على سبيل المثال، بتعريف Haskell السابق لأعضاء القائمة اللانهائية.

تاريخ

إن وجود بنيات مشابهة يسبق استخدام مصطلح "فهم القوائم". تحتوي لغة البرمجة SETL (1969) على بنية لتكوين المجموعات تشبه فهم القوائم. على سبيل المثال، يطبع هذا الكود جميع الأعداد الأولية من 2 إلى N :

print([n in [2..N] | ∀ m in {2..n - 1} | n mod m > 0]);

يحتوي نظام الجبر الحاسوبي Axiom ( 1973) على بنية مماثلة تعالج التدفقات .

كان أول استخدام لمصطلح "الفهم" لمثل هذه البنى في وصف رود بورستال وجون دارلينجتون للغة البرمجة الوظيفية NPL الخاصة بهما من عام 1977. وفي كتابه الاستعادي "بعض تاريخ لغات البرمجة الوظيفية"، [ 1 ] يتذكر ديفيد تيرنر ما يلي:

تم تطبيق لغة NPL في POP2 بواسطة بورستال، واستُخدمت في عمل دارلينجتون على تحويل البرامج (بورستال ودارلينجتون، 1977). كانت اللغة من الدرجة الأولى، ذات كتابة قوية (ولكن ليست متعددة الأشكالوظيفية بحتة ، وتعتمد على استدعاء القيم. كما احتوت على "تعبيرات المجموعات"، على سبيل المثال،

setofeven (X) <= <:x : x in X & even(x):>}}

وفي حاشية ملحقة بمصطلح "فهم القوائم"، يشير تيرنر أيضاً إلى ما يلي:

أطلقت في البداية على هذه التعبيرات اسم ZF ، في إشارة إلى نظرية مجموعة زيرميلو-فرانكل - وكان فيل وادلر هو من صاغ المصطلح الأفضل فهم القائمة .

أثر عمل بيرستال ودارلينجتون في مجال البرمجة الوظيفية الطبيعية (NPL) على العديد من لغات البرمجة الوظيفية خلال ثمانينيات القرن العشرين، ولكن لم تتضمن جميعها خاصية بناء القوائم. وكانت لغة ميراندا ، التي طورها تيرنر عام 1985، استثناءً من ذلك. وتتضمن لغة هاسكل، وهي لغة برمجة وظيفية نقية وكسولة قياسية ، العديد من ميزات ميراندا، بما في ذلك بناء القوائم.

تم اقتراح التعبيرات المترابطة كطريقة لتدوين الاستعلامات لقواعد البيانات [ 2 ] وتم تطبيقها في لغة استعلام قواعد البيانات Kleisli . [ 3 ]

أمثلة بلغات برمجة مختلفة

بنى مماثلة

فهم الموناد

في لغة هاسكل، يعد فهم الموناد تعميمًا لفهم القائمة إلى مونادات أخرى في البرمجة الوظيفية.

فهم المجموعات

بدأت لغة بايثون في تقديم صيغة لفهم المجموعات بدءًا من الإصدار 2.7. تشبه هذه الصيغة في شكلها فهم القوائم، حيث تقوم بتكوين مجموعات بايثون بدلاً من القوائم.

s : set [ str ] = { v for v in "ABCDABCD" if v not in "CB" } print ( s ) # يطبع {'A', 'D'} print ( type ( s )) # يطبع <class 'set'>

تُنتج عبارات فهم مجموعات Racket مجموعات Racket بدلاً من القوائم.

( for/set ([ v "ABCDABCD" ] #:unless ( member v ( string->list "CB" ))) v ))

فهم القاموس

قدمت لغة بايثون صيغة جديدة لفهم القواميس في الإصدار 2.7، وهي مشابهة في الشكل لفهم القوائم ولكنها تولد قواميس بايثون بدلاً من القوائم.

s : dict [ str ] = { key : val for key , val in enumerate ( "ABCD" ) if val not in "CB" } print ( s ) # يطبع {0: 'A', 3: 'D'}

تقوم عبارات فهم جداول التجزئة في Racket بإنشاء جداول التجزئة في Racket (أحد تطبيقات نوع قاموس Racket).

( for/hash ([( val key ) ( in-indexed "ABCD" )] #:unless ( member val ( string->list "CB" ))) ( values ​​key val ))

فهم القوائم المتوازية

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

-- فهم القوائم المنتظمة a = [( x , y ) | x <- [ 1 .. 5 ], y <- [ 3 .. 5 ]] -- [(1,3),(1,4),(1,5),(2,3),(2,4) ...-- فهم القائمة المضغوطة b = [( x , y ) | ( x , y ) <- zip [ 1 .. 5 ] [ 3 .. 5 ]] -- [(1,3),(2,4),(3,5)]-- فهم القوائم المتوازية c = [( x , y ) | x <- [ 1 .. 5 ] | y <- [ 3 .. 5 ]] -- [(1,3),(2,4),(3,5)]

تحتوي مكتبة Racket القياسية للفهم على إصدارات متوازية ومتداخلة من عمليات الفهم، ويُفرّق بينها باستخدام "for" مقابل "for*" في الاسم. على سبيل المثال، تُنشئ عمليات فهم المتجهات "for/vector" و"for*/vector" متجهات من خلال التكرار المتوازي مقابل التكرار المتداخل على المتتاليات. فيما يلي كود Racket لأمثلة فهم القوائم في Haskell.

> ( for*/list ([ x ( in-range 1 6 )] [ y ( in-range 3 6 )]) ( list x y )) ' (( 1 3 ) ( 1 4 ) ( 1 5 ) ( 2 3 ) ( 2 4 ) ( 2 5 ) ( 3 3 ) ( 3 4 ) ( 3 5 ) ( 4 3 ) ( 4 4 ) ( 4 5 ) ( 5 3 ) ( 5 4 ) ( 5 5 )) > ( for/list ([ x ( in-range 1 6 )] [ y ( in-range 3 6 )]) ( list x y )) ' (( 1 3 ) ( 2 4 ) ( 3 5 ))

في لغة بايثون، يمكننا القيام بما يلي:

# بناء قائمة عادية a : list [ tuple [ int , int ]] = [( x , y ) for x in range ( 1 , 6 ) for y in range ( 3 , 6 )] print ( a ) # يطبع [(1, 3), (1, 4), (1, 5), (2, 3), (2, 4), ... # بناء قائمة متوازية/مضغوطة b : list [ tuple [ int , int ]] = [ x for x in zip ( range ( 1 , 6 ), range ( 3 , 6 ))] print ( b ) # يطبع [(1, 3), (2, 4), (3, 5)]

في لغة جوليا، يمكن تحقيق نفس النتائج عمليًا على النحو التالي:

# فهم المصفوفات العادية a :: Vector { Tuple { Int , Int }} = [( x , y ) for x in 1 : 5 for y in 3 : 5 ]# فهم المصفوفات المتوازية/المضغوطة b :: Vector { Tuple { Int , Int }} = [ x for x in zip ( 1 : 3 , 3 : 5 )]

مع الاختلاف الوحيد وهو أنه بدلاً من القوائم، لدينا في جوليا مصفوفات.

XQuery و XPath

مثل استخدام NPL الأصلي، فإن هذه لغات وصول إلى قواعد البيانات بشكل أساسي.

وهذا يجعل مفهوم الفهم أكثر أهمية، لأنه من غير الممكن حسابيًا استرداد القائمة بأكملها والعمل عليها (قد تكون "القائمة الكاملة" الأولية عبارة عن قاعدة بيانات كاملة للغة الترميز القابلة للتوسيع ( XML )).

في لغة XPath، التعبير هو:

/ مكتبة / كتاب // فقرة [ @style = 'first-in-chapter' ]

يتم تقييمها من الناحية المفاهيمية على أنها سلسلة من "الخطوات" حيث تنتج كل خطوة قائمة وتقوم الخطوة التالية بتطبيق دالة تصفية على كل عنصر في مخرجات الخطوة السابقة. [ 4 ]

في XQuery، يتوفر XPath الكامل، ولكن يتم استخدام عبارات FLWOR أيضًا، وهي بنية فهم أكثر قوة. [ 5 ]

for $ b in // book where $ b [ @pages < 400 ] order by $ b // title return <shortBook> <title> { $ b // title } </title> <firstPara> {( $ book // paragraph )[ 1 ]} </firstPara> </shortBook>

هنا يتم تقييم XPath //book لإنشاء تسلسل (يُعرف أيضًا باسم قائمة)؛ عبارة where هي "مرشح" وظيفي، و order by يقوم بفرز النتيجة، ومقتطف XML هو في الواقع دالة مجهولة تقوم بإنشاء/تحويل XML لكل عنصر في التسلسل باستخدام نهج "map" الموجود في لغات البرمجة الوظيفية الأخرى.<shortBook>...</shortBook>

لذا، في لغة برمجة وظيفية أخرى، يمكن تنفيذ عبارة FLWOR المذكورة أعلاه على النحو التالي:

map ( newXML ( shortBook , newXML ( title , $ 1.title ) , newXML ( firstPara , $ 1 ... )) filter ( lt ( $ 1.pages , 400 ), xpath (// book ) ) )

LINQ في لغة C#

يحتوي C# 3.0 على مجموعة من الميزات ذات الصلة تسمى الاستعلام المتكامل للغة (LINQ)، والتي تحدد مجموعة من عوامل تشغيل الاستعلام لمعالجة تعدادات الكائنات .

باستخدام System.Collections.Generic ؛ باستخدام System.Linq ؛IEnumerable <int> s = Enumerable.Range ( 0 , 100 ) .Where ( x = > x * x > 3 ) .Select ( x = > x * 2 ) ;

كما أنها توفر صيغة فهم بديلة، تذكرنا بلغة الاستعلامات المهيكلة ( SQL ):

باستخدام System.Collections.Generic ؛ باستخدام System.Linq ؛IEnumerable <int> s = from x in Enumerable.Range ( 0 , 100 ) where x * x > 3 select x * 2 ;

يوفر LINQ ميزةً تتجاوز تطبيقات بناء القوائم التقليدية. فعندما يُطبّق الكائن الجذري للبناء الواجهة IQueryable، بدلاً من مجرد تنفيذ الأساليب المتسلسلة للبناء، يتم تحويل سلسلة الأوامر بأكملها إلى كائن شجرة بناء جملة مجردة (AST)، والذي يتم تمريره إلى كائن IQueryable لتفسيره وتنفيذه.

وهذا يتيح القيام بالعديد من الأمور، بما في ذلك تمكين IQueryable من:

  • أعد كتابة فهم غير متوافق أو غير فعال
  • قم بترجمة شجرة بناء الجملة المجردة (AST) إلى لغة استعلام أخرى (مثل SQL) لتنفيذها

لغة سي++

لا تحتوي لغة C++ على ميزات لغوية تدعم بشكل مباشر إنشاء قوائم الفهم، ولكن تم استخدام تحميل المعاملات الزائد (مثل تحميل المعاملات الزائدة |) >>لتوفير بنية معبرة للغات الاستعلام الخاصة بالمجال "المضمنة" . بدلاً من ذلك، يمكن إنشاء قوائم الفهم باستخدام أسلوب الحذف والإزالة لاختيار العناصر في حاوية، وخوارزمية STL لتحويلها.>>=for_each

تاريخيًا، <algorithm>كان ملف الرأس يحتوي فقط على خوارزميات تعتمد على المُكرِّرات على نطاق من العناصر. [ 6 ] تم توسيع هذا لاحقًا في C++20 بإضافة خوارزميات مقيدة (في مساحة الاسم std::ranges) إلى ملف الرأس هذا، والتي تعمل على نطاق بدلاً من المُكرِّرات. [ 7 ]

استيراد std ؛باستخدام std :: vector ;template < typename Collection , typename Pred , typename Trans > Collection comprehend ( Collection && source , const Pred & predicate , const Trans & transformation ) { // تهيئة الوجهة Collection d = std :: forward < Collection > ( source );// تصفية العناصر d.erase ( std :: ranges :: remove_if ( d , predicate ), d.end ( ) ) ;// تطبيق التحويل std :: ranges :: for_each ( d , transformation );أعد د ؛ }int main ( int argc , char * argv []) { vector < int > range ( 10 ); // range عبارة عن قائمة من 10 عناصر، جميعها أصفار std :: ranges :: iota ( range , 1 ); // range الآن تحتوي على 1، 2، ...، 10vector <int> result = comprehend ( range , []( int x ) -> bool { return x * x <= 3 ; }, []( int & x ) - > void { x *= 2 ; } ); // result now contains 4, 6, ..., 20 }

<ranges>في C++20، أُضيفت ملفات رأسية إضافية، مثل `..`، والتي تتميز بخوارزميات نطاق قابلة للتركيب وعرض مُقَيَّم بشكل كسول على أي نطاق. باستخدام std::ranges::viewsالمكتبة `..` (المختصرة أيضًا بـ std::views`..`) [ 8 ] ، يمكن كتابة هذا على النحو التالي:

باستخدام std :: vector ؛ باستخدام std :: ranges :: to ؛ باستخدام std :: views :: filter ؛ باستخدام std :: views :: transform ؛vector <int> range ( 10 ); // range عبارة عن قائمة من 10 عناصر، جميعها أصفار std :: ranges :: iota ( range , 1 ); // range الآن تحتوي على 1، 2، ...، 10vector <int> result = range | filter ([]( int x ) - > bool { return x * x > 3 ; } ) | transform ([]( int x ) - > int { return x * 2 ; }) | to <vector> ( );

جافا

قدمت واجهة برمجة تطبيقات Streams في Javajava.util.stream.Stream 8 كائنًا شبيهًا بالنطاق يتم تقييمه عند الحاجة، يُسمى stream. يمكن استخدام أي نوع يُنفذ هذه الواجهة مع streams. [ 9 ] يمكن تجميع هذه الكائنات بواسطة toArray()`or` toList()، حيث تُجمع العناصر في ` Object[]or` List<T>.

استيراد java.util.List ;List <Integer> numbers = List.of ( 1 , 2 , 3 , 4 , 5 ) ; List <Integer> doubledEven = numbers.stream ( ) . filter ( x - > x % 2 == 0 ) .map ( x - > x * 2 ) .toList ( ) ;System.out.println ( doubledEven ) ; // [ 4 , 8 ]

الصدأ

تحتوي لغة Ruststd::iter::Iterator على مُكرِّرات، يتم تقييمها بشكل كسول. يمكن استخدام أي نوع يُطبِّق هذه السمة مع المُكرِّرات. [ 10 ] باستخدام هذه الطرق، يتم إنشاء سلسلة من مُهايئات المُكرِّرات، والتي يتم جمعها في النهاية collect()في مجموعة فعلية (عادةً Vec<T>).

let numbers = vec! [ 1 , 2 , 3 , 4 , 5 ]; let doubled_even : Vec < i32 > = numbers . iter () . filter ( |& x | x % 2 == 0 ) . map ( | x | x * 2 ) . collect ();println! ( "{:?} " , doubled_even ); // [4, 8]

انظر أيضاً

ملاحظات ومراجع

  1. تيرنر، ديفيد (2012). "بعض تاريخ لغات البرمجة الوظيفية" (ملف PDF) . الندوة الدولية حول اتجاهات البرمجة الوظيفية . برلين، هايدلبرغ: سبرينغر . الصفحات 1-20 . 
  2. التعبيرات المترابطة، وهي صيغة استعلام لـ DBPLs
  3. المكونات الوظيفية لنظام استعلام Kleisli
  4. "2.1 خطوات تحديد الموقع" . لغة مسار XML (XPath) . اتحاد شبكة الويب العالمية ( W3C ) . 16 نوفمبر 1999. مؤرشف من الأصل في 9 ديسمبر 2012. تم الاطلاع عليه في 24 ديسمبر 2008 .
  5. "تعبيرات XQuery FLWOR" . W3Schools . مؤرشف من الأصل بتاريخ 2011-10-08.
  6. cppreference.com. "رأس المكتبة القياسية <algorithm>" . cppreference.com . cppreference.com . تم الاطلاع عليه بتاريخ 9 مايو 2026 .
  7. cppreference.com. "الخوارزميات المقيدة" . cppreference.com . cppreference.com . تم الاطلاع عليه بتاريخ 9 مايو 2026 .
  8. cppreference.com. "مكتبة النطاقات (منذ C++20)" . cppreference.com . تم الاطلاع عليه بتاريخ 9 مايو 2026 .
  9. شركة أوراكل. "تدفق الواجهة<T>" . docs.oracle.com . شركة أوراكل.
  10. فريق Rust (14 أبريل 2026). "مكرر السمات" . فريق Rust.
  • فهم القوائم في قاموس الحوسبة المجاني على الإنترنت، المحرر دينيس هاو.
  • وادلر، فيليب (1990). "فهم المونادات" . وقائع مؤتمر ACM لعام 1990 حول لغة LISP والبرمجة الوظيفية . نايس.

بديهية

  • أمثلة على تدفق البديهيات

كلوجر

لغة الشفرة الشائعة

هاسكل

  • تقرير هاسكل 98، الفصل 3.11: فهم القوائم .
  • دليل مستخدم نظام تجميع Glasgow Haskell الرائع، الفصل 7.3.4: فهم القوائم المتوازية .
  • دليل مستخدم Hugs 98، الفصل 5.1.2 فهم القوائم المتوازية (المعروفة أيضًا باسم فهم zip) .

أوكاميل

بايثون