اذهب إلى المحتوى

كل الأنشطة

تحدث تلقائيًا

  1. الساعة الماضية
  2. اليوم
  3. صحيح، من أشهر الخوارزميات المستخدمة في التجميع Clustering نظرًا لبساطتها وسرعتها في التنفيذ، ولا يعني هذا بالضرورة أنها الأفضل في جميع الحالات، فهناك العديد من الخوارزميات الأخرى التي تكون أكثر فعالية حسب طبيعة البيانات والمشكلة التي تحاول حلها. مثلاً DBSCAN (Density-Based Spatial Clustering of Applications with Noise) تعتمد الخوارزمية على الكثافة ولا تتطلب تحديد عدد المجموعات مسبقًا، كما أنها قادرة على التعامل مع الضوضاء والعثور على المجموعات ذات الأشكال غير المنتظمة. ولديك أيضًا Hierarchical Clustering التجميع الهرمي، مفيد في الحالات التي ترغب فيها ببناء شجرة تراتبية من المجموعات، وتوجد أشكال مختلفة من التجميع الهرمي مثل التجميع المتراكم Agglomerative والتجميع التقسيمي Divisive. بجانب Gaussian Mixture Models (GMM) وتلك الخوارزمية تعتمد على النماذج الإحصائية وتستخدم لتقدير التوزيعات المختلفة في البيانات، وهي أكثر مرونة من K-Means في التعامل مع مجموعات البيانات التي تمتلك أشكالًا وتوزيعات مختلفة. بالإضافة إلى Spectral Clustering التي تستخدم لتحليل القيم الذاتية لمصفوفة التشابه لإنشاء مجموعات، وفعالة في اكتشاف المجموعات المعقدة في البيانات. أخيرًا خوارزمية Mean Shift التي تعتمد على تقدير الكثافة لتحديد مراكز المجموعات، ولا تتطلب تحديد عدد المجموعات مقدما.
  4. السلام عليكم هو خورزميات الKMeans هي اشهر حاجه في ال Clustering بس هل معني كده هي احسن حاجه والا ممكن يكون في حاجه افضل ؟
  5. من المرجح أنك تعرف كيف تبحث عن النصوص بالضغط على Ctrl+f وإدخال الكلمات التي تريد البحث عنها، في حين أن التعابير النمطية Regular expressions تنقل الأمور إلى مرحلة أعلى: فهي تسمح لك بتحديد «نمط» النص الذي تبحث عنه، فلو لم تكن تعرف رقم هاتف الشركة في الورقة أمامك، لكنك تعيش في الولايات المتحدة أو كندا، فستعلم أن أرقام الهاتف تتألف من 3 أرقام ثم شرطة -، ثم 4 أرقام أخرى (وقد يبدأ برمز المنطقة وهو 3 أرقام في البداية). أي أنك ستعرف أن ما يلي هو رقم هاتف 415‎-555-1234 لكن 4,155,551,234 ليس رقمًا هاتفيًا. يمكننا التعرف على مختلف أنماط النصوص بسهولة: فعناوين البريد الإلكتروني تحتوي الرمز @ في منتصفها، وعناوين URL للمواقع تحتوي على نقط وخطوط مائلة، والعناوين الأخبارية تستعمل نسق العنوان Title case، والتاغات في وسائل التواصل الاجتماعي تبدأ برمز # ولا تحتوي على فراغات …إلخ. التعابير النمطية مفيدة جدًا، لكن قلّة من غير المبرمجين يعرفونها على الرغم من أن أغلبية المحررات النصية الحديثة ومعالجات النصوص مثل مايكرروسوفت وورد وليبرأوفيس رايتر يمتلكون خاصيات بحث واستبدال تستعمل التعابير النمطية. والتعابير النمطية تسرع معالجة النصوص لدرجة كبيرة، فلا يستفيد منها مستخدمو برمجيات التحرير فقط، بل المبرمجون أيضًا، ويقول الكاتب الشهير كوري دكتورو أن علينا تدريس التعابير النمطية قبل تدريس البرمجة: سنبدأ هذا المقال بكتابة برنامج لمطابقة نمط من النصوص دون استخدام التعابير النمطية، ثم سنرى كيف يمكننا كتابة شيفرة بسيطة باستخدام التعابير النمطية؛ وسنبدأ بالتعابير البسيطة ثم ننتقل إلى الميزات المتقدمة أكثر، مثل استبدال النصوص وفئات الحروف، ثم سنكتب في نهاية المقال مشروعًا بسيطًا يستخرج أرقام الهواتف وعناوين البريد الإلكتروني من مستند نصي. مطابقة الأنماط دون استخدام التعابير النمطية لنقل أننا نريد البحث عن رقم هاتف يتبع نظام الكتابة الأمريكي في سلسلة نصية، أي يحتوي 3 أرقام ثم شرطة ثم 3 أرقام ثم شرطة ثم 4 أرقام، مثل ‎415-555-4242. لنكتب دالةً باسم isPhoneNumber()‎ للتحقق إن كانت تمثل السلسلة النصية رقم هاتف، وتعيد القيمة True أو False. احفظ الشيفرة الآتية في ملف باسم isPhoneNumber.py: def isPhoneNumber(text): ➊ if len(text) != 12: return False for i in range(0, 3): ➋ if not text[i].isdecimal(): return False ➌ if text[3] != '-': return False for i in range(4, 7): ➍ if not text[i].isdecimal(): return False ➎ if text[7] != '-': return False for i in range(8, 12): ➏ if not text[i].isdecimal(): return False ➐ return True print('Is 415-555-4242 a phone number?') print(isPhoneNumber('415-555-4242')) print('Is ABC a phone number?') print(isPhoneNumber(ABC)) حين تجربة المثال السابق فسينتج ما يلي: Is 415-555-4242 a phone number? True Is ABC a phone number? False تحتوي الدالة isPhoneNumber()‎ على شيفرة تجري عدة عمليات تحقق للتأكد أن السلسلة النصية في المعامل text هي رقم هاتف صالح، وإذا فشلت أي تحقق من هذه التحققات فستعيد الدالة القيمة False. تبدأ الشيفرة بالتحقق أن السلسلة النصية بطول 12 محرفًا تمامًا ➊، ثم تتحقق أن رقم المنطقة (أي أول 3 محارف في text) تحتوي على محارف رقمية فقط ➋، بقية الدالة تتحقق أن السلسلة النصية تتبع نمط أرقام الهواتف: يجب أن تكون أول شرطة في الرقم بعد رمز المنطقة ➌، ثم 3 أرقام ➍، ثم شرطة ➎، ثم 4 أرقام ➏، وإن أنهينا كل عمليات التحقق بنجاح فستعيد الدالة True ➐. استدعاء الدالة isPhoneNumber()‎ مع الوسيط '‎415‎-555-4242' سيعيد True، بينما استدعاؤها مع 'ABC' سيعيد False، إذ ستفشل أول عملية تحقق لأن السلسلة النصية 'ABC' ليست بطول 12 محرفًا. إذا أردت العثور على رقم هاتف في سلسلة نصية طويل، فستحتاج إلى كتابة المزيد من الشيفرات لمطابقة نمط رقم الهاتف. بدِّل استدعاء الدالة print()‎ في المثال السابق إلى ما يلي: message = 'Call me at 415-555-1011 tomorrow. 415-555-9999 is my office.' for i in range(len(message)): ➊ chunk = message[i:i+12] ➋ if isPhoneNumber(chunk): print('Phone number found: ' + chunk) print('Done') سينتج البرنامج الناتج الآتي: Phone number found: 415-555-1011 Phone number found: 415-555-9999 Done في كل دورة لحلقة for سنأخذ 12 محرفًا من المتغير message ونسندها إلى المتغير chunk ➊، فمثلًا في أول دورة لحلقة التكرار تكون قيمة i هي 0، وستسند القيمة message[0:12]‎ إلى المتغير chunk (أي السلسلة النصية 'Call me at 4')، وفي الدورة التالية تكون قيمة i تساوي 1، وستسند القيمة message[1:13]‎ إلى chunk (أي السلسلة النصية 'all me at 41')، بعبارة أخرى: في كل دورة من حلقة for ستتغير قيمة chunk بزيادة مكان بدء عملية الاقتطاع بمقدار واحد، ويكون طولها 12 محرفًا: 'Call me at 4' 'all me at 41' 'll me at 415' 'l me at 415-‎' وهلم جرًا… سنمرر بعدئذٍ المتغير chunk إلى الدالة isPhoneNumber()‎ للتحقق إن كان يطابق نمط أرقام الهواتف ➋، وإذا طابقها فسنطبع القيمة المطابقة. سنستمر في تنفيذ حلقة التكرار التي ستمر على السلسلة النصية كلها، وستختبر كل 12 محرفًا فيها على حدة، وتطبع قيمة chunk إن أعادت الدالة isPhoneNumber()‎ القيمة True، وبعد المرور على جميع محارف السلسلة النصية message فسنطبع الكلمة Done. على الرغم من أن السلسلة النصية في هذا المثال message قصيرة، لكنها قد تكون طويلة جدًا وفيها ملايين المحارف، وسيعالجها البرنامج في أقل من ثانية؛ لكن إن كتبنا برنامجًا يطابق أرقام الهواتف باستخدام التعابير النمطية فسيعمل بسرعة مشابهة تقريبًا، لكن برمجته أسهل بكثير. العثور على تعبير نصي باستخدام التعابير النمطية يعمل البرنامج الذي كتبناه في القسم السابق عملًا صحيحًا، لكننا احتجنا إلى كتابة شيفرة كثيرة للقيام بأمر بسيط؛ فطول الدالة isPhoneNumber()‎ هو 17 سطرًا لكنها تستطيع مطابقة نمط واحد من أرقام الهواتف. فماذا عن الأرقام المكتوبة بالشكل 415.555.4242 أو ‎(415) 555-4242؟ ماذا لو كان هنالك رمز تحويل بعد رقم الهاتف مثل ‎415-555-4242 x99؟ ستفشل الدالة isPhoneNumber()‎ بمطابقتها. صحيحٌ أنك تستطيع كتابة شيفرات إضافية للتحقق من هذه الحالات، لكن هنالك طريقة أسهل بكثير. التعابير النمطية Regular Expression أو اختصارًا Regex تصف نمط النصوص (ومن هنا أتى اسمها). فمثلًا ‎\d في صياغة التعابير النمطية تعني محرف رقمي، أي رقم مفرد من 0 حتى 9. ويستعمل التعبير النمطي ‎\d\d\d-\d\d\d-\d\d\d\d في بايثون لمطابقة النص الذي تطابقه الدالة isPhoneNumber()‎ السابقة: سلسلة من 3 أرقام، ثم شرطة، ثم 3 أرقام، ثم شرطة، ثم 4 أرقام. ولن تطابق أي سلسلة نصية لها نمط آخر التعبير النمطي ‎\d\d\d-\d\d\d-\d\d\d\d. لكن يمكن أن تكون التعابير النمطية أعقد من ذلك بكثير، فمثلًا إضافة الرقم 3 بين قوسين مجعدين {3} يعني «طابق هذا النمط 3 مرات»، وبالتالي يمكننا كتابة التعبير النمطي السابق بشكل مختصر ‎\d{3}-\d{3}-\d{4}‎ وستكون النتيجة نفسها. إنشاء كائنات Regex جميع دوال التعابير النمطية موجودة في الوحدة re، وعلينا استيرادها أولًا: >>> import re ملاحظة: أغلبية الأمثلة في هذا المقال تستعمل الوحدة re، لذا من المهم أن تتذكر استيرادها في بداية كل سكربت أو حينما تعيد تشغيل محرر Mu، وإلا فستحصل على رسالة الخطأ NameError: name 're' is not defined. تمرير سلسلة نصية تمثل التعبير النمطي إلى re.compile()‎ سيعيد كائن Regex. لإنشاء كائن Regex يطابق نمط أرقام الهواتف السابق، فأدخل ما يلي إلى الطرفية التفاعلية. تذكر أن ‎\d تعني «محرف رقمي»، و ‎\d\d\d-\d\d\d-\d\d\d\d هو التعبير النمطي الذي سيطابق رقم الهاتف. >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') يحتوي المتغير phoneNumRegex الآن على كائن Regex. مطابقة كائنات Regex يمتلك الكائن Regex التابع search()‎ الذي يبحث في السلسلة النصية التي مررت إليها لأي مطابقة للتعبير النمطية. سيعيد التابع search()‎ القيمة None إذا لم يُطابَق التعبير النمطي في السلسلة النصية، وإذا عُثر على التعبير النمطي فسيعيد التابع search()‎ كائن Match، الذي فيه التابع group()‎ الذي يعيد النص الذي جرت مطابقته مع التعبير النمطي (سنشرح المجموعات لاحقًا فلا تقلق): >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') >>> mo = phoneNumRegex.search('My number is 415-555-4242.') >>> print('Phone number found: ' + mo.group()) Phone number found: 415-555-4242 اسم المتغير mo هو اسم عام لكائنات Match، قد يبدو المثال السابق معقدًا لكنه أقصر بكثير من برنامج isPhoneNumber.py. بدايةً مررنا التعبير النمطي الذي نريده إلى re.compile()‎ وحفظنا كائن Regex الناتج في المتغير phoneNumRegex، ثم استدعينا التابع search()‎ على phoneNumRegex ومررنا السلسلة النصية التي نريد البحث فيها عن النمط إلى التابع search()‎. ستخزن نتيجة البحث في المتغير mo، ونحن نعرف في هذا المثال أن التابع سيعيد الكائن Match، ولمعرفتنا أن المتغير mo سيحتوي على كائن Match وليس قيمة فارغة None، فاستدعينا التابع group()‎ على mo لإعادة الناتج، ولأننا كتبنا mo.group()‎ داخل الدالة print()‎ فسنعرض الناتج الذي جربت مطابقته 415‎-555-4242. مراجعة لعملية لمطابقة التعابير النمطية هنالك عدة خطوات لاستعمال التعابير النمطية في بايثون، وكل واحدة منها بسيطة وسهلة: استيراد الوحدة Regex بكتابة import re. إنشاء كائن Regex مع الدالة re.compile()‎، وتذكر أن تستعمل سلسلة نصية خام raw بكتابة r قبلها. تمرير السلسلة النصية التي تريد البحث فيها إلى التابع search()‎ الذي سيعيد كائن من النوع Match. استدعاء الدالة group()‎ للكائن Match التي تعيد السلسلة النصية المطابقة. ملاحظة: صحيحٌ أنني أنصحك أن تجرب الشيفرات في الطرفية التفاعلية لكنك تستطيع استخدام تطبيقات ويب لاختبار التعابير النمطية التي تظهر لك بوضوح كيف يطابق التعبير النمطي النص الذي أدخلته. أنصحك بتجربة موقع pythex. ميزات إضافية لمطابقة النصوص عبر التعابير النمطية لقد تعلمنا الخطوات الأساسية لإنشاء والبحث عبر التعابير النمطية في بايثون، وأصبحنا جاهزين لتجربة ميزات أقوى لها. التجميع مع الأقواس لنقل أنك تريد أن تفصل رقم المنطقة من بقية رقم الهاتف. إذا أضفنا أقواسًا في التعابير النمطية فستنشِئ مجموعات groups كما في (‎\d\d\d)-(\d\d\d-\d\d\d\d)، ثم يمكننا استخدام التابع group()‎ للكائن Match للحصول على النص المطابق من مجموعة معينة. ستخزن القيمة المطابقة من أول مجموعة في المجموعة 1، والمجموعة الثانية في 2… وإذا مررنا القيمة 1 أو 2 إلى التابع group()‎ فسنحصل على أجزاء مختلفة من النص الذي جرت مطابقته. أما تمرير القيمة 0 أو عدم تمرير أي قيمة إلى التابع group()‎ فسيعيد كامل النص الذي جرت مطابقته من التعبير النمطي: >>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d-\d\d\d\d)') >>> mo = phoneNumRegex.search('My number is 415-555-4242.') >>> mo.group(1) '415' >>> mo.group(2) '555-4242' >>> mo.group(0) '415-555-4242' >>> mo.group() '415-555-4242' إذا أردت الحصول على جميع المجموعات في آن واحد، فاستعمل التابع groups()‎ (لاحظ أن اسم التابع بالجمع وليس المفرد): >>> mo.groups() ('415', '555-4242') >>> areaCode, mainNumber = mo.groups() >>> print(areaCode) 415 >>> print(mainNumber) 555-4242 يعيد التابع mo.groups()‎ صفًا tuple فيه أكثر من قيمة، ويمكنك استعمال الإسناد المتعدد لإسناد كل قيمة إلى متغير كما في السطر الآتي: areaCode, mainNumber = mo.groups()‎ للأقواس معنى خاص في التعابير النمطية، لكن ماذا نفعل لو أردنا مطابقة الأقواس في النص؟ لنقل مثلًا أن رمز المنطقة في أرقام الهواتف التي تريد مطابقتها موجودة بين قوسين، وفي هذه الحالة سنحتاج إلى تهريب المحرفين ( و ) عبر شرطة مائلة خلفية: >>> phoneNumRegex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)') >>> mo = phoneNumRegex.search('My phone number is (415) 555-4242.') >>> mo.group(1) '(415)' >>> mo.group(2) '555-4242' سيطابق المحرفان المهربان ‎`)‎و ‎`(‎ في السلسلة النصية الخام الممررة إلى الدالة re.compile()‎ الأقواس في السلسلة النصية التي سنبحث فيها. هنالك معانٍ خاصة للمحارف الآتية في التعابير النمطية: . ^ $ * + ? { } [ ] \ | ( ) في حال أردت مطابقة أحد تلك المحارف في النص الذي تريد البحث فيه، فعليك تهريبها بوضع خط مائل خلفي قبلها: \. \^ \$ \* \+ \? \{ \} \[ \] \\ \| \( \) تأكد أنك لم تخلط بين الأقواس المهربة وبين أقواس المجموعات في التعابير النمطية، إذا ظهرت لك رسالة خطأ حول «قوس ناقص» أو «أقواس غير متوازنة» فاعلم أنك نسيت إغلاق قوس مجموعة غير مهرب كما في المثال الآتي: >>> re.compile(r'(\(Parentheses\)') Traceback (most recent call last): --snip-- re.error: missing ), unterminated subpattern at position 0 تقول لك رسالة الخطأ أن هنالك قوس مفتوح في الفهرس 0 من السلسلة النصية r'((Parentheses)'‎ الذي لا يوجد له قوس إغلاق. مطابقة أكثر من مجموعة باستخدام الخط العمودي محرف الخط العمودي | الذي يسمى أيضًا بالأنبوب Pipe يستعمل في أي مكان تريد فيه مطابقة أحد التعابير الموفرة، أي لنقل أن لدينا التعبير النمطي r'Batman|Yasmin'‎ الذي سيطابق 'Batman' أو 'Yasmin'. فلو كانت الكلمتان Batman و Yasmin موجودةً في السلسلة النصية التي سنبحث فيها، فستعاد أول قيمة تطابق إلى الكائن Match: >>> heroRegex = re.compile (r'Batman|Yasmin') >>> mo1 = heroRegex.search('Batman and Yasmin') >>> mo1.group() 'Batman' >>> mo2 = heroRegex.search('Yasmin and Batman') >>> mo2.group() 'Yasmin' ملاحظة: يمكنك العثور على جميع حالات المطابقة باستخدام التابع findall()‎ المشروحة في هذا المقال. يمكنك أيضًا استخدام الخط العمودي لمطابقة تعابير فرعية مختلفة، فمثلًا لو قلنا أننا نريد مطابقة أي سلسلة نصية من 'Batman' و 'Batmobile' و 'Batcopter' و 'Batbat'، ولأن كل هذه السلاسل النصية تبدأ بالكلمة Bat فمن المنطقي أن نكتب هذه السابقة مرة واحدة، ويمكننا فعل ذلك عبر الأقواس كما يلي: >>> batRegex = re.compile(r'Bat(man|mobile|copter|bat)') >>> mo = batRegex.search('Batmobile lost a wheel') >>> mo.group() 'Batmobile' >>> mo.group(1) 'Mobile' استدعاء التابع mo.group()‎ يعيد النص 'Batmobile' بينما mo.group(1)‎ يعيد النص المطابق داخل مجموعة الأقواس الأولى، أي 'mobile'. استخدام الخط العمودي | يتيح لنا مطابقة أحد التعابير النمطية الموفرة، وإذا أردنا أن نطابق الخط العمودي نفسه في السلسلة النصية المبحوث فيها فلا ننسى تهريبه ‎|‎. المطابقة الاختيارية عبر إشارة الاستفهام قد ترغب أحيانًا بمطابقة نمط اختياريًا، أي أن التعبير النمطي سيطابق السلسلة النصية بغض النظر إن احتوت السلسلة النصية على ذاك النمط أم لا. وتشير علامة الاستفهام ? أن النمط الذي يسبقها اختياري: >>> batRegex = re.compile(r'Bat(wo)?man') >>> mo1 = batRegex.search('The Adventures of Batman') >>> mo1.group() 'Batman' >>> mo2 = batRegex.search('The Adventures of Batwoman') >>> mo2.group() 'Batwoman' لاحظ أن الجزء ‎(wo)?‎ يعني أن النمط wo هو مجموعة اختيارية، فسيطابَق التعبير النمطي لو احتوت السلسلة النصية على 0 أو 1 من السلسلة النصية wo داخلها. وهذا يعني أن التعبير النمطي سيطابق 'Batwoman' و 'Batman' معًا. لو عدنا إلى مثال أرقام الهواتف السابق، يمكننا تعديل التعبير النمطي لكي يبحث عن أرقام الهواتف التي تحتوي أو لا تحتوي على رمز المنطقة: >>> phoneRegex = re.compile(r'(\d\d\d-)?\d\d\d-\d\d\d\d') >>> mo1 = phoneRegex.search('My number is 415-555-4242') >>> mo1.group() '415-555-4242' >>> mo2 = phoneRegex.search('My number is 555-4242') >>> mo2.group() '555-4242' يمكنك أن تقول أن ? يعني «طابق النمط الفرعي السابق 0 مرة أو مرة واحدة»، وإذا أردنا مطابقة علامة الاستفهام فلا ننسى تهريبها بكتابة ‎\?‎. المطابقة صفر مرة أو أكثر باستخدام رمز النجمة رمز النجمة * asterisk يعني «طابق صفر مرة أو أكثر»، فاستعمال النجمة يعني أن التعبير النمطي الذي يسبقها سيطابق لأي عدد من المرات في السلسلة النصية؛ فقد لا يكون موجودًا أو يكون مكررًا مرات كثيرة: >>> batRegex = re.compile(r'Bat(wo)*man') >>> mo1 = batRegex.search('The Adventures of Batman') >>> mo1.group() 'Batman' >>> mo2 = batRegex.search('The Adventures of Batwoman') >>> mo2.group() 'Batwoman' >>> mo3 = batRegex.search('The Adventures of Batwowowowoman') >>> mo3.group() 'Batwowowowoman' لاحظ أن التعبير النمطي ‎(wo)*‎ سيُطابَق 0 مرة في 'Batman'، ومرة واحدة في 'Batwoman'، وأربع مرات في 'Batwowowowoman'. إذا أردنا مطابقة النجمة نفسها فلا ننسَ تهريبها بكتابة ‎\*‎. المطابقة مرة واحدة أو أكثر باستخدام إشارة الجمع على خلاف إشارة النجمة * التي تطابق النمط صفر مرة أو أكثر، تعني إشارة الجمع أو إشارة الزائد + «مطابقة النمط مرة واحدة أو أكثر»، هذا يعني أن النمط الذي يسبق إشارة + يجب أن يكون موجودًا على الأقل مرة واحدة، على خلاف إشارة النجمة التي تسمح بألّا يكون النمط موجودًا في السلسلة النصية. قارن بين أثر رمز النجمة في القسم السابق والمثال الآتي: >>> batRegex = re.compile(r'Bat(wo)+man') >>> mo1 = batRegex.search('The Adventures of Batwoman') >>> mo1.group() 'Batwoman' >>> mo2 = batRegex.search('The Adventures of Batwowowowoman') >>> mo2.group() 'Batwowowowoman' >>> mo3 = batRegex.search('The Adventures of Batman') >>> mo3 == None True التعبير النمطي Bat(wo)+man لا يطابق السلسلة النصية 'The Adventures of Batman' لأن من المطلوب وجود التعبير الفرعي wo مرة واحدة على الأقل. إذا أردنا مطابقة إشارة الزائد فلا ننسى تهريبها ‎+‎. مطابقة النمط لعدد معين من المرات باستخدام الأقواس المجعدة إذا كانت لديك مجموعة تريد تكرارها لعدد معين من المرات، فأتبع تلك السلسلة برقم محاط بأقواس {}. فمثلًا التعبير النمطي ‎(Ha){3}‎ يطابق السلسلة النصية 'HaHaHa' لكنه لن يطابق 'HaHa' لأنها تحتوي على تكرارين للتعبير Ha فقط. وبدلًا من كتابة رقم واحد، يمكننا تحديد مجال من التكرارات بكتابة الحد الأدنى، ثم فاصلة، ثم الحد الأقصى ضمن القوسين، مثلًا التعبير النمطي ‎(Ha){3,5}‎ سيطابق 'HaHaHa' و 'HaHaHaHa' و 'HaHaHaHaHa'. يمكنك ألّا تحدد الرقم الأدنى أو الأقصى ضمن القوسين لتترك المجال مفتوحًا، فمثلًا ‎(Ha){3,}‎ سيطبق 3 تكرارات أو أكثر من المجموعة (Ha)، بينما ‎(Ha){,5}‎ سيطابق المجموعة (Ha) من 0 حتى 5 تكرارات. ستساعدنا الأقواس المجعدة على تقصير التعبير النمطي، إذ يتساوى التعبيران النمطيان الآتيان: (Ha){3} (Ha)(Ha)(Ha) وسيتساوي أيضًا: (Ha){3,5} ((Ha)(Ha)(Ha))|((Ha)(Ha)(Ha)(Ha))|((Ha)(Ha)(Ha)(Ha)(Ha)) جرب ما يلي في الطرفية التفاعلية: >>> haRegex = re.compile(r'(Ha){3}') >>> mo1 = haRegex.search('HaHaHa') >>> mo1.group() 'HaHaHa' >>> mo2 = haRegex.search('Ha') >>> mo2 == None True سيطابق ‎(Ha){3}‎ السلسلة النصية 'HaHaHa' لكنه لن يطابق 'Ha'، وبالتالي سيعيد التابع search()‎ القيمة None. المطابقة الجشعة والمطابقة غير الجشعة لمّا كان التعبير ‎(Ha){3,5}‎ يطابق 3 أو 4 أو 5 تكرارات من Ha في السلسلة النصية 'HaHaHaHaHa' فستتسائل لماذا يعيد استدعاء التابع group()‎ على الكائن Match السلسلة النصية 'HaHaHaHaHa' بدلًا من الاحتمالات الأخرى، ففي النهاية 'HaHaHa' و 'HaHaHaHa' هي مطابقات صالحة في التعبير النمطي ‎(Ha){3,5}‎. تكون التعابير النمطية في بايثون جشعة greedy افتراضيًا، وهذا يعني أنه في الحالات غير المحددة ستحاول التعابير النمطية مطابقة أطول سلسلة نصية ممكنة؛ أما المطابقة غير الجشعة non-greedy (وتسمى أحيانًا بالمطابقة الكسولة lazy) تطابق أقصر سلسلة نصية ممكنة، ونستطيع تحديد أننا نريد مطابقة غير جشعة عبر وضع علامة استفهام بعد قوس الإغلاق المجعد. جرب ما يلي ولاحظ الاختلافات بين النسخة الجشعة وغير الجشعة وماذا ستطابق في السلسلة النصية نفسها: >>> greedyHaRegex = re.compile(r'(Ha){3,5}') >>> mo1 = greedyHaRegex.search('HaHaHaHaHa') >>> mo1.group() 'HaHaHaHaHa' >>> nongreedyHaRegex = re.compile(r'(Ha){3,5}?') >>> mo2 = nongreedyHaRegex.search('HaHaHaHaHa') >>> mo2.group() 'HaHaHa' لاحظ أن علامة الاستفهام لها معنيان في التعابير النمطية، الأول هو المطابقة غير الجشعة، والثاني هو جعل المجموعة اختيارية. وهذان المعنيان غير مرتبطين ببعضهما. التابع findall()‎ بالإضافة إلى التابع search()‎ تمتلك كائنات Regex التابع findall()‎، وفي حين أن التابع search()‎ يعيد كائن Match لأول جزء مطابَق من السلسلة النصي التي نبحث فيها، يعيد التابع findall()‎ جميع المطابقات في السلسلة النصية. لنرى كيف يعيد التابع search()‎ الكائن Match على أول سلسلة نصية مطابقة: >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') >>> mo = phoneNumRegex.search('Cell: 415-555-9999 Work: 212-555-0000') >>> mo.group() '415-555-9999' وفي المقابل لن يعيد التابع findall()‎ الكائن Match، بل قائمة فيها سلاسل نصية، لطالما لم تكن هنالك مجموعات فرعية في التعبير النمطي؛ وتمثل كل سلسلة نصية في القائمة المعادة الجزء من النص المطابق للتعبير النمطي: >>> phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d') # لا توجد تعابير فرعية >>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000') ['415-555-9999', '212-555-0000'] أما لو كانت هنالك مجموعات أو تعابير فرعية في التعبير النمطي، فسيعيد التابع findall()‎ قائمةً من الصفوف tuples، ويمثل كل صف مطابقةً، وعناصر الصف هي السلاسل النصية المطابقة لكل تعبير فرعي في التعبير النمطي. لنرى الكلام السابق عمليًا لنفهمه، جرب ما يلي في الطرفية التفاعلية ولاحظ وجود أنماط فرعية في التعبير النمطي: >>> phoneNumRegex = re.compile(r'(\d\d\d)-(\d\d\d)-(\d\d\d\d)') # توجد تعابير فرعية >>> phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000') [('415', '555', '9999'), ('212', '555', '0000')] لنلخص ما الذي يعيد التابع findall()‎: عند استدعائه على تعبير نمطي لا يحتوي على مجموعات، مثل ‎\d\d\d-\d\d\d-\d\d\d\d فسيعيد التابع findall()‎ قائمةً من السلاسل النصية مثل ‎['415-555-9999', '212-555-0000']‎. عند استدعائه على تعبير نمطي يحتوي على مجموعات أو تعابير نمطية فرعية، مثل (‎\d\d\d)-(\d\d\d)-(\d\d\d\d) فسيعيد التابع findall()‎ قائمةً من الصفوف التي تحتوي على سلاسل نصية، سلسلة نصية لكل مجموعة، مثل: ‎[('415', '555', '9999'), ('212', '555', '0000')]‎ فئات المحارف تعلمنا في مثال أرقام الهواتف السابق أن ‎\d يطابق أي رقم، أي أنه اختصار للتعبير ‎(0|1|2|3|4|5|6|7|8|9)‎. هنالك عدد من فئات المحارف المختصرة، والتي تستطيع معرفتها من الجدول 7-1. اختصار فئة المحارف يمثل \d أي محرف يمثل رقمًا من 0 حتى 9 \D أي محرف ليس رقمًا من 0 حتى 9 \w أي حرف أو رقم أو الشرطة السفلية، يمكننا أن نقول أنه يطابق محارف «الكلمات» \W أي محرف ليس حرفًا أو رقمًا أو شرطةً سفلية (عكس ‎\w) \s أي مسافة فارغة أو مسافة جدولة أو سطر جديد، يمكننا أن نقول أنه يطابق «الفراغات» \S أي محرف ليس فراغًا أو مسافة جدولة أو سطر جديد الجدول 7-1: اختصارات فئات المحارف الشائعة تساعد فئات المحارف Character classes على اختصار التعابير النمطية، ففئة المحارف [0‎-5] تطابق الأرقام من 0 إلى 5 فقط، وهذا أكثر اختصارًا من كتابة (0‎|1|2|3|4|5)، لاحظ أننا نملك \d لمطابقة الأرقام فقط، لكن ‎\w يطابق الأرقام والأحرف والشرطة السفلية _، ولا يوجد اختصار لمطابقة الأحرف فقط، لكنك تستطيع أن تكتب [a-zA-Z] التي سنشرحها لاحقًا. >>> xmasRegex = re.compile(r'\d+\s\w+') >>> xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7 swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge') ['12 drummers', '11 pipers', '10 lords', '9 ladies', '8 maids', '7 swans', '6 geese', '5 rings', '4 birds', '3 hens', '2 doves', '1 partridge'] سيطابق التعبير النمطي ‎\d+\s\w+‎ أي نص فيه رقم واحد أو أكثر ‎\d+‎ يكون متبوعًا بفراغ ‎\s ويكون متبوعًا برقم أو حرف أو شرطة سفلية ‎\w+‎. سيعيد التابع findall()‎ السلاسل النصية المطابقة من التعبير النمطي في قائمة. كتابة فئات محارف مخصصة هنالك حالات نحتاج فيها إلى استخدام مجموعة من المحارف لكن الفئات المختصرة الشائعة (مثل ‎\d و ‎\w و ‎\s …إلخ.) غير مناسبة لحالتنا؛ لذا يمكننا تعريف فئات المحارف بأنفسنا باستعمال الأقواس المربعة []. فمثلًا فئة المحارف [aeiouAEIOU] ستطابق أي حرف صوتي سواءً كان بالحالة الصغيرة أو الكبيرة: >>> vowelRegex = re.compile(r'[aeiouAEIOU]') >>> vowelRegex.findall('RoboCop eats baby food. BABY FOOD.') ['o', 'o', 'o', 'e', 'a', 'a', 'o', 'o', 'A', 'O', 'O'] يمكنك أيضًا تضمين مجال من الأرقام أو الأحرف باستخدام الشرطة، فمثلًا فئة المحارف [a-zA-Z0-9] ستطابق جميع الأحرف الصغيرة والأحرف الكبيرة والأرقام. لاحظ أن رموز التعابير النمطية العادية لن تفسر داخل الأقواس المربعة، فلا حاجة إلى تهريب المحارف . أو * أو ? أو () بخط مائل خلفي. فمثلًا فئة المحارف [‎0-5.‎] تطابق الأعداد من 0 إلى 5 ونقطة. ولا حاجة إلى تهريبها وكتابة [0‎-5\.‎]. إذا وضعنا رمز القبعة caret ^ بعد قوس بداية فئة المحارف فسيعني «الرفض»، أي سيعكس محتويات فئة المحارف، أي أنها ستطابق أي محرف ليس موجودًا في فئة المحارف المحددة: >>> consonantRegex = re.compile(r'[^aeiouAEIOU]') >>> consonantRegex.findall('RoboCop eats baby food. BABY FOOD.') ['R', 'b', 'C', 'p', ' ', 't', 's', ' ', 'b', 'b', 'y', ' ', 'f', 'd', '.', ' ', 'B', 'B', 'Y', ' ', 'F', 'D', '.'] فبدلًا من مطابقة الأحرف الصوتية، أصبحت فئة المحارف تطابق كل شيء عدا الأحرف الصوتية. رمز القبعة ورمز الدولار يمكننا أيضًا استخدام رمز القبعة ^ في بداية التعبير النمطي للإشارة أن المطابقة يجب أن تبدأ من بداية السلسلة النصية التي نبحث فيها عن النمط. وبالمثل يمكننا استخدام رموز الدولار $ في نهاية التعبير النمطي للإشارة أنه يجب أن تنتهي السلسلة النصية بالتعبير النمطي. يمكننا أن نستعمل الرمزين ^ و $ معًا للإشارة إلى أن السلسلة النصية كلها يجب أن تطابق التعبير النمطي، أي لا يسمح بإجراء مطابقة جزئية على السلسلة النصية فقط. لنأخذ مثالًا بسيطًا التعبير النمطي r'^Hello'‎ الذي سيطابق أي سلسلة نصية تبدأ بالكلمة 'Hello': >>> beginsWithHello = re.compile(r'^Hello') >>> beginsWithHello.search('Hello, world!') <re.Match object; span=(0, 5), match='Hello'> >>> beginsWithHello.search('He said hello.') == None True التعبير النمطي ‎r'\d$'‎ يطابق السلاسل النصية التي تنتهي برقم من 0 إلى 9: >>> endsWithNumber = re.compile(r'\d$') >>> endsWithNumber.search('Your number is 42') <re.Match object; span=(16, 17), match='2'> >>> endsWithNumber.search('Your number is forty two.') == None True أما التعبير ‎r'^\d+$'‎ فهو يطابق السلاسل النصية التي تحتوي على رقم واحد على الأقل: >>> wholeStringIsNum = re.compile(r'^\d+$') >>> wholeStringIsNum.search('1234567890') <re.Match object; span=(0, 10), match='1234567890'> >>> wholeStringIsNum.search('12345xyz67890') == None True >>> wholeStringIsNum.search('12 34567890') == None True لاحظ أن آخر استدعائين للتابع search()‎ يوضحان كيف يجب أن تطابق السلسلة النصية كلها النمط، وليس جزءًا منها. حرف البدل تسمى النقطة . في التعابير النمطية بحرف البدل wildcard، وهي تطابق أي محرف عدا السطر الجديد. فمثلًا: >>> atRegex = re.compile(r'.at') >>> atRegex.findall('The cat in the hat sat on the flat mat.') ['cat', 'hat', 'sat', 'lat', 'mat'] تذكر أن النقطة ستطابق محرفًا واحدًا فقط، وهذا هو السبب في أن التعبير النمطي طابق lat في السلسلة النصية flat فقط. إذا أردنا البحث عن رمز النقطة فلا ننسى تهريبه عبر خط مائل خلفي ‎\.‎. مطابقة كل شيء مع النقطة والنجمة نحتاج أحيانًا إلى مطابقة كل شيء، فمثلًا لو أردنا مطابقة السلسلة النصية 'First Name‎:‎' متبوعةً بأي نصف، فحينها سنستعمل النقطة والنجمة ‎.*‎. تذكر أن النقطة تعني أي حرف عدا السطر الجديد، والنجمة تعني تكرار النمط الذي يسبقها 0 مرة أو أكثر. >>> nameRegex = re.compile(r'First Name: (.*) Last Name: (.*)') >>> mo = nameRegex.search('First Name: Al Last Name: Sweigart') >>> mo.group(1) 'Al' >>> mo.group(2) 'Sweigart' تجري النقطة والنجمة مطابقةً جشعةً، أي أنها تحاول أن تطابق النصوص أكثر ما يمكن، ولجعلها مطابقة غير جشعة فيمكننا استخدام النقطة والنجمة وإشارة الاستفهام ‎.*?‎ وبالتالي ستطابق بايثون النمط مطابقةً غير جشعة. جرب المثال الآتي لترى الفرق بين النسخة الجشعة وغير الجشعة: >>> nongreedyRegex = re.compile(r'<.*?>') >>> mo = nongreedyRegex.search('<To serve man> for dinner.>') >>> mo.group() '<To serve man>' >>> greedyRegex = re.compile(r'<.*>') >>> mo = greedyRegex.search('<To serve man> for dinner.>') >>> mo.group() '<To serve man> for dinner.>' يمكننا ترجمة التعبيرين النمطيين السابقين إلى «طابق قوس بدء زاوية ثم أي شيء ثم قوس إغلاق زاوية»، لكن السلسلة النصية '‎<To serve man> for dinner.>‎' فيها مطابقتين لقوس إغلاق زاوية، ففي النسخة غير الجشعة تطابق بايثون أقصر سلسلة نصية ممكنة '<To serve man>'؛ أما في النسخة الجشعة فتحاول بايثون مطابقة أطول سلسلة نصية ممكنة: '‎<To serve man> for dinner.>‎'. مطابقة الأسطر الجديدة مع رمز النقطة تعلمنا أن النقطة والنجمة ستطابق كل المحارف عدا السطر الجديد، لكن بتمرير وسيط ثاني إلى الدالة re.compile()‎ هو re.DOTALL فسنتمكن من استعمال النقطة لمطابقة جميع المحارف بما فيها السطر الجديد: >>> noNewlineRegex = re.compile('.*') >>> noNewlineRegex.search('Serve the public trust.\nProtect the innocent. \nUphold the law.').group() 'Serve the public trust.' >>> newlineRegex = re.compile('.*', re.DOTALL) >>> newlineRegex.search('Serve the public trust.\nProtect the innocent. \nUphold the law.').group() 'Serve the public trust.\nProtect the innocent.\nUphold the law.' التعبير النمطي noNewlineRegex لم نمرر إليه re.DOTALL حين استدعاء re.compile()‎ لذا سيطابق النمط كل شيء حتى الوصول إلى محرف السطر الجديد؛ بينما newlineRegex مررنا إليه re.DOTALL حين استدعاء re.compile()‎ لذا سيطابق كل شيء، لهذا أعاد استدعاء newlineRegex.search()‎ السلسلة النصية كاملة بما فيها الأسطر الجديدة. مراجعة لرموز التعابير النمطية شرحنا الكثير من الرموز في هذا المقال، لنراجع سريعًا ما الذي تعلمناه حول أساسيات التعابير النمطية: يطابق ? المجموعة التي تسبقه 0 مرة أو 1 مرة. يطابق * المجموعة التي تسبقه 0 مرة أو أكثر. يطابق + المجموعة التي تسبقه 1 مرة أو أكثر. يطابق {n} المجموعة التي تسبقه n مرة تمامًا. يطابق {‎n,‎} المجموعة التي تسبقه n مرة أو أكثر. يطابق {‎, m} المجموعة التي تسبقه 0 مرة حتى m مرة. يطابق {n, m} المجموعة التي تسبقه n مرة على الأقل و m على الأكثر. تجري ‎{n,m}?‎ أو ‎*?‎ أو ‎+?‎ مطابقة غير جشعة. يطابق ‎^spam السلاسل النصية التي تبدأ بالكلمة spam. يطابق spam$‎ السلاسل النصية التي تنتهي بالكلمة spam. يطابق الرمز . أي محرف عدا السطر الجديد. تطابق فئة المحارف ‎\d و ‎\w و ‎\s رقمًا أو كلمةً أو فراغًا على التوالي وبالترتيب. تطابق فئة المحارف ‎\D و ‎\W و ‎\S أي شيء عدا أن يكون رقمًا أو كلمةً أو فراغًا على التوالي وبالترتيب. تطابق فئة المحارف [abc] أي شيء بين القوسين المربعين (مثل a أو b أو c). تطابق فئة المحارف [‎^abc] أي شيء ليس بين القوسين المربعين. المطابقة غير الحساسة لحالة الأحرف تطابق التعابير النمطية النص بنفس الحالة المحددة، فمثلًا تطابق التعابير النمطية الآتية سلاسل نصية مختلفة: >>> regex1 = re.compile('RoboCop') >>> regex2 = re.compile('ROBOCOP') >>> regex3 = re.compile('robOcop') >>> regex4 = re.compile('RobocOp') لكن في بعض الأحيان لا يهمنا ما هي حالة الأحرف، وكل ما نريده هو مطابقة النص بغض النظر عن حالته، فحينها نريد جعل التعابير النمطية غير حساسة لحالة الأحرف، وذلك بتمرير وسيطٍ ثانٍ للدالة re.compile()‎ هو re.IGNORECASE أو re.I: >>> robocop = re.compile(r'robocop', re.I) >>> robocop.search('RoboCop is part man, part machine, all cop.').group() 'RoboCop' >>> robocop.search('ROBOCOP protects the innocent.').group() 'ROBOCOP' >>> robocop.search('Al, why does your programming book talk about robocop so much?').group() 'robocop' استبدال السلاسل النصية عبر التابع sub()‎ لا تستعمل التعابير النمطية للعثور على أنماط من النصوص فقط، وإنما لاستبدالها أيضًا. يقبل التابع sub()‎ في كائنات Regex معاملين، الأول هو السلسلة النصية التي نريد تبديل المطابقات إليها، والثاني هو السلسلة النصية التي نريد البحث فيها عن مطابقات لتبديلها. يعيد التابع sub()‎ سلسلةً نصية بعد تطبيق جميع عمليات الاستبدال: >>> namesRegex = re.compile(r'Agent \w+') >>> namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.') 'CENSORED gave the secret documents to CENSORED.' قد نحتاج أحيانًا إلى استخدام النص المطابق كجزء من النص الجديد، لذا يمكننا استخدام ‎\1 و ‎\2 و ‎\3 في الوسيط الأول الممرر إلى التابع sub()‎ للوصول إلى المجموعات الفرعية المطابقة. لنقل أننا نريد أن نخفي أسماء الأشخاص في مثالنا السابق، لكننا نريد إظهار الحرف الأول من اسمهم فقط، فحينها نستطيع استخدام التعبير النمطي Agent (\w)\w*‎ وتمرير r'\1‎****'‎ كأول معامل إلى التابع sub()‎، وستشير ‎\1 إلى السلسلة النصية المطابقة من النمط الفرعي الأول، أي المجموعة (‎\w) في التعبير النمطي: >>> agentNamesRegex = re.compile(r'Agent (\w)\w*') >>> agentNamesRegex.sub(r'\1****', 'Agent Alice told Agent Carol that Agent Eve knew Agent Bob was a double agent.') A**** told C**** that E**** knew B**** was a double agent.' التعامل مع التعابير النمطية المعقدة التعابير النمطية التي تطابق نصًا بسيطًا تكون سهلة الفهم، لكن إذا أردنا مطابقة النصوص المعقدة فستصبح التعابير النمطية معقدة وطويلة، يمكننا التعامل مع هذا الإشكال بالطلب من الدالة re.compile()‎ أن تتجاهل الفراغات والتعليقات داخل السلسلة النصية التي تمثل التعبير النمطي، وهذا النمط يسمى verbose mode، ويمكن تفعيله بتمرير re.VERBOSE إلى المعامل الثاني في الدالة re.compile()‎. فبدلًا من محاولة قراءة تعبير نمطي صعب مثل هذا: phoneRegex = re.compile(r'((\d{3}|\(\d{3}\))?(\s|-|\.)?\d{3}(\s|-|\.)\d{4} (\s*(ext|x|ext.)\s*\d{2,5})?)') نستطيع تقسيم التعبير النمطي إلى عدة أسطر مع تعليقات توضح وظيفة كل قسم: phoneRegex = re.compile(r'''( (\d{3}|\(\d{3}\))? # رمز المنطقة (\s|-|\.)? # فاصل \d{3} # أول 3 أرقام (\s|-|\.) # فاصل \d{4} # آخر 4 أرقام (\s*(ext|x|ext.)\s*\d{2,5})? # اللاحقة )''', re.VERBOSE) لاحظ أننا استعملنا علامة الاقتباس الثلاثية لإنشاء سلسلة نصية متعددة الأسطر، لكي نستطيع تقسيم التعبير على عدة أسطر وتسهيل قراءته. تتبع التعليقات داخل التعابير النمطية نفس قواعد التعليقات في بايثون: سيتم تجاهل كل شيء بعد الرمز #. لاحظ أن الفراغات الزائدة في السلسلة النصية متعددة الأسطر لا تؤثر على معنى التعبير النمطي ولا تعد جزءًا منه، لذا يمكنك استخدام الفراغات كيفما تشاء لتسهيل قراءة التعبير النمطي. استخدام re.IGNORECASE و re.DOTALL و re.VERBOSE ماذا لو أردنا استخدام re.VERBOSE لكتابة تعليقات في التعابير النمطية لكننا نريد أن نتجاهل حالة الأحرف أيضًا عبر re.IGNORECASE؟ للأسف لا تقبل الدالة re.compile()‎ غير قيمة واحدة كثاني وسيط لها، لكن يمكننا تجاوز هذه المحدودية بجمع القيم re.IGNORECASE و re.DOTALL و re.VERBOSE مع بضعها عبر الخط العمودي | وهو يسمى في هذا السياق بعامل OR الثنائي Bitwise OR. أي أننا لو أردنا كتابة تعبير نمطي غير حساس لحالة الأحرف ويسمح بمطابقة محرف السطر الجديد برمز النقطة، فحينها سيكون استدعاء الدالة re.compile()‎ كما يلي: >>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL) ولو أردنا تضمين الخيارات كلها فنكتب: >>> someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL | re.VERBOSE) أصل هذه الصيغة من الإصدارات القديمة من بايثون، وشرح العوامل الثنائية خارج سياق هذه السلسلة. لاحظ أن هنالك خيارات أخرى يمكن تمريرها إلى الدالة re.compile()‎ لكنها غير شائعة الاستخدام، ويمكنك القراءة عنها في التوثيق الرسمي. مشروع: برنامج استخراج أرقام الهواتف وعناوين البريد الإلكتروني لنقل أنك موكل بمهمة مملة هي العثور على جميع أرقام الهواتف وعناوين البريد الإلكتروني في صفحة ويب طويلة أو مستند كبير. إذا كنت ستتصفح تلك الصفحة وتبحث عنها يدويًا فستأخذ منك وقتًا طويلًا وجهدًا كبيرًا؛ لكن إن كان لديك برنامج يمكنه البحث في الحافظة عن أرقام الهواتف وعناوين البريد الإلكتروني فسيكون ذلك رائعًا، فكل ما عليك فعله هو الضغط على Ctrl+A لتحديد كل النص ثم Ctrl+C لنسخه إلى الحافظة، ثم شغل برنامجك وسيعدل محتويات المحافظة ويضع فيها أرقام الهواتف وعناوين البريد الإلكتروني التي سيعثر عليها. قد يغريك حينما تبدأ بمشروع جديد أن تبدأ كتابة الشيفرات فورًا، لكن من الأفضل أن تنظر نظرةً شاملة على البرنامج قبل البدء بكتابته، وأنصحك أن ترسم خطةً بسيطةً لما يجب على برنامجك فعله، دون التفكير بالشيفرات وكيفية كتابتها، وإنما اكتب العناوين العريضة فقط. فمثلًا لو أردنا العمل على برنامج استخراج أرقام الهواتف وعناوين البريد الإلكتروني فسنحتاج إلى: الحصول على النص من الحافظة. العثور على جميع أرقام الهواتف وعناوين البريد الإلكتروني. لصق الناتج في الحافظة. يمكنك أن تبدأ الآن بالتفكير كيفية كتابة ذلك في بايثون. سيحتاج برنامجك إلى: استخدام الوحدة pyperclip لنسخ ولصق النصوص من الحافظة. إنشاء تعبيران نمطيان، واحد لمطابقة أرقام الهواتف والثاني لمطابقة عناوين البريد الإلكتروني. العثور على جميع المطابقات لكلي التعبيرين النمطيين، وليس أول مطابقة فقط. تنسيق الناتج في سلسلة نصية واحدة جاهزة ولصقها في الحافظة. عرض رسالة خطأ إن لم يعثر على مطابقات في النص. هذا هو المخطط العام للمشروع، وسنركز على كيفية حل كل خطوة حين كتابة الشيفرات. لاحظ أننا تعلمنا كيفية التعامل مع كل خطوة من الخطوات السابقة في بايثون. الخطوة 1: إنشاء تعبير نمطي لأرقام الهواتف علينا بداية إنشاء تعبير نمطي للبحث عن أرقام الهواتف، لننشِئ ملفًا باسم phoneAndEmail.py ونكتب فيه: #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة import pyperclip, re phoneRegex = re.compile(r'''( (\d{3}|\(\d{3}\))? # رمز المنطقة (\s|-|\.)? # فاصل (\d{3}) # أول 3 أرقام (\s|-|\.) # فاصل (\d{4}) # آخر 4 أرقام (\s*(ext|x|ext.)\s*(\d{2,5}))? # اللاحقة )''', re.VERBOSE) # TODO: إنشاء التعبير النمطي لعناوين البريد الإلكتروني # TODO: العثور على المطابقات في الحافظة # TODO: لصق الناتج في الحافظة التعليقات التي تبدأ بالكلمة TODO هي لبنية التطبيق، وسنبدلها إلى شيفرات لاحقًا. يبدأ رقم الهاتف برمز منطقة اختياري، لذا نجعل المجموعة الفرعية التي تدل على رمز المنطقة اختياريةً بإضافة علامة استفهام ?. ولأن رمز المنطقة يمكن أن يكون 3 أرقام (أي ‎\d{3}‎) أو 3 أرقام ضمن أقواس (أي ‎\(\d{3}\)‎) فاستعملنا الخط العمودي | للفصل بينهما. لاحظ أننا استطعنا إضافة التعليقات إلى التعبير النمطي متعدد الأسطر لكي نتذكر ماذا يفعل كل سطر. يمكن أن يكون الفاصل في أرقام الهواتف فراغًا ‎\s أو شرطة - أو نقطة .، لذا فصلنا بين هذه الاحتمالات بخط عمودي. بقية أقسام التعبير النمطي واضحة وسهلة: 3 أرقام، يتبعها فاصل، يتبعها 4 أرقام، وآخر قسم هو اللاحقة الاختيارية التي تبدأ بأي عدد من الفراغات يليها الكلمة ext أو x أو ext.‎، ويليها رقمين إلى 5 أرقام. ملاحظة: من السهل أن نخلط بين التعابير النمطية التي تحتوي على مجموعات عبر استخدام الأقواس ()، وبين الأقواس المُهربة ‎)‎ و ‎(‎. تذكر أن تتأكد أنك تستخدم النوع الصحيح من الأقواس إن حصلت على رسالة خطأ تشير إلى قوسٍ ناقص. الخطوة 2: إنشاء تعبير نمطي لعناوين البريد الإلكتروني علينا الآن إنشاء تعبير نمطي لمطابقة عناوين البريد الإلكتروني: #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة import pyperclip, re phoneRegex = re.compile(r'''( --مقتطع-- # إنشاء التعبير النمطي لعناوين البريد الإلكتروني emailRegex = re.compile(r'''( ➊ [a-zA-Z0-9._%+-]+ # اسم المستخدم ➋ @ # @ إشارة ➌ [a-zA-Z0-9.-]+ # اسم النطاق (\.[a-zA-Z]{2,4}) # اللاحقة )''', re.VERBOSE) # TODO: العثور على المطابقات في الحافظة # TODO: لصق الناتج في الحافظة يكون اسم المستخدم جزءًا من البريد الإلكتروني ➊، وفيه محارف واحدة أو أكثر مما يلي: الأحرف الكبيرة والصغيرة، والأرقام، والنقطة، والشرطة السفلية، وإشارة النسبة المئوية، وإشارة زائد، وشرطة عادية. يمكننا جمع كل ما سبق في فئة المحارف الآتية [a-zA-Z0-9‎._%+-‎]. يفصل بين اسم المستخدم والنطاق برمز @ ➋؛ ولا يسمح اسم النطاق بنفس المحارف التي يسمح بها اسم المستخدم، ولذا يجب أن ننشِئ فئة محارف فيها الأرقام والأحرف والنقط والشرطات [a-zA-Z0-9‎.-‎] ➌. آخر جزء هو اللاحقة، مثل ‎.com (يسمى تقنيًا بالنطاق في أعلى مستوى top-level domain) الذي هو رمز النقطة ويلي أي شيء، ويفترض أن يكون بين 2 و 4 محارف. هنالك قواعد كثيرة جدًا لمطابقة عناوين البريد الإلكتروني، والتعبير النمطي الذي كتبناه لن يطابق جميع عناوين البريد الصالحة قاعديًا، لكنه سيطابق جميع عناوين البريد الشائعة. الخطوة 3: العثور على جميع المطابقات في الحافظة أصبح لدينا الآن التعابير النمطية التي ستطابق أرقام الهواتف وعناوين البريد الإلكتروني عبر الوحدة re في بايثون، واستطعنا الوصول إلى محتويات الحافظة عبر الدالة paste()‎ في الوحدة pyperclip. نحتاج الآن إلى استخدام التابع findall()‎ للكائن Regex لإعادة قائمة من الصفوف. تأكد أن برنامجك يشبه البرنامج الآتي: #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة import pyperclip, re phoneRegex = re.compile(r'''( --مقتطع-- # العثور على المطابقات في الحافظة text = str(pyperclip.paste()) ➊ matches = [] ➋ for groups in phoneRegex.findall(text): phoneNum = '-'.join([groups[1], groups[3], groups[5]]) if groups[8] != '': phoneNum += ' x' + groups[8] matches.append(phoneNum) ➌ for groups in emailRegex.findall(text): matches.append(groups[0]) # TODO: لصق الناتج في الحافظة هنالك صف لكل مطابقة ناتجة، وكل صف يحتوي على السلاسل النصية في كل مجموعة في التعبير النمطي، تذكر أن المجموعة 0 تطابق كامل التعبير النمطي، لذا ما يهمنا من الناتج هو المجموعة في الفهرس 0. سنخزن المطابقات في قائمة باسم matches ➊، وسنبدأ برنامجنا بقائمة فارغة، ثم لدينا حلقتَي for. ففي الحلقة الخاصة بالبريد الإلكتروني نضيف محتويات المجموعة 0 إلى القائمة matches ➌؛ أما في الحلقة الخاصة بأرقام الهواتف فلا نريد أن نضيف ناتج المجموعة 0 إلى القائمة. صحيحٌ أن برنامجنا يستطيع مطابقة أرقام الهواتف بأكثر من صيغة، لكننا نريد إضافة أرقام الهواتف بصيغة واحدة معيارية، لذا ننشِئ المتغير phoneNum الذي يحتوي على قيمة المجموعات 1 و 3 و 5 و 8 من النص المطابق، وهي تمثل رقم المنطقة، وأول 3 أرقام، وآخر 4 أرقام، واللاحقة على التوالي وبالترتيب. الخطوة 4: جمع المطابقات في سلسلة نصية ونسخها إلى الحافظة أصبح لدينا الآن عناوين البريد الإلكتروني وأرقام الهواتف كقائمة من السلاسل النصية المخزنة في matches، إذا أردنا نسخ هذه القائمة إلى الحافظة فسنستعمل الدالة pyperclip.copy()‎ التي تقبل سلسلة نصية، لكن المطابقات موجودة لدينا في قائمة، لذا نحن بحاجة إلى استخدام التابع join()‎ على القائمة matches. للتأكد أن برنامجنا يعمل كما ينبغي له، فلنطبع أي مطابقات إلى الطرفية، وإذا لم تطابق أي عناوين بريدية أو أرقام هواتف فسيخبر البرنامجُ المستخدمَ بذلك. #! python3 # phoneAndEmail.py - البحث عن أرقام الهواتف وعناوين البريد في الحافظة --مقتطع-- for groups in emailRegex.findall(text): matches.append(groups[0]) # Copy results to the clipboard. if len(matches) > 0: pyperclip.copy('\n'.join(matches)) print('Copied to clipboard:') print('\n'.join(matches)) else: print('No phone numbers or email addresses found.') تشغيل البرنامج للتجربة، افتح المتصفح وتوجه إلى صفحة تواصل معنا من موقع nostarch.com، ثم حددها كلها بالضغط على Ctrl+A ثم انسخها إلى الحافظة Ctrl+C، ثم شغل البرنامج. يجب أن يكون الناتج كما يلي: Copied to clipboard: 800-420-7240 415-863-9900 415-863-9950 info@nostarch.com media@nostarch.com academic@nostarch.com info@nostarch.com أفكار لمشاريع مشابهة هنالك تطبيقات كثيرة لعملية مطابقة أنماط من النص (واستبدلها عبر التابع sub()‎)، فمثلًا يمكنك: العثور على جميع روابط URL التي تبدأ بالسابقة http://‎ أو https://‎. العثور على التواريخ بصيغها المختلفة مثل 3/14/2022 أو 03-14-2022 أو 2022/3/14 وتبديلها إلى صيغة واحدة قياسية موحدة. إزالة البيانات الحساسة مثل أرقام البطاقات البنكية من المستندات. العثور على الأخطاء الشائعة مثل الفراغات المكررة بين الكلمات، أو الكلمات المكررة خطأً، أو علامات الترقيم المكررة. الخلاصة صحيحٌ أن الحواسيب قادرة على البحث عن النصوص بسرعة، لكن يجب أن نخبرها تحديدًا ما الذي عليها البحث عنه. تسمح لنا التعابير النمطية أن نحدد نمطًا من المحارف الذي نريد البحث عنه، وتستخدم التعابير النمطية في البرمجة أو حتى في محررات النصوص أو مع برامج معالجة جداول البيانات. تأتي الوحدة re مع أساس لغة بايثون، وتسمح لنا ببناء كائنات Regex، التي تمتلك عددًا من التوابع: التابع search()‎ للبحث عن مطابقة واحدة، والتابع findall()‎ للعثور على جميع المطابقات، والتابع sub()‎ لإجراء عمليات البحث والاستبدال. ستجد المزيد من المعلومات في توثيق بايثون الرسمي عن التعابير النمطية، وفي موقع regular-expressions.info. مشاريع تدريبية لكي تتدرب، اكتب برامج لتنفيذ المهام الآتية. التحقق من التاريخ اكتب تعبيرًا نمطيًا يستطيع التعرف على التواريخ بالصيغة DD/MM/YYYY، افترض أن الأيام تتراوح بين 01 إلى 31، والأشهر من 01 إلى 12، والسنوات من 1000 إلى 2999. لاحظ أنه إذا كان اليوم أو الشهر متألف من رقم واحد فيجب أن يسبق بصفر 0. لا حاجة أن يتعرف التعبير النمطي على الأرقام الصحيحة لكل شهر من أشهر السنة، أو إذا كانت السنوات كبيسة؛ فلا بأس أن يقبل تاريخًا مثل 31/02/2022 أو 31/04/2022. خزن السلاسل النصية الناتج في متغيرات باسم month و day و year، ثم اكتب شيفرة تتحقق إن كان التاريخ صالحًا، فأشهر نسيان/أبريل وحزيران/يونيو وأيلول/سبتمبر وتشرين الثاني/نوفمبر لها 30 يومًا، وشهر شباط/فبراير له 28 يومًا، وبقية الأشهر 31 يومًا. يكون طول شهر شباط/فبراير 29 يومًا في السنوات الكبيسة، والسنة الكبيرة هي كل سنة تكون قابلة للقسمة على 4، عدا السنوات القابلة للقسمة على 100؛ ما لم تكن السنة قابلة للقسم على 400. لاحظ أن هذه العمليات الحسابية تجعل من المستحيل كتابة تعبير نمطي يمكنه فعل كل ذلك. التحقق من كلمة مرور قوية اكتب دالةً تستخدم التعابير النمطية للتأكد أن كلمة المرور الممررة إليها قوية. يمكننا تعريف كلمة المرور القوية أنها بطول 8 محارف على الأقل، وتحتوي على أحرف كبيرة وصغيرة، وفيها رقم واحد على الأقل. قد تحتاج إلى اختبار كلمة المرور على أكثر من تعبير نمطي للتأكد من قوتها. نسخة من الدالة strip()‎ باستخدام التعابير النمطية اكتب دالةً تقبل سلسلةً نصيةً وتفعل فيها كما تفعله الدالة strip()‎ التي تعلمناها في المقال السابق. إذا لم يمرر إليها أي وسائل بخلاف السلسلة النصية، فسنحذف جميع الفراغات من بداية ونهاية السلسلة النصية؛ وإلا فسنحذف المحارف المحددة في الوسيط الثاني الممرر إلى الدالة. ترجمة -بتصرف- للفصل Pattern Matching With Regular Expressions من كتاب Automate the Boring Stuff with Python. اقرأ أيضًا المقال السابق: معالجة النصوص باستخدام لغة بايثون python التعابير النمطية في البرمجة القوائم Lists في لغة بايثون تهيئة بيئة العمل في بايثون Python
  6. لن يتم السؤال في كل الأمور النظرية، مع التركيز بشكل أكبر على الجانب العملي والأمور النظرية الهامة بالنسبة للجانب العملي. أيضًا السؤال حول التطبيقات العملية التي قمت بها ومدى استيعابك، بعد ذلك سيتم تحديد مشروع عملي لك لتنفيذه كمشروع تخرج.
  7. السلام عليكم هل اختبار إنهاء الدورة سيشتمل على المعلومات النظرية التي يتم ذكرها أثناء الشرح أم أنه مقتصر فقط على التطبيق العملي؟
  8. الخطأ الذي تواجهه ناتج عن وجود عنصر <a> داخل عنصر آخر <a>. في الكود لديك، يتم استخدام NavLink كعنصر أب ويحتوي داخله على عنصر <Dropdown> والذي يحتوي بدوره على عنصر <Link>. هذا ما يسبب الخطأ لأن NavLink يستخدم عنصر <a> في هيكله الداخلي. لحل هذه المشكلة، يمكنك تعديل هيكل الكود بحيث لا يحتوي NavLink على عناصر <a> أخرى بداخله. يمكن فعل ذلك عن طريق وضع محتوى الـ Dropdown خارج الـ NavLink، ثم استخدام أسلوب آخر لعرض القائمة المنسدلة. على سبيل المثال: import React, { useState } from "react"; import "./header.css"; import { Link, NavLink } from "react-router-dom"; import { CiMenuBurger } from "react-icons/ci"; import { RiCloseFill } from "react-icons/ri"; import logo from "../../images/logo.png"; import { useSelector } from "react-redux"; import { BsThreeDotsVertical } from "react-icons/bs"; import Dropdown from "./Dropdown"; function Header() { const [click, setClick] = useState(false); const [dropdown, setDropdown] = useState(false); const { user } = useSelector((state) => state.auth); const handleClick = () => setClick(!click); return ( <> <nav className="navbar"> <div className="nav-container"> <Link to="/" className="nav-logo"> <div className="logo"> <img src={logo} alt="logo" /> </div> <span className="logo-icon">Bookly</span> </Link> <ul className={click ? "nav-menu active" : "nav-menu"}> {user ? ( <> <li className="nav-item"> <NavLink to="/" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Home </NavLink> </li> {user.isAdmin && ( <li className="nav-item"> <NavLink to="/admin" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Dashboard </NavLink> </li> )} <li className="nav-item"> <span className="nav-links nav-dropdown" onClick={() => setDropdown(!dropdown)} > {user.name} <BsThreeDotsVertical /> </span> {dropdown && <Dropdown />} </li> </> ) : ( <> <li className="nav-item"> <NavLink to="/" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Home </NavLink> </li> <li className="nav-item"> <NavLink to="/login" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Login </NavLink> </li> </> )} </ul> <div className="nav-icon" onClick={handleClick}> {click ? ( <span className="icon"> <RiCloseFill /> </span> ) : ( <span className="icon"> <CiMenuBurger /> </span> )} </div> </div> </nav> </> ); } export default Header; وفي مكون Dropdown، تأكد من أن الروابط لا تستخدم عناصر <a> داخل عناصر أخرى من نفس النوع: import React from 'react' import { useDispatch } from 'react-redux' import { logoutUser } from '../../redux/apiCalls/authApiCalls' import { Link } from 'react-router-dom' function Dropdown() { const dispatch = useDispatch() const logout = () => { dispatch(logoutUser()) } return ( <div className='dropdown'> <ul className="dropdown-menu"> <li className="dropdown-item" onClick={logout}> Logout </li> <li className="dropdown-item"> <Link className='dropdown-item-link' to='/favorites'> My Favorites </Link> </li> </ul> </div> ) } export default Dropdown بهذه الطريقة، تتجنب وجود عنصر <a> داخل عنصر آخر، مما يحل الخطأ الذي يظهر لك
  9. السلام عليكم. الرجاء مساعدتي في حل الخطأ التالي react-dom.development.js:86 Warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>. at a Code: import React, { useState } from "react"; import "./header.css"; import { Link, NavLink } from "react-router-dom"; import { CiMenuBurger } from "react-icons/ci"; import { RiCloseFill } from "react-icons/ri"; import logo from "../../images/logo.png"; import { useSelector } from "react-redux"; import { BsThreeDotsVertical } from "react-icons/bs"; import Dropdown from "./Dropdown"; function Header() { const [click, setClick] = useState(false); const { user } = useSelector((state) => state.auth); const handleClick = () => setClick(!click); return ( <> <nav className="navbar"> <div className="nav-container"> <Link to="/" className="nav-logo"> <div className="logo"> <img src={logo} alt="logo" /> </div> <span className="logo-icon">Bookly</span> </Link> <ul className={click ? "nav-menu active" : "nav-menu"}> {user ? ( <> <li className="nav-item"> <NavLink to="/" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Home </NavLink> </li> {user.isAdmin && ( <li className="nav-item"> <NavLink to="/admin" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Dashboard </NavLink> </li> )} <li className="nav-item"> <NavLink to="/login" className={({ isActive }) => isActive ? "active" : "nav-links nav-dropdown" } onClick={handleClick} > {user.name} <BsThreeDotsVertical /> <Dropdown /> </NavLink> </li> </> ) : ( <> <li className="nav-item"> <NavLink to="/" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Home </NavLink> </li> <li className="nav-item"> <NavLink to="/login" className={({ isActive }) => isActive ? "active" : "nav-links" } onClick={handleClick} > Login </NavLink> </li> </> )} </ul> <div className="nav-icon" onClick={handleClick}> {click ? ( <span className="icon"> <RiCloseFill /> </span> ) : ( <span className="icon"> <CiMenuBurger /> </span> )} </div> </div> </nav> </> ); } export default Header; Child Component import React from 'react' import { useDispatch } from 'react-redux' import { logoutUser } from '../../redux/apiCalls/authApiCalls' import { Link } from 'react-router-dom' function Dropdown() { const dispatch = useDispatch() const logout = () => { dispatch(logoutUser()) } return ( <div className='dropdown'> <ul className="dropdown-menu"> <li className="dropdown-item" onClick={logout}> Logout </li> <li className="dropdown-item"> <Link className='dropdown-item-link' to='/favorites'> My Favorites </Link> </li> </ul> </div> ) } export default Dropdown شكرا.
  10. هل تم إخبارك بسبب للرفض؟ أيضًا هل المحتوى أصلي وبجودة جيدة؟ يمكنك طرح المدونة هنا لتفقدها.
  11. البارحة
  12. لقد قمت بكل ماتم ذكره في المقال وضعت اكثر من100 مقال بأكثر من الف كلمة في بعض المقالات والمدونة للقصص مستوفية لجميع الشروط وعمرها أكثر من سنة ولم يتم القبول بعد
  13. اشكرك اخي للتوضيح وبإذن الله سيتم إجتياز الامتحان وتطبيق ماتم تعلمه
  14. أتفهم الظروف في دولة اليمن، لكن مجال البرمجة له طابع خاص، وهو أن الشهادة ليس لها وزن بشكل كبير، نعم هي دلالة على أنه الشخص اجتاز محتوى تعليمي معين ولديه مهارات معينة، لكن ليس بالضرورة أن ذلك صحيح. لذا يتم الإهتمام بالمهارات الفعلية وليس الشهادة، أي ما أنت قادر على تقديمه وتنفيذه في الواقع العملي وإثباته من خلال معرض أعمالك. في الأكاديمية لا يوجد ماجستير فقط شهادة إعتماد على إتمام الدورة وليس حضورها أي يتم منحها لن اجتاز الدورة بالفعل واستوعبها وأتم التطبيقات العملية.
  15. السلام عليكم ورحمة الله وبركاته وكل عام وانتم بألف خير وصحة وعافية انا المهندس ميثاق عبدالله من اليمن وعندي بكالوريوس علوم وهندسة الكمبيوتر عام 2006 وحاليا اخذت دورتين من دورات تصميم تطبيقات الويب التابعة لكم وعندي استفسار هل تسمحو بإعطاء شهادة ماجستير من قبلكم في مجال برمجة الويب وتطبيقات الجوال عن طريق اخذ الدورات التابعة لكم والدخول للامتحان لان اوضاعنا في اليمن لاتخفى عليكم ولااستطيع السفر للدراسة فالطريقة الوحيدة لدي هي تعلم دوراتكم اون لاين ودخول الامتحان للحصول على شهادة الماجستير
  16. بخصوص ذلك، أرجو التحدث لمركز المساعدة، فالأمور المالية أو تبديل الدورات يتم من خلاله. وفي حال هناك أمر تودي الاستفسار عنه أرجو طرحه.
  17. يمكنك التكلم مع إدارة حسوب من خلال مركز الدعم من هنا.
  18. اود تبديل الدورة من بايثون إلى جافا سكربت كيف أبدل الدورة من بايثون إلى جافا سكربت
  19. هل تريد شراء كلية أم تريد بيع كليتك؟ هل تبحث عن فرصة لبيع كليتك مقابل المال بسبب الانهيار المالي ولا تعرف ماذا تفعل، تواصل معنا اليوم وسنقدم لك مبلغًا جيدًا من المال شاملاً التأمين على الحياة والتأمين الطبي لكليتك . اسمي دكتور جوس أنا استشاري أمراض الأعصاب الدولي في مستشفى ماونت ألفيرنيا. عيادتنا متخصصة في جراحة الكلى والجراحات الأخرى ونتعامل أيضًا مع شراء وزرع الكلى مع متبرع حي. نحن موجودون في سنغافورة، تركيا، الولايات المتحدة الأمريكية، ماليزيا، الهند. إذا كنت مهتمًا ببيع أو شراء الكلى فلا تتردد في الاتصال بنا على، فايبر ..+91 9233859627 البريد الإلكتروني: contact@mountalverniahospitals.com أطيب التحيات الدكتور جوس تيو. المستشار الطبي الدولي..
  20. بفرض أن لديك منتجًا رائعًا يتناسب مع مجال متجرك الإلكتروني، وقد وضعتَ له إستراتيجية تسويق ناجحة، ما الذي سيدفع الأشخاص الذين يتصفحون الإنترنت بعفوية إلى التحول إلى مشترين لهذا المنتج حسب رأيك؟ ربما تكون قد جهزت صورًا جذابة وأوصافًا جيدةً للمنتجات مع أسعار مناسبة، لكنك لا تزال غير قادر على جذب كمية المبيعات المطلوبة من السوق المستهدَف، أو ربما لديك مبيعات مقبولة لكنك ترغب في زيادتها، فالجميع يرغب بتحسين مستوى عمله باستمرار. يفتقر أصحاب المتاجر الإلكترونية إلى ميزة التفسيرات الشفهية للزبائن التي يتمتع بها أصحاب المتاجر التقليدية؛ حيث يستطيع البائع شرح جميع مزايا المنتجات والإشارة إليها، أو حتى شرح آلية عملها. كل ما يمتلكه أصحاب المتاجر الإلكترونية هو قوة كلماتهم المكتوبة، وفي حال استُخدِمت هذه القوة بطريقة صحيحة، فقد تكون من أكثر أساليب التسويق فعاليةً. ربما كل ما عليك فعله لتحقيق مبيعات أفضل هو تعديل شيء واحد صغير: وصف المنتج الخاص بك. يتطلب الأمر إجراء بضع تعديلات إستراتيجية فقط لتتمكن كلماتُك من اصطياد متصفحي موقعك بطريقة سحرية (بما يحقق مصالحهم). باستخدام الأدوات المناسبة، يمكنك إبقاء العملاء مركِّزين على موقعك الإلكتروني لساعات. لنبدأ بالحديث عن أهم الأفكار التي تساعد على تحقيق ذلك. لماذا يجب التركيز على وصف المنتجات؟ ينجذب الكثير من المتسوقين إلى الصور (كما في إنستجرام) والأسعار، لكن في حال كان الوصف غير موافق لما في أذهانهم، فسرعان ما يتلاشى هذا الانجذاب. أوصاف المنتجات مهمة جدًا في تشكيل تصورات العملاء التي يتعذر تغييرها في كثيرٍ من الأحيان. ولا شك أن الانطباع الأول يحمل أهميةً أكبر في مجال التجارة الإلكترونية، حيث يمكن أن تكون نقرةٌ واحدة كفيلةً بخسارة العميل. فيما يلي العناصر الأساسية التي يجب أن يتضمنها وصف المنتجات الجيد: عنوان جذاب: لا يكفي مجرد إدراج المسمى العامّ للمنتج، بل يجب وصفه باستخدام كلمات معبرة تجعل القارئ يرغب بشراء ما يعاينه. صورة مصغرة مبهجة للنظر: وهي بمثابة واجهة لمنتجك أمام العملاء المحتملين، لذا فمن الضروري أن تكون واضحةً وجذابة. مواصفات مفصَّلة: نظرًا لأن العميل سينفق المال على منتجك، فيجب أن تزوده بقائمة دقيقة تتضمن خصائص المنتج. وفي حال كان يبحث عن خاصية معينة، فيجب أن يكون قادرًا على معرفة ما إذا كانت موجودةً أم لا من خلال الاطلاع على وصف المنتج. عناصر مرئية وتنسيقات مثيرة للاهتمام: يُجري معظم القراء مسحًا سريعًا للصفحة (خلال 5-10 ثوانٍ في معظم الأحيان)، وتساعد العناصر المرئية على جذب انتباههم، وهنا تتجلى أهمية ترتيب موضع النص بعناية، كما أن استخدام جداول المقارنة والمعلومات المصورة (الإنفوجرافيك) تساعد الجمهورَ المستهدف على الحصول على المعلومات التي يبحثون عنها بصورة أسرع. تحتاج إلى إشغال العملاء بوصف المنتجات لتحفيزهم على شراء منتجاتك، وهو ما يسمى "خطة التسويق الإستراتيجية". ما هي ملامح وصف المنتجات الجيد؟ لا شك بأن وصف المنتجات المكتوب بطريقة جيدة عنصرٌ مهم في أي خطة تسويق إستراتيجية، لذا يجب إدراج وصف المنتج في برنامج تسويق المشروع التجاري، كما يجب أن يصبح جزءًا من الأهداف العامة والأساليب التسويقية للمشروع. وفيما يلي مجموعة من النصائح لإبراز وصف منتجاتك والمساعدة على تحقيق أهدافك التسويقية. اعرف جمهورك المستهدف يُعَد تحديد الجمهور مرحلةً ضروريةً من الإستراتيجية التسويقية قبل البدء بكتابة وصف المنتجات. تساعد الأسئلة التالية على تحديد الجمهور المستهدف: كيف يعثر الأشخاص أو الشركات على صفحة التجارة الإلكترونية الخاصة بك؟ ما هي اهتماماتهم؟ ما الذي يجذبهم في متجرك الإلكتروني؟ كيف يصف العملاء أو الشركات منتجَك للأصدقاء؟ ما هي خصائص المنتج الأكثر جذبًا للأشخاص أو الشركات؟ من الأمثلة على التسويق الإستراتيجي الناجح منتجات مصابيح ملح الهيمالايا الطبيعي من موقع The Salt Lady، التي يتجلى فيها الإدراك الحقيقي لجمهورهم المستهدف، ويتضح ذلك من وصف منتجاتهم. في الصورة وصف لمنتجات مصابيح ملح الهيمالايا من موقع The Salt Lady. يركز الوصف على جميع الميزات والفوائد الصحية التي تتمتع بها هذه المصابيح، والتي تجذب الجمهور المستهدف، مع ذكر الأبعاد المادية، بالإضافة إلى نصائح حول الاستخدام الأمثل. يستهدف التسويق الإستراتيجي لهذه المنتجات الأشخاصَ الذين يبحثون عن نوم أكثر راحة، وذلك من خلال تحسين أسلوب الحياة من جميع النواحي؛ كما يتضمن جمهورهم المستهدف الأشخاصَ الذين يرغبون بتخفيف الصداع والقلق الناجمَين عن استخدام المنتجات الإلكترونية في الحياة اليومية. أجرى أصحاب منصة The Salt Lady أبحاثهم وطوروا إستراتيجيةً تسويقيةً تراعي الخصائص التي تهمّ شخصية المشتري لجمهورهم. يعني ذلك أنه يتعين عليك تركيز الاهتمام على فوائد منتجك. توضح النصيحة التالية كيفية تحقيق ذلك واكتساب ميزة تنافسية في عملية التسويق. ركز على فوائد المنتج يعتقد الكثير من أصحاب المشاريع التجارية أن منتجاتهم تتفوق بخصائصها على جميع المنتجات الأخرى في السوق. صحيح أن خصائص المنتج هي المفتاح لكتابة وصفه، لكن يجب أن يعرف المشتري ما الذي يمكن أن يقدمه المنتج له عمليًا. قد تكون لديك أحدث الخصائص وأعلاها جودةً، لكنها مجرد تفاصيل غير مهمة بالنسبة لمعظم العملاء، إذ يهتم العملاء بما يمكن أن يقدمه المنتج لهم، لذا يجب وصف ما يفعله المنتج بدلًا من ذكر مواصفاته. سواء كان المنتج يتعلق بتوفير الراحة، أو الأناقة، أو الملحقات المساعِدة، فلا بد أن تكون هناك غاية من شراء هذا المنتج، ولا تكون الخصائص الفردية للمنتج ذات أهمية إلا بقدر ما تقدّمه من قيمة فعلية للمشتري. يشتري العملاء المنتجات من أجل الفوائد التي تقدمها، لذا فإن الوصف الجيد يجب أن يراعي التوازن الصحيح بين الخصائص والفوائد الفعلية. في المثال التالي لإحدى إستراتيجيات التسويق، قد يكون وصف المنتج طويلًا بعض الشيء، لكنه يدمج بين الفوائد والخصائص بطريقة مثالية. يوضح هذا الوصف كيف أن المنتج (وهو منظِّف حمض الجليكوليك) عبارة عن مقشر فعال، وكيف يزيل الأوساخ والزيوت وآثار مساحيق التجميل. هذه هي أهم الأمور التي يريد الجمهور المستهدف سماعها، والتي لا يمكن التقليل من أهمية أي منها. يتضمن وصف المنتج تعريفًا بالمادة الفعالة وآلية عملها مع ذكر فوائدها الفعلية للبشرة والتأكيد على تقييمها من قبل المختصين، وهو بذلك يستوفي جميع النواحي المطلوبة. اجعل أوصاف المنتجات قابلة للقراءة السريعة وجدت دراسة حول سهولة استخدام الويب أن 16% فقط من الناس يقرؤون المحتوى كلمةً بكلمة، مقابل 79% يُجرون مسحًا سريعًا للصفحة. بعد أن أنفقتَ المال والوقت في إنشاء المحتوى، لا بد أنك بحاجة لاستهداف هؤلاء الـ 79%، وهم عبارة عن أشخاص يبحثون عن معلومات محددة بأقصر وقت ممكن؛ لذلك ينبغي مراعاة ما يلي في وصف المنتجات: استخدام جمل قصيرة: لستَ بصدد كتابة ورقة بحثية، لذا يجب أن يكون الوصف مختصرًا وسهل القراءة. استخدام النقاط: اختر الفوائد التي ترغب بإبرازها ورتبها حسب الأهمية، فهذا يضمن اطلاع القرّاء على الفوائد الأساسية قبل الانتقال إلى مكان آخر. استخدام المساحة البيضاء: لا يفضل الكثير من الناس قراءة فقرات نصية طويلة، لذا يجب الاهتمام بتخطيط الصفحة واستخدام المساحات البيضاء. إضافة مقاطع فيديو قصيرة للمنتج: أظهرت الدراسات أن نسبة 7 من كل 10 أشخاص يرون العلامات التجارية أفضل بعد مشاهدة مقاطع فيديو للمنتجات. تساعد هذه الطريقة على تقليل كمية الكتابة النصية وإيصال معلومات كثيرة خلال دقائق قليلة. عزز المصداقية من خلال الدليل الاجتماعي يزداد احتمال شراء الناس للمنتجات إذا أوصى بها صديق أو قريب لهم أو أحد الأشخاص على وسائل التواصل الاجتماعي، وهو أمر بسيط نسبيًا في التسوق التقليدي، لكنه يصبح أكثر تعقيدًا في التسوق عبر الإنترنت. تنص المجلة الدولية لإدارة المعلومات على أن التوصية بالمنتجات عبر الإنترنت OPR تساعد على تحسين تجربة التسوق للعملاء. يساعد ذلك أيضًا على تشجيع بناء علاقات طويلة الأمد. عندما يكون المستهلك في حيرة من أمره حيال ما يشتريه، فمن المرجح أن يختار المنتج بناءً على عدد المراجعات الإيجابية، وقد أظهرت دراسة حديثة أن الناس يقرؤون وسطيًا عشر مراجعات -سواءً على صفحة المنتج أو عبر وسائل التواصل الاجتماعي- قبل أن يقرروا شراء المنتج من متجر إلكتروني؛ كما بينت هذه الدراسة أن 73% من الناس يهتمون بالمراجعات التي أضيفت في الشهر الأخير فقط. احرص على السماح للمشترين بترك مراجعاتهم عند وضع خطةً للتسويق ووصف المنتجات، إذ ينعكس تأثير ذلك على المشترين اللاحقين. وفيما يلي العناصر التي يجب إضافتها في وصف المنتجات. شجع المشترين على كتابة مراجعاتهم. كلما ازداد عدد المراجعات المضافة، كانت هذه المراجعات أكثر موثوقية. احرص على توثيق المراجعات لإثبات مصداقيتها، ويمكن التحقق من المراجعات الحقيقية من خلال موقع Fakespot.com. عدد النجوم التي حصلتَ عليها مؤشرٌ مهم يعكس مدى رضا العملاء عن منتجك. خاتمة وصف المنتجات هو واحد من العناصر المهمة جدًا لزيادة المبيعات عند إعداده بطريقة صحيحة؛ وذلك من خلال معرفة الجمهور المستهدف، والتركيز على الفوائد العملية للمنتج، وتخطيط الصفحة بما يسمح بالقراءة السريعة، والتمكين من إضافة المراجعات لمعرفة تقييم الناس الذين سبق أن اشتروا المنتجات. يمكنك أيضًا الاستعانة بخبراء التسويق للحصول على مساعدة بشأن كيفية إعداد وصف منتجات ناجح يرقى بسويّة عملك. ترجمة -وبتصرّف- للمقال Expert Strategic Marketing Tips to Increase Conversions With Your Product Descriptions لصاحبته Katrina McKinnon. اقرأ أيضًا نصائح لتطوير صفحات المنتج في متجرك الإلكتروني تبني المنتج: الطريق إلى إقبال العملاء على منتجك كل ما يجب أن تعرفه عن أنواع المنتجات وإدارة تطويرها كيفية عمل مواقع الويب ذات معدل التحويل العالي
  21. حسنا إذن سؤالك ليس عاما حول برمجة الألعاب، بل ما تريده تحديدا هو برمجة Roblox باستخدام لغة البرمجة Lua. على حد علمي، لا توفر أكاديمية حسوب حاليا دروسا للغة البرمجة Lua لأن الدروس المتوفرة على الأكاديمية معظمها ليست موجهة لتطوير الألعاب، ولأن Lua مشهورة أكثر في برمجة الألعاب. يمكنك أن تبحث في الأنترنت عن "تعلم البرمجة بلغة Lua" ورؤية النتائج التي تظهر لك. بعدما تصل إلى مستوى جيد في هذه اللغة أعتقد أنه لن يكون من الصعب عليك استخدامها في الألعاب التي تريد.
  22. من ناحية تصميم شخصية وتحريكة يعتبر بسيط بنسبة لي لأنني لن استخدم unity او UE h او godot سوف استخدمRoblox Studio فهو محرك سهل وجميل ومن ناحية الأنميشن فهو شي بسيط فيه ولاكن الشي اكثر تعقيد بنسبة لي هو لغة برمجة luau لا اعرف من اين اتعلمها
  23. من الجميل جدا أن يكون لديك هذا الحماس لتعلم البرمجة من هذه السن. هذا شيء رائع. لا أعتقد أنه يوجد سن محدد لا يمكن تعلم البرمجة قبله، فهذا الأمر يختلف من شخص إلى آخر. من الوارد جدا أن تستطيع تعلم البرمجة بلغة JavaScript حتى وأنت في هذا السن وأن تسبق من هم أكبر منك سنا. لذلك أرى أن الاشتراك في تلك الدورة ليس مشكلة، خصوصا أن الدورة سوف تبقى متاحة لك فيما بعد ويمكنك الرجوع إليها بعد سنوات، وسيتم أيضا تحديثها من فترة لأخرى لتوافق متطلبات السوق. أما بخصوص الدفع، فمادام والداك موافقان فهما من سيتكفلا بشراء الدورة، لأن المعاملات المالية تشترط كون الشخص وصل إلى عمر 18 سنة على الأقل.
  24. السن مناسب لا مشكلة، فالحد الأدنى هو ما بين 8 إلى 11 سنة، لكن دورة جافاسكريبت دورة متقدمة بعض الشيء، ستحتاج أولاً إلى دراسة دورة علوم الحاسوب. وكحل وسط، تستطيع تعلم أساسيات البرمجة من خلال سكراتش من أي مصدر، ثم الإشتراك بدورة جافاسكريبت هنا بالأكاديمية. ستجد تفصيل هنا:
  25. عمري ١٠ سنين واريد ان أشارك في دورة الجافا سكريبت وامي وابي يريدون هذا أيضا فهل عمري مناسب لهذه الدورة؟
  26. بالفعل، تعلم البرمجة شرط أساسي لبرمجة الألعاب ولأي نوع آخر من البرامج. لكن أصارحك القول بأن تطوير الألعاب هو مجال متقدم من مجالات البرمجة وليس شيئا يتم تعلمه من البداية. تطوير لعبة متكاملة، بالإضافة إلى التكمن التام من لغة برمجة معينة، سوف يتطلب استخدام مكتبة تعمل كمحرك ألعاب، مثل Unity أو Unreal Engine أو Godot. في هذا المجال، سوف تحتاج أيضا تمكنا من بعض أمور الفيزياء، مثل قوانين الحركة والسرعة والجاذبية. وتحتاج أيضا إلى معرفة بتصميم الأشياء والشخصيات وتحريكها بشكل صحيح. هذا أمر يحتاج إلى كثير من المعرفة والعمل وقد لا يكفي فيه شخص واحد، لذلك غالب الألعاب لا يطورها شخص واحد بل فريق متكامل، بحيث يتخصص كل شخص في مهمات معينة. بالإضافة إلى كل الأمور التقنية، اللعبة تحتاج قصة و قوانين تسير عليها من أجل إمتاع اللاعب، وهذا أمر إضافي يجب صياغته بشكل جيد قبل البدء في برمجة اللعبة. الدورات التي تقدمها أكاديمية حسوب عموما، ودورة علوم الحاسوب خصوصا لا تدخل إلى مجال الألعاب نظرا لأن هذا المجال معقد كما شرحت ولا يصلح لتعليم المبتدئين في البرمجة. لكن بعد التمكن في تلك الدورات يمكنك بعدها التخصص في برمجة الألعاب إن شئت.
  1. عرض المزيد
×
×
  • أضف...