Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...
Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...
رقم الخبر | عنوان الخبر | التفاصيل |
---|---|---|
72,862 | سوني تستحوذ على شركة تطوير الألعاب Valkyrie Entertainment |
أعلنت شركة سوني عن خامس استحواذ لها هذا العام لتعزيز قسم الألعاب، وذلك من خلال الاستحواذ على شركة تطوير الألعاب Valkyrie Entertainment الأمريكية، بعد شراكة طويلة بين الشركتين. تأسست شركة Valkyrie Entertainment، وهي عبارة عن استديو تطوير ألعاب بالتشارك، حيث يرتكز عملها على تطوير الألعاب بالتعاون مع الشركات الأصلية، فمثلًا عملت على تطوير لعبة God of War مع سوني مؤخرًا، وسبق لها تطوير ألعاب أخرى مع مايكروسوفت مثل Halo Infinite، وكذلك مع Riot. تسعى سوني من خلال هذا الاستحواذ إلى تعزيز مكانتها في قطاع الألعاب؛ حيث يُعد القطاع الوحيد تقريبًا الذي يحقق النمو سنويًا بشكل ملحوظ على عكس القطاعات الأخرى مثل الأجهزة الإلكترونية والهواتف. وتأتي هذه الخطوة في ظل النمو الهائل الذي يشهده قطاع الألعاب في السنوات الماضية. المصدر: التدوينة سوني تستحوذ على شركة تطوير الألعاب Valkyrie Entertainment ظهرت أولاً على عالم التقنية. |
72,827 | لعبة PUBG: Battlegrounds ستتوفر للعب بشكل مجاني بداية العام المقبل |
بعد نحو 5 سنوات من إطلاق لعبة ببجي التي غيرت نظرة المستخدمين وعشاق الألعاب في العالم لاسيما ألعاب إطلاق النار، ستتوفر مع نسخته الأصلية PUBG: Battlegrounds بشكل مجاني للعب بداية العام المقبل. سيتمكن الجمهور منذ 12 يناير 2022 من لعب ببجي PUBG: Battlegrounds دون رسوم لنسختها الأساسية بعد أن كانوا بحاجة للاشتراك في اللعب ليتمكنوا من تجربتها. وستوفر الشركة اشتراك باسم Battlegrounds Plus يسمح للمستخدمين الحصول على مزايا إضافية والدخول لأوضاع وتصنيفات مختلفة مقابل 13 دولارًا أمريكيًا، ما يعني أن الاشتراك سيقتصر على الذين يرغبون بشراء بعض المزايا الإضافية والأسلحة في داخل اللعبة والحصول على على 1,300 عملة نقدية “كوين” عند تفعيل الاشتراك لأول مرة، كما سيوفر هذا الاشتراك إمكانية الاستمتاع بوضع “Ranked Mode” الذي يسمح للاعبين باللعب مع آخرين من نفس المستوى. تُمثل هذه الخطوة بُشرى سارة للذين يرغبون بتجربة اللعبة أو الذين يلعبونها مسبقًا للاستمتاع بها مجانًا، وكذلك عند توجههم للحصول على مزايا إضافية احترافية مقابل اشتراك واحد. يُشار أن شركة Krafton الكورية الجنوبية، الناشر الرسمي للعبة PUBG: Battlegrounds، تمكنت من بيع أكثر من 75 مليون نسخة حتى الآن، وتحظى لعبتها بتفاعل قوي جدًا على مختلف المنصات مع وجود متوسط لاعبين في نفس الوقت بما يزيد 146 ألف لاعب – وهي نسبة تتضاعف في أوقات الذروة يوميًا. المصدر: التدوينة لعبة PUBG: Battlegrounds ستتوفر للعب بشكل مجاني بداية العام المقبل ظهرت أولاً على عالم التقنية. |
72,803 | كيفية إصلاح خطأ الخادم الداخلي 500 Internal Server Error في مواقع ووردبريس |
يظهر الخطأ Internal Server Error 500 على كل صفحات موقعك عندما تحصل مشكلة في الخادم أو ملفات النظام، وغالبًا تكون المشكلة في المسار الجذر للموقع، حيث توجد ملفات ووردبريس، ولكن يُمكن أن تكون المشكلة بسبب خادم استضافتك. يُعَد هذا الخطأ واحدًا من أكثر الأخطاء التي تتعب أصحاب مواقع ووردبريس، وذلك لعدم وجود حل واضح وصريح له، وبالتالي يتطلب الكثير من البحث لمعرفة جذر المشكلة، ويستهلك هذا وقتك وصبرك في آن واحد. سوف نحاول تبسيط الأمر عليك من خلال اقتراح مجموعة من حلول هذه المشكلة وشرح آلية تطبيق كل واحدة. كيفية إصلاح الخطأ Internal Server Error 500 في موقع ووردبريسيجب عليك أولًا اتخاذ بعض الإجراءات الوقائية، حيث تتطلب الحلول المطروحة الكثير من التعديل على ملفات الموقع. أخذ نسخة احتياطية عن الموقعكما ذكرنا سابقًا، فإن هذه الحلول تتطلب الكثير من التعديل على ملفات الموقع ضمن المسار الرئيسي لملفات ووردبريس، لذلك يُنصح بشدة بأخذ نسخة احتياطية عن ملفات الموقع قبل الإقدام على أي خطوة في حال حدوث أمر غير متوقع. استخدام برنامج FTP Clientإن كنت تملك خبرةً مُسبقةً في استخدام برنامج FTP Client، فستستطيع تخطي هذا القسم، لأننا سوف نشرح آلية تثبيت هذا البرنامج، حيث تتطلب أغلب الحلول استخدامه. يسمح لك برنامج FTP Client بالدخول والتعديل على ملفات موقعك الإلكتروني. صحيح أنك تستطيع استخدام مدير الملفات الذي تُوفره الاستضافة، لكن استخدام برنامج FTP Client أسهل بكثير. توجد العديد من برامج FTP Client، ولكننا سوف نستخدم برنامج FileZilla في هذا المقال. توجه إلى صفحة FileZilla الرئيسية لتنزيل برنامج FileZilla Client، ثم اضغط على زر التنزيل الأخضر في حال قدم الموقع توصية بالإصدار المناسب لنظام تشغيل حاسوبك؛ أما في حال لم يظهر لك هذا الخيار، فاضغط على Show Additional Download Options الموجود تحت الزر الأخضر، ونزِّل الإصدار المناسب لنظام تشغيل حاسوبك. افتح البرنامج حالما تنتهي من تنزيله لبدء تثبيته على حاسوبك، وبعد الانتهاء شغل البرنامج، واضغط على File ثم Site Manager، بعد ذلك اختر New Site، وأدخل اسم موقعك الإلكتروني. اضبط الإعدادات التالية:
استخدم اسم المُستخدم وكلمة المرور التي تستعملها للدخول لمدير الملفات في الاستضافة؛ أما إن كانت الاستضافة تستخدم لوحة تحكم cPanel، فعندها استخدم معلومات تسجيل الدخول الخاصة بها، وإن لم تكن متأكدًا من معلومات الدخول، تستطيع سؤال فريق دعم الاستضافة. توجه إلى Transfer Settings، وفعّل خيار Limit Number of Simultaneous Connections. اضبط الرقم الأعظم للاتصال لـ 8، حيث تمنع هذه الخطوة الخادم من حجب عنوان IP الخاص بك، ثم اضغط على Connect للاتصال بالخادم. وبهذا تكون قد أصبحت جاهزًا الآن لتطبيق الحلول المختلفة لمشكلة Internal Server Error 500. الحلول الشائعة للخطأ Internal Server Error 500إن السببين الشائعين لهذا الخطأ هما تلف ملف htaccess. وتجاوز حد ذاكرة PHP للخادم، ويُمكن أن يتلف ملف htaccess. بعد تثبيت إضافة أوعمل تغيير آخر على موقع ووردبريس، والحل البسيط لهذه المشكلة هو بإنشاء ملف htaccess. جديد. تظهر مشاكل تجاوز حد ذاكرة PHP بسبب الكتابة الضعيفة لشيفرة إحدى الإضافات التي تعمل على موقعك، أو بسبب نمو موقعك مع الوقت واستخدامه للعديد من الإضافات، ليبدأ بعدها بتجاوز حد الذاكرة المُخصص له من قِبل الاستضافة. سوف يظهر الخطأ Internal Server Error 500 عند تحقق أحد الأسباب السابقة. سوف نعلمك كيف تُنشئ ملف htaccess. جديدًا، وتتخلص من النسخة التالفة القديمة، وتختبر ما إذا كان موقعك يتجاوز حد ذاكرة PHP المُخصص له. إنشاء ملف htaccess. جديدافتح المسار الجذر (يكون عادةً public_html) لملفات تثبيت ووردبريس باستخدام FileZille أو برنامج FTP Client الذي تفضله. إذا رأيت مجلدات تُدعى wp-content وwp-admin، فيعني هذا أنك في المكان الصحيح، وإن كنت لا تستطيع العثور على ملف htaccess. أو أي ملف يبدأ اسمه بنقطة، فعندها يجب عليك إظهار هذه الملفات من خلال الضغط على خادم Server، ثم تفعيل خيار Force Showing Hidden Files. انقر بزر الفأرة الأيمن على ملف htaccess. حالما تجده، وأعد تسميته إلى htaccess.bak.، وذلك لحذف ملف htaccess. التابع للموقع. بعد ذلك أنشئ ملفًا جديدًا من خلال التوجه إلى لوحة تحكم ووردبريس، والضغط على تبويب الروابط الدائمة ضمن قسم الإعدادات، ثم توجه لأسفل الصفحة واضغط على حفظ الإعدادات، وبهذا سوف يُنشئ ووردبريس ملف htaccess. جديدًا. أعد تحميل الموقع، فإذا اختفى الخطأ Internal Server Error 500، فسيعني هذا أن السبب كان تلف ملف htaccess. وحُلّت المشكلة الآن، ولكن إن ظهر الخطأ مرةً أخرى، فعندها يتوجب عليك إجراء المزيد من الاختبارات لمعرفة سبب المشكلة. اقتباسملاحظة: تُسبب التسمية الخاطئة لهذا الملف ظهور خطأ كهذا، لذلك تأكد أن اسمه htaccess. وليس أي شيء آخر. زيادة حد ذاكرة PHP في ووردبريسيُضبط حد ذاكرة PHP في ووردبريس من قِبل الاستضافة، وسوف يحاول ووردبريس زيادة هذا الحد عند بدء تجاوزه، لكنه لا يستطيع تجاوز الحد الذي تفرضه الاستضافة على الخادم، ويكون هذا الحد عادةً منخفضًا في الاستضافات المشتركة، لذلك عليك رفع هذا الحد في ووردبريس وإعادة تحميل الموقع للتأكد أنه هو سبب ظهور الخطأ Internal Server Error 500. افتح مسار تثبيت ووردبريس، وحدد مكان الملف wp-config.php، ثم انقر عليه بزر الفأرة الأيمن، واختر Download، ثم افتحه باستخدام محرر النصوص المُفضل لديك، وأضف له التعليمة التالية: define('WP_MEMORY_LIMIT', '64M');احفظ الملف، ثم أعد رفعه لمسار تثبيت ووردبريس، واستبدله بالملف الموجود ضمن الخادم، ثم أعد تحميل أي صفحة ضمن الموقع، فإذا اختفى الخطأ، فسيعني هذا أن السبب هو الذاكرة المحدودة؛ أما إذا بقي الخطأ، فهذا يعني أن المشكلة غير مرتبطة بالذاكرة، وتستطيع إزالة السطر السابق من ملف wp-config.php، وإعادة رفع الملف لمسار تثبيت ووردبريس مرةً ثانيةً، واستبدال الملف السابق. لا تفرح كثيرًا إن اختفى الخطأ، فما زال عليك تنفيذ بعض الخطوات، لأنك بزيادة حد الذاكرة تُصلح المشكلة السطحية، وليس السبب الرئيسي للمشكلة، فعند زيادة حد الذاكرة، لن يعني هذا أن الموقع لن يتجاوز هذا الحد مرةً أخرى، لذلك الحل الصحيح هو البحث عن سبب الاستهلاك المتزايد للذاكرة والتخلص منه. تستطيع متابعة القراءة لمعرفة الأسباب والحلول الأخرى لهذه المشكلة، ولكن عندما يستمر هذا الخطأ في الظهور، سوف تُضطر لرفع مواصفات الخادم. الحلول الأقل استخداما للخطأ Internal Server Error 500إصلاح تلف ملف htaccess. ورفع حد ذاكرة PHP هما الحلان الأكثر استخدامًا لإصلاح الخطأ Internal Server Error 500، ولكن هناك حلولًا أخرى في حال لم يعمل الحلّان السابقان، سوف أذكر منها التالي:
إن كنت تستطيع الدخول للوحة تحكم ووردبريس، فيعني هذا أنك تستطيع تعطيل هذه الإضافات واحدةً تلو الأخرى، وبعد تعطيل أي إضافة، جرب تحميل صفحات الموقع من جديد لتتأكد مما إذا عادت للعمل أم لا، وبالتالي إذا اختفى الخطأ، فإن المشكلة ناتجة عن الإضافة التي عطلتها قبل تحميل الصفحة من جديد. ابحث عن بديل لهذه الإضافة واحذفها إن كانت وظيفتها مهمةً بالنسبة لموقعك، ولكن إن كنت تظن أنك لا تستطيع استبدالها أو الاستغناء عنها، فعندها عليك التواصل مع مطور الإضافة مباشرةً وطلب إصلاحها، وتستطيع عمل هذا من خلال استمارة الدعم للإضافة على موقع wordpress.org، ولكني أنصح بالبحث إن كان يتوفر دعم في مكان آخر حتى لو كان بمقابل مادي. أما إن كنت لا تستطيع الدخول للوحة تحكم ووردبريس، فعندها عليك استخدام FTP Client، والتوجه للمسار الرئيسي لتثبيت ووردبريس، والدخول لمجلد wp-content. يحتوي هذا المجلد على الإضافات والقوالب ومجلدات أخرى. أعد تسمية مجلد الإضافات plugins لأي اسم آخر مثل plugins_old، حيث يُعطل هذا جميع الإضافات على موقعك، وبعد ذلك أعد تحميل صفحات الموقع وتأكد إن كان الخطأ اختفى، وفي حال اختفاء الخطأ، أعد تفعيل الإضافات واحدةً تلو الأخرى، مع إعادة تحميل صفحات الموقع في كل مرة للكشف عن الإضافة التي تُسبب هذا الخطأ، ولا تنسَ إعادة اسم المجلد إلى plugins. تصحيح أخطاء موقع ووردبريسإن كنت تواجه مشكلةً في تحديد سبب الخطأ الذي يظهر على موقعك، فتستطيع استخدام ميزة كشف الأخطاء الموجودة في ووردبريس. عليك استخدام الشيفرة كما هو مُوضح في الصورة ضمن ملف wp-config.php، وذلك لتفعيل وضع تصحيح الأخطاء في ووردبريس ضمن موقعك. سوف تُسجل جميع الأخطاء ضمن ملف في مسار تثبيت ووردبريس، لذلك إن كنت مطوِّرًا، فستستطيع البدء بتصحيح أخطاء موقعك، لكن إن كنت مالكًا للموقع ومتوسط الخبرة، فالأفضل ألا تخوض في هذا الأمر، لأنك لن تفرق بين الأخطاء البسيطة التي يُمكن تجاهلها والأخطاء التي يجب إصلاحها. لا يُفضل استخدام هذه الميزة في موقع يعمل وله زُوار، لأنها تكشف شيفرةً برمجيةً أمام الزوار، لكن في بعض الأحيان تكون هي الطريقة الوحيدة لمعرفة سبب الخطأ 500 Internal Server Error ضمن موقعك. التحقق من صلاحيات الملفات) يجب التحقق من صلاحيات الملفات مع أنها نادرًا ما تكون المشكلة، حيث تُعطى الصلاحية 755 للمجلدات و644 للملفات ضمن مسار تثبيت ووردبريس، ويُحتمل أن يُسبب تغيير هذه الصلاحيات لقيم مُختلفة الخطأ 500 Internal Server Error. ادخل للمسار الرئيسي لتثبيت ووردبريس باستخدام برنامج FTP Client مثل FileZilla، وألقِ نظرةً على عمود الصلاحيات إن كان مُختلفًا عما ذُكر سابقًا. رفع نسخة جديدة من مجلدي wp-admin وwp-includes لموقعكيجب عليك اللجوء لهذه الخطوة بوصفها ملاذًا أخيرًا، وذلك بعد استخدام جميع الخطوات السابقة دون نتيجة، لذلك قبل رفع هذه المجلدات عليك أخذ نسخة احتياطية عن ملفات موقعك، ثم رفع نسخة جديدة من ملفات ووردبريس. عليك فك ضغط الملفات من ملف zip، ثم فتح المجلد بعد فك ضغطه. افتح مسار تثبيت ووردبريس لموقعك من خلال برنامج FTP Client، ثم عليك رفع مجلدات wp-admin وwp-includes من مجلد ووردبريس الذي فككت ضغطه إلى مسار موقعك، وذلك لاستبدال النسخ القديمة من المجلدات وما تتضمنه من ملفات. أعد تحميل موقعك من المتصفح، فإذا اختفى الخطأ، فيعني هذا أن السبب ملف معطوب، لكن إن استمر بالظهور، فليس لديك خيار آخر سوى الاتصال بفريق دعم الاستضافة وطلب المساعدة. التواصل مع فريق دعم الاستضافةإن جربت جميع الطرق السابقة ولم تستطع حل المشكلة، فمن المُحتمل أن يتمكن فريق دعم الاستضافة من مُساعدتك، لكن من المهم أن تُجرب الطُرق السابقة لتتأكد أن المشكلة ليست من مسار ووردبريس. تختلف جودة الدعم الذي سوف تتلقاه من استضافة لاستضافة أخرى، لكن سوف تُلقي معظم الاستضافات باللوم على ملفات موقعك بدلًا من اتهام خوادمها، وهذا مُحتمل، حيث تتسبب العديد من القوالب والإضافات في العديد من المشاكل المشابهة، والتي لا يستطيع فريق الدعم التدخل لحلها إلا بتعطيل الإضافة أو القالب. من المهم تجريب جميع الخطوات السابقة لكي تتمكن من إخبار فريق الدعم بالخطوات التي جربتها ولم تحل المشكلة، وذلك لتؤكد لهم أن المُشكلة ليست من ملفات موقعك. اطلب منهم بلطف التحقق من سجلات الخادم لمعرفة إن كانت المُشكلة تظهر هناك. الخلاصةلا يوجد سبب واضح للخطأ 500 Internal Server Error، لذا يحتاج للكثير من عمليات البحث عن سبب المشكلة لتتمكن في الأخير من إصلاحه. نتمنى أن تكون الخطوات السابقة واضحةً كي تتمكن من تطبيقها بنفسك لإصلاح المشكلة، لكن قبل أن تبدأ بأي خطوة عليك أخذ نسخة احتياطية عن الموقع، والأفضل هو أخذ نسخة احتياطية عن الموقع دوريًا، كي تتمكن من إصلاح الموقع بالعودة لنسخة حديثة عن الموقع لا تتضمن هذا الخطأ، كما يجب عليك التأكد من مطور الإضافة أو القالب من أن التحديث متوافق مع نسخة ووردبريس على موقعك ولن يتسبب في أي مشكلة، ومن المُهم جدًا تحديث الاستضافة دوريًا، حيث تظهر أغلب هذه المشاكل في الاستضافات المُشتركة منخفضة التكلفة. ترجمة -وبتصرّف- للمقال How to Fix the 500 Internal Server Error on Your WordPress Website لصاحبه Lyn Wildwood. اقرأ أيضًا |
72,802 | هل فات الوقت على تعلّم البرمجة؟ |
لا أعلم عزيزي القارئ كم هو عمرك بالضبط، لكن ما دمت لم تتجاوز الخمسين فجوابي هو لا لست كبيرًا على تعلم البرمجة (وحتى لو كنت تجاوزتها فعلى الأرجح أنك لست كبيرًا على التعلم كذلك). في الوقت الحالي على ما يبدو الجميع يقوم بالبرمجة بدءًا من الأطفال الصغار الذين يتعلمونها في المدارس أو في كثير من الألعاب مثل لعبة ماين كرافت Minecraft الشهيرة، وصولًا إلى المراهقين والبالغين كذلك الذين يتوفر لديهم المحتوى الغني المناسب لكلٍ منهم إما في الألعاب الالكترونية أو الدورات التدريبية ومقاطع اليوتيوب، باختصار هناك الكثير من المحتوى لنتعلم منه. سؤالنا الرئيسي هو : ما هو العمر الذي يعتبر الشخص فيه كبيرًا على تعلم البرمجة؟ هل هي للصغار فقط؟ أو يمكن لمن هم أكبر سنًا أو لمن يرغبون بتغيير مسار وظيفتهم أو الحصول على ترقية تعلمها ؟ لا تقلق خذ الخطوة الأولى فحسب ..الكثير منا يسيرون بمساراتٍ وظيفية كانت منطقية بالنسبة لهم في وقتٍ ما، ومع مرور الوقت قد تتغير الظروف وتتبدل القناعات، فتتحول المحبة إلى نفور والشغف إلى ملل تام!، ويصبح غير مجدٍ ولا عملي ما كان مناسبًا لنا في فترة سابقة فندخل في فترة ركود وسكون. بغض النظر عن الحالة التي أنت عليها الآن، أنت تعلم أن تعلم البرمجة هو ما تريد الوصول إليه لكن المشكلة الظاهرة أمامك حالياً هي أن الجميع سبقوك إليها وبدأوا بتعلمها منذ الصغر، و أصبحوا يتقنون العديد من المهارات التي لم تكن تعلم بأنها ممكنة الحدوث!، تشعر بالشيخوخة وأنك غير مرتبط بهذا العالم ومتأخرٌ عن اللحاق بالركب، و بالرغم من كل هذا ترغب بأن تكون مبرمجاً لتطور من نفسك وترتقي في مهنتك. عزيزي القارئ أنت تنظر للأمور من المنظور الخاطئ! لا ينبغي لتركيزك أن يكون منصبًا على العمر، لأنك إن كنت تكره ماتقوم به أو لم تكن تحرز تقدمًا نحو أهدافك ولاتمارس شغفك خلال سير طريقك الحالي، فلا عيب ولا حرج من تصحيح طريقك للوصول إلى المسار الصحيح، إن كان تعلم البرمجة هو المسار الذي تحتاج السير فيه فأقدم ولاتتردد أو تقلق بشأن مسألة العمر فلم يفت الأوان بعد. بدايةً ما هو (الكود) على وجه التحديد؟بكل بساطة هو عبارة عن مجموعة من الأفكار تم ترتيبها بشكل منطقي بطريقة معينة لإنشاء واجهة للمستخدمين ليتفاعلوا معها. عملية البرمجة بحد ذاتها تتطلب مستوى معين من المثابرة و تكريس الذات وبذل المجهود للتعلم الذاتي. هناك الكثير لنتعلمه من خلال البرمجة فهي أكثر من مجرد فهم للأوامر و طريقة الكتابة أو استخدام الخوارزميات، بل تتعدى ذلك إلى ترجمة الأفكار إلى شيء ملموس فكونك مبرمج أنت الأساس الوسيط ما بين صاحب الفكرة الأولية والمُخرج النهائي. كل ما تعمقت في تعلم البرمجة كل ما أدركت أنها أداة لتحقيق غرضك واختلاف لغات البرمجة ما هو إلا إصدارات مختلفة من تلك الأداة. لأنه في نهاية المطاف بإمكانك تحقيق نفس النتائج باستخدام لغات برمجة مختلفة، بالطبع قد تكون لغةٌ ما أكثر ملائمة في مسائل معينة من لغات أخرى، فمع الخبرة و الممارسة ستصل إلى التعرف على نقاط قوة وضعف كلٍ منها ومدى ملائمتها لاحتياجاتك. التعلم في سن كبيرة.. المميزات والعيوبالمميزات المشكلة التي يواجهها العديد من متعلمي البرمجة في سن متقدمة هي مقارنة أنفسهم بالأصغر سنًا والذين يبدو أنهم أكثر تقدمًا منهم في معرفة البرمجة، وقد يعتقدون أن وجود مثل هؤلاء المطورين الشباب يعد خبرًا سيئًا لمن يرغبون بتغيير حياتهم المهنية ، لكن هذا ليس صحيحًا بالضرورة.. في واقع الأمر بصفتك متعلمًا أكبر سنًا أنت تتمتع بميزة عنهم، فلديك العديد من المهارات الحياتية والخبرات العملية التي ستكون حتمًا مساعدًا لك على ترجمة أفكارك إلى أكواد. فمهما كان ما قمت به من عمل حتى هذه النقطة، فقد اكتسبت منه خبرة و مهارة سواء عن طريق تفاعلك مع العملاء أو العمل على مهارات الإدارة أو أي شيء آخر يتطلب نوعًا مختلفًا من المهارات الفنية فكل هذا لن يذهب هباءً. على سبيل المثال: ربما كنت تعمل في مطعم لتقديم البرجر في السنوات الماضية، فأنت بحكم عملك تعرف الكثير من التفاصيل الدقيقة لسير عملية البيع و إعداد الوجبات ومعالجة الطلبات والتعاملات المالية ونحوها، كل هذا الكم من المعرفة بالتأكيد يميزك عن من يفتقدها مِن مَن هم أصغر سنًا منك. الأصغر سنًا ممن لديهم خبرة برمجية قد تصل إلى أعوامٍ لا يملكون ما لديك بالتأكيد، قد يكونون مبرمجين محترفين لكنهم يفتقرون إلى المهارات التي لا يمكن اكتسابها إلا من خلال النضج والخبرة. ربما يكون مجال تخصصك القانون أو التعليم أو العلوم أو العقارات، بالرغم من بعده عن البرمجة إلا أنه مفيد جدًا. ففي وقتنا الحالي في كل مجال يمكنك تخيله هنالك شركة ناشئة تبني منتجًا تقنيًا لهذا المجال، وبالطبع سنراهن على أنهم سيكونون حريصين على وجود من يفهم مجال عملهم بالإضافة إلى المعرفة البرمجية. إذا كانت لديك خبرة في التعامل مع مواقف العمل المعقدة ، أو القدرة على توحيد آراء الفريق، أو التحدث أمام الجمهور، أو معرفة كيفية تحديد أولويات العمل حتى يتم إنجاز المشروع في الوقت المحدد، فإن هذه المهارات ستضعك في مرتبة متقدمة على العديد من المهندسين في صناعة التكنولوجيا. زيادة خبرتك في الحياة تعني زيادة فرص حصولك على المشاريع المميزة والفريدةالشيء الوحيد الذي يحتاجه أي مبرمج هو طريقة لإثبات قدرته على البرمجة. يتم ذلك غالبًا من خلال المشاريع، تُحب الشركات أن ترى أنه يمكنك التعامل مع مشكلة من العالم الحقيقي وإنشاء برنامج لحل هذه المشكلة. ميزتك هنا هي أنك واجهت مواقف حياتية كثيرة. ربما يمكنك إنشاء صفحة ويب للترويج لحدث في مجال عملك، و ربما تكون والدًا جديدًا ويمكنك إنشاء تطبيق لتسجيل أنماط نوم طفلك و عرض البيانات بطريقة تصويرية ملهمة. النقطة المهمة هي أنه يمكنك استخدام خبراتك الفريدة لإنشاء مشاريع غير عادية لن يراها أرباب عملك من أشخاص أقل منك خبرة. مقارنة بالمهن الأخرى ذات الرواتب المرتفعة التي بإمكانك مزاولتها و تطوير مهاراتك فيها، فإن تعلم البرمجة يعتبر سريعًا، وبامكانك تعلمه بشكل كامل من المنزل، و تقوم بتغيير مهني كامل في غضون بضعة أشهر، بينما المهن الأخرى قد تتطلب تدريباً رسميًا طويلًا أو شهاداتٍ معقدة أو حتى سنوات من العودة لمقاعد الدراسة!. بالطبع لا يعني ذلك أن التحول سهل المنال (فتعلم مهارة جديدة والحصول على وظيفة منها قد يكون من أصعب مراحل حياتك)، لكن إن وجدت المتعة في رحلة تعلمك فبالتأكيد سيؤتي عملك الشاق ثماره. العيوب أحد الأمور التي قد تكون عائقًا في رحلة تعلمك هو وقت الفراغ، فقد يكون لديك بشكل أقل من أقرانك الأصغر عمرًا، سواء كنت أحد الوالدين أو مشغولاً بالفعل بمهنة مرهقة في مجال مختلف، ستحتاج إلى العمل بجدية أكبر لتكريس الوقت والمساحة لتعلم البرمجة. فمثلها مثل أي مهارة جديدة يتطلب تعلمها تكريس الوقت والجهد اللازم لتحرز تقدمًا. إذا كان هدفك العمل في مجال التكنولوجيا فيجب عليك قبول فكرة أن من يرأسك أو يتداخل في عملك قد يكون أصغر منك ببضع سنوات وفي بعض الحالات أصغر بعقود!، يمكن أن تكون هذه صدمة إذا كنت آتيًا من بيئة عمل تقليدية حيث يزيد العمر عمومًا مع التسلسل الهرمي للشركات، حاول تقبل ذلك و التعود عليه. و من جانب آخر قد تواجه في بعض الأحيان وبشكلٍ غير ظاهر بعض التحيز ضد سنك، فيميل بعض متخذي القرار الأصغر سنًا إلى توظيف من هم على شاكلتهم. كقاعدة عامة، كلما كبرت الشركة زاد تنوع نطاق الأعمار الذي ستجده فيها. في الشركات الناشئة في الغالب تكون الأعمار متقاربة فهي قد بدأت للتو على يد مجموعة أصدقاء مثلًا، هذا النوع من الشركات يتضمن الكثير من العمل غير المجدول وساعات العمل الإضافية لمحاولة هذه الشركة إطلاق نفسها بمجالها، وقد يصاحب ذلك انعدام الأمن الوظيفي( هل ستستمر هذه الشركة لأكثر من عام؟). يمكن أن يؤدي هذا النمط من العمل وحجم المخاطر إلى جعل مثل هذه الشركات في المراحل المبكرة غير جذابة لكبار السن الذين لديهم التزامات مثل العائلات والرهون العقارية. نصائح أخيرةإذا كنت تستمتع بالبرمجة، فلا يجب أن يكون العمر عائقًا أمام تعلمها أو العثور على وظيفة في مجال التكنولوجيا. إليك ما يمكنك فعله لزيادة فرصك في النجاح: خصص وقتًا للتعلم: سواء كانت استراحة الغداء أو بضع ساعات كل مساء بعد ذهاب الأطفال للنوم، خصص بعض الوقت غير المنقطع في البرمجة. تواصل مع المبرمجين الآخرين في عمرك: تعلم مع صديق، أو ابحث عن أشخاص عبر الإنترنت لمشاركة رحلتك معهم، وجود أقران يفهمون تحدياتك الفريدة سيجعل العملية أكثر إمتاعًا. لا تقارن نفسك بالآخرين: تعلم البرمجة ليس منافسة، وإذا كنت تفكر في الأمر على أنه منافسة فسوف تشعر بالإحباط. ركز على رحلتك الفريدة ونقاط قوتك وأهدافك. استهدف المزيد من الشركات الراسخة: عندما يحين وقت البحث عن وظيفة ، ضع في اعتبارك تركيز جهودك على الشركات الكبرى. هذا لا يعني أنه لا يجب عليك أبدًا العمل في شركة ناشئة ، ولكن عليك فقط أن تدرك أنها توفر قدرًا أقل من الاستقرار ، وقد تواجه مشكلة أكبر في الشعور وكأنك تنتمي إلى تلك البيئة. لا يهم كم عمرك الآن. لا يوجد شيء مثل “التأخير” أو “التقدم في السن” في البرمجة. معظم من هم في المجال في حالة دائمة لتعلم شيء جديد، نحن جميعًا مبتدئون في شيء ما. عندما يكون تعلم البرمجة شرطًا أساسيًا لإحداث تغيير إيجابي في حياتك، فلا يهم في أي عمر تقرر اختياره. لا توجد جائزة كبرى لمن هو أصغر مطور برامج!. الكاتب: نوف المنيف التدوينة هل فات الوقت على تعلّم البرمجة؟ ظهرت أولاً على عالم التقنية. |
72,764 | الخبراء يجيبون.. هل رفض مخترع لقاح بيونتك- فايزر أخذه فعلاً؟ | انتشر على مواقع التواصل الاجتماعي فيديو يدعي أن مخترع لقاح بيونتيك/ فايزر ضد فيروس كورونا البروفسور أوغور شاهين وزوجته وموظفي شركته لم يأخذوا اللقاح. لكن القصة الحقيقية في عدم أخذ شاهين للقاح حينها تعود لسبب آخر. |
72,763 | لا تنمر بعد اليوم ولا مضايقات.. تويتر يحمي المشاهير |
بعد الأزمات الأخيرة التي عصفت بمواقع التواصل الاجتماعي، بشأن الخصوصية واختراق الحسابات وغيرها، توجهت الأنظار لتفادي هذه المشكلات بأسرع وقت. فقد كشف تقرير عن خدمة سرية يقدمها "تويتر" لحماية الشخصيات الهامة والمستخدمين الذين لديهم عدد كبير من المتابعين من أي هجمات. قائمة داخلية"Project Guardian"، الخدمة التي كشف عنها تقرير لوكالة "بلومبيرغ" الأميركية، وهي عبارة عن قائمة داخلية تضم الآلاف من المستخدمين الذين يعتبرهم العصفور الأزرق عرضة أكبر للمضايقات والانتقادات. كما كشف التقرير أن الخدمة تعمل عند تلقي تويتر بلاغا عن منشور مسيء متعلق بحساب ما في القائمة، ليقوم فريق الإشراف على المحتوى في الشركة بالاستجابة فوراً وبسرعة أكبر من جميع التقارير الأخرى التي قام بتجميعها. وأوضحت المعلومات أيضاً أن الخدمة الجديدة تمكّن تويتر من منع انتشار المحتوى الضار، وكذلك الاحتفاظ بمحتوى مغردات بارزة على المنصة مع تقليل أحداث التنمّر. بدوره، اعتبر يويل روث، رئيس قسم سلامة الموقع، أن هناك مجموعة من المستخدمين تشكل قائمة Project Guardian، ولا يتعين على هؤلاء أن يكونوا من المشاهير فقط، بل ستضم القائمة أيضاً مستخدمين عاديين آخرين معرضين للخطر يتم اختيارهم بناء على معايير معينة يحددها الموظفون. كما تمت إضافة مستخدم إلى الخدمة عندما يلاحظ موظفو تويتر أنهم يرون عدداً كبيراً من الرسائل البغيضة، حتى لو لم يدرك ذلك المستخدم، مؤكدة أن من حق الأخير طلب تقديم مزبد من الحماية. طريقة ذكيةيشار إلى أن هذه الخطوة قد لاحقت استحسان الكثيرين عبر المنصة، إذا اعتبر البعض أن تويتر سيكون قادراً على منح كل مستخدم نفس القدر من الدعم الأمني. فيما اعتبرها آخرون طريقة ذكية من المنصة لمنع المصايقات تماماً عبر صفحاتها. |
72,759 | موتورولا تكشف عن هاتف Motorola Edge X30 مع نسخة خاصة بكاميرا تحت الشاشة |
كشفت شركة موتورولا عن أحدث هواتفها الرائدة الذي يتمثل في Motorola Edge X30 مع مزايا قوية، أبزرها خيار وجود كاميرا تحت الشاشة، لتتبع بذلك شركة سامسونج وشاومي وZTE في هذا الأمر. يتميز هاتف موتورولا الجديد بشاشة 6.8 بوصة من نوع OLED وبدقة FHD+، وتوفر معدل تحديث 144 هرتز مع حساسية لمس 567 هرتز، وستدعم الشاشة تقنية HDR10+، ما يجعلها مثالية لتجربة الألعاب والاستخدام باستثناء عدم دعمها لمستشعر البصمة تحتها. وسيضيف الهاتف معالج كوالكوم الجديد Snapdragon 8 Gen 1 الذي تم تصنيعه بدقة 4 نانومتر وأعلنت عنه الشركة في بداية الشهر ليكون أول هاتف في العالم يحمل المعالج الأقوى من كوالكوم، وسيضيف معه ذاكرة رام LPDDR5 بخيارات 8 جيجابايت أو 12 جيجابايت، وسعة من نوع UFS 3.1 بخيارات 128 جيجابايت و256 جيجابايت. وسيحمل هاتف Motorola Edge X30 نظام كاميرا ثلاثي بالخلف مع عدسة رئيسية بدقة 50 ميجابكسل، وأخرى واسعة بدقة 50 ميجابكسل، وثالثة للتصوير القريب والتركيز بدقة 2 ميجابكسل – سيكون بإمكان هذه الكاميرات تصوير فيديو بدقة 8K على سرعة 24 إطارًا في الثانية، بجانب التقاط صورًا واضحة. فيما تتواجد على الجهة المعاكسة كاميرا سيلفي قوية جدًا بدقة 60 ميجابكسل. وبجانب ذلك، سيوفر الهاتف بطارية 5,000 ملي أمبير/ ساعة، وسيدعم تقنية الشحن السريع TurboPower بقوة 68 واط، بإمكانها شحن كامل بطارية الهاتف خلال 35 دقيقة. مزايا أخرى في هاتف موتورولا Motorola Edge X30:
سيتوفر الهاتف الجديد من موتورولا باللونين الأبيض والأسود بداية من الأسبوع القادم بداية من الصين، وبالتحديد في 15 ديسمبر. فيما سيكون متاحًا للشراء بداية من 500 دولار أمريكي تقريبًا للنسخة الأساسية مع 8 جيجابايت ذاكرة و128 سعة تخزين بكاميرا سيلفي تقليدية، وحتى 630 دولارًا أمريكيًا لنسخة 12 جيجابايت رام و256 جيجابايت سعة تخزين بكاميرا تحت الشاشة. المصدر: التدوينة موتورولا تكشف عن هاتف Motorola Edge X30 مع نسخة خاصة بكاميرا تحت الشاشة ظهرت أولاً على عالم التقنية. |
72,751 | غيتس يتوقع جديدا: بعد سنتين ستنقلب طريق العمل والاجتماعات |
قريبا قد تنقلب اجتماعات العمل رأسا على عقب، هذا ما كشف الملياردير الأميركي بيل غيتس. ففي متابعة لما نشره قبل أيام على مدونته الخاصة، توقع مؤسس "مايكروسوفت" أن تحدث جائحة كورونا تغيراً جذرياً ومستمراً في مكان العمل، لاسيما بعد أن عمدت عدة شركات إلى اتباع نظام العمل عن بعد. كما أوضح أن العمل عن بعد لن يؤدي إلا إلى جذب المزيد من الناس إلى تقنية "ميتافيرس"، التي أعلنت عنها شركة "ميتا" (فيسبوك سابقا) مؤخرا. "ميتافيرس" سيكتسح!كذلك، رجح أن تنتقل معظم الاجتماعات الافتراضية، خلال العامين أو الثلاثة القادمة، من شبكات صور الكاميرا ثنائية الأبعاد إلى "ميتافيرس"، وهي مساحة ثلاثية الأبعاد فيها صور رمزية رقمية". واعتبر أن "هذا العرض الشبكي ثنائي الأبعاد، الذي شبهه ببرنامج الألعاب "Hollywood Squares" هو ما نحصل عليه حالياً من معظم منصات اجتماعات الفيديو، مثل "زووم" و"تيمز" التابعة لمايكروسوفت، لكن "ميتافيرس" سيوفر صورة رمزية ثلاثية الأبعاد يمكنها حضور الاجتماعات في مساحة مكتب افتراضية أو وجهة أخرى، حيث يمكن أن تتفاعل مع الصور الرمزية لزملائك في العمل"! كما أوضح قائلا: "الفكرة هي أنك ستستخدم الصورة الرمزية الخاصة بك في النهاية لمقابلة أشخاص في مساحة افتراضية، وكأنك تتواجد معهم في غرفة فعلية، ولكن المستخدمين سيضطرون لارتداء سماعات رأس للواقع الافتراضي أو نظارات واقية للقيام بذلك". إلى ذلك، أعلن غيتس أن شركته تعمل على إضافة صور رمزية ثلاثية الأبعاد وعناصر أخرى متوافقة مع "ميتافيرس" إلى برنامج "تيمز" الخاص بـ"مايكروسوفت"، مرجحا أن يكون لدى "تيمز" مستوى واضحا من الوصول إلى "ميتافيرس" في الوقت الحالي، وذلك بعد أن دخلت شركته في شراكة مع مارك زوكربيرغ وشركته "ميتا" لجعل شبكة "فيسبوك" للتواصل الاجتماعي متوافقة مع "تيمز". |
72,732 | اكتشاف كوكب عملاق يشبه المشتري ولكنه يدور خلافا لما هو شائع | كشفت دراسة حديثة عن وجود كوكب عملاق يشبه كوكب المشتري ويدور خلافاً للمألوف حول نظام نجمي ضخم جدا. ويتألف هذا النظام من نجمين وهو ثقيل الوزن، إذ تبلغ كتلته ستة إلى عشرة أضعاف كتلة الشمس. |
72,731 | بنى البيانات المترابطة Linked Data Structures |
يتضمَّن أي كائنٍ object صالحٍ للاستعمال عددًا من متغيِّرات النسخ instance variables. عندما يُعطى نوع متغير نسخةٍ معين من قِبل اسم صنف class أو واجهة interface، فسيحمل هذا المتغير مرجًعا reference إلى كائنٍ آخر، ويُطلَق على المرجع اسم مؤشر pointer، ويُقال أن المُتغيِّر يُشير إلى كائنٍ ما. ومع ذلك، قد يحتوي المتغيِّر على القيمة الخاصة null؛ وفي تلك الحالة، لا يُشير المُتغير فعليًا إلى أي شيء. في المقابل، عندما يحتوي كائنٌ على متغير نسخة instance variable يُشير فعليًا إلى كائنٍ آخر، يُقال أن الكائنين مربوطين من خلال المؤشر. إن غالبية بنى البيانية data structures المعقدة مبنيّةٌ على تلك الفكرة؛ أي ربط عدة كائناتٍ ببعضها بعضًا. الترابط التعاودي Recursive Linkingعندما يتضمَّن كائنٌ معينٌ مُتغيِّر نسخة instance variable يُشير إلى كائنٍ آخر من نفس نوع الكائن الأصلي، يُقال أن تعريف الصنف تعاودي recursive. يحدث هذا النوع من التعاود في كثير من الحالات، فإذا كان لدينا مثلًا صنفٌ مُصمَّمٌ لتمثيل الموظفين العاملين ضمن شركةٍ معينة، بحيث يُشرف على كل موظف باستنثاء رئيس الشركة موظفٌ آخر ضمن نفس الشركة. في تلك الحالة، ينبغي أن يتضمَّن الصنف Employee مُتغيّر نسخة من النوع Employee لتمثيل المشرف. يمكننا تعريف الصنف على النحو التالي: public class Employee { String name; // اسم الموظف Employee supervisor; // مشرف الموظف . . // متغيرات وتوابع نسخ أخرى . } // نهاية صنف الموظفإذا كان emp متغيرًا من النوع Employee، فإن emp.supervisor هو بالضرورة متغيرٌ آخر من النوع Employee؛ فإذا كان emp يُشير إلى رئيس الشركة، فينبغي أن تكون قيمة emp.supervisor فارغة أي تُساوِي null للدلالة على أن رئيس الشركة ليس لديه أي مشرف. إذا أردنا طباعة اسم المشرف الخاص بموظف معين، يُمكِننا استخدام الشيفرة التالية على سبيل المثال: if ( emp.supervisor == null) { System.out.println( emp.name + " is the boss and has no supervisor!" ); } else { System.out.print( "The supervisor of " + emp.name + " is " ); System.out.println( emp.supervisor.name ); }لنفترض الآن أننا نريد معرفة عدد المشرفين الواقعين بين موظفٍ معين وبين رئيس الشركة. كل ما علينا فعله هو تتبُّع متتاليةٍ من المشرفين، وعَدّ عدد الخطوات المطلوبة حتى نصل إلى رئيس الشركة. ألقِ نظرةً على الشيفرة التالية. if ( emp.supervisor == null ) { System.out.println( emp.name + " is the boss!" ); } else { Employee runner; // For "running" up the chain of command. runner = emp.supervisor; if ( runner.supervisor == null) { System.out.println( emp.name + " reports directly to the boss." ); } else { int count = 0; while ( runner.supervisor != null ) { count++; // Count the supervisor on this level. runner = runner.supervisor; // Move up to the next level. } System.out.println( "There are " + count + " supervisors between " + emp.name + " and the boss." ); } }بينما يُنفِّذ الحاسوب حلقة التكرار while بالأعلى، سيشير runner مبدئيًا إلى الموظف الأصلي أي emp، ثم إلى مشرف الموظف emp، ثم إلى مشرف مشرف الموظف emp، وهكذا. ستزداد قيمة المُتغيّر count بمقدار الواحد بكل مرةٍ يزور خلالها المُتغيّر runner موظفًا جديدًا، وتنتهي حلقة التكرار عندما يُصبِح runner.supervisor فارغًا، وهو ما يُشير إلى وصولنا إلى رئيس الشركة. سيحتوِي المتغير count في تلك النقطة من البرنامج على عدد الخطوات بين emp ورئيس الشركة. يُعدّ المتغير supervisor في هذا المثال مفيدًا وبديهيًا نوعًا ما، حيث تُعدّ بنى البيانات data structures المعتمدة على ربط عدة كائناتٍ ببعضها بعضًا مفيدةً جدًا لدرجة أنها تُمثِل موضوعًا رئيسيًا للدراسة بعلوم الحاسوب computer science. سنناقش عدة أمثلةٍ نموذجيةٍ متعلّقةٍ بهذا النوع من بنى البيانات، وسنتناول بالتحديد القوائم المترابطة linked lists ضمن هذا المقال وما يليه. تتكوَّن أي قائمةٍ مترابطة linked list من سلسلةٍ من الكائنات من نفس النوع، حيث يَربُط مؤشرٌ pointer كائنًا معينًا بالكائن الذي يليه. يشبه ذلك كثيرًا سلسلة المشرفين الواقعة بين الموظف emp ورئيس الشركة بالمثال السابق. من الممكن أيضًا أن نتعرَّض لمواقف ٍأكثر تعقيدًا، وعندها قد يَتضمَّن كائنٌ واحدٌ روابطًا إلى كائناتٍ أخرى عديدة، وهو ما سنناقشه بمقال قادم. القوائم المترابطة Linked Listsستُبنَى القوائم المترابطة linked lists في غالبية الأمثلة المتبقية ضمن هذا المقال من كائناتٍ objects تنتمي إلى الصنف Node المُعرَّف على النحو التالي: class Node { String item; Node next; }يُستخدم عادةً مصطلح العقدة node للإشارة إلى أحد الكائنات الموجودة ببنيةٍ بيانيةٍ مترابطة linked data structure، حيث يُمكِننا ربط كائناتٍ من النوع Node ببعضها بعضًا كما هو مُوضَّح بالجزء العلوي من الصورة السابقة. تحتوي كل عقدةٍ على سلسلةٍ نصيةٍ من النوع String بالإضافة إلى مؤشرٍ للعقدة التالية ضمن القائمة إن وُجدت. يُمكِننا دائمًا تمييز العقدة الأخيرة بمثل تلك القائمة؛ حيث سيحمل متغير النسخة next ضمن تلك العقدة القيمة الفارغة null، وليس مؤشرًا إلى عقدةٍ أخرى. الهدف من ربط العقد بتلك الطريقة هو تمثيل قائمةٍ من السلاسل النصية strings؛ حيث تكون السلسلة النصية الأولى ضمن تلك القائمة مُخزَّنةً بالعقدة الأولى؛ بينما تكون السلسلة النصية الثانية مُخزَّنةً بالعقدة الثانية، وهكذا. في حين اِستخدَمنا مؤشراتٍ وكائناتٍ من النوع Node لبناء البنية البيانية data structure، ستكون البيانات التي نريد تمثيلها بالأساس هي قائمة السلاسل النصية. ونستطيع كذلك تمثيل قائمةٍ من الأعداد الصحيحة، أو قائمةٍ من الأزرار من النوع Button، أو قائمةٍ من أي نوعٍ آخر من البيانات فقط بتغيير نوع متغير النسخة item المُخزَّن بكل عقدة. على الرغم من تعريف الصنف Nodes بهذا المثال تعريفًا بسيطًا، فما يزال بإمكاننا استخدامه لتوضيح العمليات الشائعة على القوائم المترابطة linked lists، حيث تتضمَّن تلك العمليات ما يلي: حذف عقدةٍ من القائمة، أو إضافة عقدةٍ جديدة إلى القائمة، أو البحث عن سلسلةٍ نصية معينةٍ من النوع String ضمن عناصر القائمةitems. سنناقش مجموعةً من البرامج الفرعية subroutines المسؤولة عن تنفيذ جميع تلك العمليات. لاستخدام قائمةٍ مترابطةٍ ضمن برنامج، فإنه يحتاج إلى تعريف مُتغيّرٍ يشير إلى العقدة الأولى من تلك القائمة. في الواقع، كل ما يحتاجه البرنامج هو مؤشرٌ واحدٌ فقط إلى العقدة الأولى؛ بينما يمكن الوصول لجميع العقد الأخرى ضمن القائمة من خلال البدء من العقدة الأولى ثم التنقُّل من عقدةٍ لأخرى باتباع الروابط عبر القائمة. سنستخدم دائمًا بالأمثلة التالية متغيرًا اسمه head من النوع Node للإشارة إلى العقدة الأولى بالقائمة المترابطة، وعندما تَكون القائمة فارغةً، ستكون قيمة المتغير head هي القيمة الفارغة null. إجراء المعالجات الأساسية على قائمة مترابطةنحتاج عادةً إلى إجراء معالجةٍ معينةٍ على جميع العناصر الموجودة ضمن قائمةٍ مترابطة linked list، حيث لا بُدّ من البدء من رأس head القائمة، ثم التحرُّك من كل عقدةٍ للعقدة التي تليها باتباع المؤشر المُعرَّف ضمن كل عقدة، والتوقُّف أخيرًا عند الوصول إلى نهاية القائمة، وهو ما سندركه عند الوصول إلى القيمة الفارغة null. فإذا كان head متغيرًا من النوع Node، وكان يُشير إلى العقدة الأولى ضِمْن قائمةٍ مترابطة، فستكون الصياغة العامة المُستخدمة لمعالجة جميع عناصر تلك القائمة على النحو التالي: Node runner; // المؤشر المستخدم لاجتياز القائمة runner = head; // سيُشير runner إلى رأس القائمة مبدئيًا while ( runner != null ) { // استمر إلى أن تصل إلى القيمة الفارغة process( runner.item ); // عالج العنصر الموجود بالعقدة الحالية runner = runner.next; // تحرك إلى العقدة التالية }يُعدّ المتغير head الطريقة الوحيدة المُتاحة للوصول إلى عناصر القائمة. ونظرًا لأننا بحاجةٍ إلى تعديل قيمة المُتغيِّر runner، فلا بُدّ من إنشاء نسخةٍ من المتغير head، وإلا سنخسر الطريقة الوحيدة المتاحة للوصول إلى القائمة. بناءً على ذلك، سنستخدم تعليمة الإسناد assignment statement التالية runner = head لإنشاء نسخةٍ من المتغير head، حيث سيُشير المتغير runner إلى كل عقدةٍ ضمن القائمة تباعًا، وسيحمل runner.next مؤشرًا إلى العقدة التالية ضمن القائمة. وبالتالي، ستُحرِك تعليمة الإسناد runner = runner.next المؤشر على طول القائمة من عقدةٍ إلى أخرى، وعندما تُصبِح قيمة runner مساويةً للقيمة الفارغة null، نكون قد وصلنا إلى نهاية القائمة. لاحظ أن شيفرة المعالجة بالأعلى ستعمل بنجاح حتى لو كانت القائمة فارغة؛ لأن قيمة head لقائمةٍ فارغة هي القيمة الفارغة null، وعندها لن يُنفَّذ متن body حلقة التكرار while نهائيًا. يُمكِننا مثلًا طباعة جميع السلاسل النصية strings ضمن قائمةٍ من النوع String بكتابة ما يلي: Node runner = head; while ( runner != null ) { System.out.println( runner.item ); runner = runner.next; }يمكننا إعادة كتابة حلقة التكرار while باستخدام حلقة التكرار for، فعلى الرغم من أن المتغير المُتحكِّم بحلقة التكرار for عادةً ما يكون من النوع العددي، فإن ذلك ليس أمرًا ضروريًا. تعرض الشيفرة التالية حلقة تكرار for مُكافِئة لحلقة while بالأعلى: for ( Node runner = head; runner != null; runner = runner.next ) { System.out.println( runner.item ); }بالمثل، يُمكِننا أن نجتاز traverse قائمة أعدادٍ صحيحة لحساب حاصل مجموع الأعداد ضمن تلك القائمة، ويُمكننا بناء قائمةٍ مترابطة من الأعداد الصحيحة باستخدام الصنف التالي: public class IntNode { int item; // أحد الأعداد الصحيحة ضمن القائمة IntNode next; // مؤشر إلى العقدة التالية }إذا كان head متغيرًا من النوع IntNode، وكان يشير إلى قائمةٍ مترابطة من الأعداد الصحيحة، فإن الشيفرة التالية تحسب حاصل مجموع الأعداد الصحيحة ضمن تلك القائمة. int sum = 0; IntNode runner = head; while ( runner != null ) { sum = sum + runner.item; // أضف العنصر الحالي إلى المجموع runner = runner.next; } System.out.println("The sum of the list of items is " + sum);يُمكِننا أيضًا استخدام التعاود recursion لمعالجة قائمة مترابطة، ولكن من النادر استخدامه لمعالجة قائمة؛ لأنه من السهل دومًا استخدام حلقة loop لاجتيازها. ومع ذلك، قد يُساعد فهم طريقة تطبيق التعاود على القوائم على استيعاب كيفية تطبيق المعالجة التعاودية على بنى البيانات الأكثر تعقيدًا. يُمكِننا النظر لأي قائمةٍ مترابطة غير فارغة على أنها مُكوَّنة من جزئين:
يُعدّ ذيل القائمة ذاته بمثابة قائمةٍ مترابطةٍ أخرى، والتي هي أقصر من القائمة الأصلية بفارق عقدةٍ واحدة. يُمثِل ذلك أرضيةً مناسبةً نوعًا ما لتطبيق التعاود، حيث يمكن تقسيم معالجة القائمة إلى معالجة رأس القائمة ومعالجةٍ تعاوديةٍ لذيل القائمة، ويُمثِّل العثور على قائمةٍ فارغة الحالة الأساسية base case. تعرض الشيفرة التالية مثلًا خوارزميةً تعاوديةً recursive algorithm لحساب حاصل مجموع الأعداد الصحيحة ضمن قائمة مترابطة. // إذا كانت القائمة فارغة if the list is empty then // أعد صفر لأنه ليس هناك أي أعداد لجمعها return 0 (since there are no numbers to be added up) otherwise // اضبط listsum إلى العدد الموجود بعقدة الرأس let listsum = the number in the head node // اضبط tailsum إلى حاصل مجموع الأعداد الموجودة بقائمة الذيل تعاوديًا let tailsum be the sum of the numbers in the tail list (recursively) // أضف tailsum إلى listsum add tailsum to listsum return listsumيتبقى الآن الإجابة على السؤال التالي: كيف نحصل على ذيل قائمة مترابطة غير فارغة؟ إذا كان head متغيرًا يُشير إلى عقدة الرأس لقائمةٍ معينة، فسيشير متغير head.next إلى العقدة الثانية ضمن تلك القائمة، والتي تُمثِل في الوقت نفسه العقدة الأولى بذيل القائمة. لذلك، يُمكِننا النظر إلى head.next على أنه مؤشرٌ إلى ذيل القائمة. لاحِظ أنه في حالة كانت القائمة الأصلية مُكوَّنةً من عقدةٍ واحدةٍ فقط، فسيكون ذيل القائمة فارغًا، وعليه ستكون قيمة head.next مُساويةً للقيمة الفارغة null. نظرًا لاستخدام مؤشرٍ فارغٍ null pointer عمومًا لتمثيل القوائم الفارغة، فسيظل head.next مُمثِلًا مناسبًا لذيل القائمة حتى في تلك الحالة الخاصة. يمكننا إذًا تعريف الدالة التعاودية التالية بلغة جافا لحساب حاصل مجموع الأعداد ضمن قائمة. public static int addItemsInList( IntNode head ) { if ( head == null ) { // تُمثِل القائمة الفارغة أحد الحالات الأساسية return 0; } else { // 1 int listsum = head.item; int tailsum = addItemsInList( head.next ); listsum = listsum + tailsum; return listsum; } }تشير [1] إلى الحالة التعاودية وذلك عندما لا تكون القائمة فارغةً، احسب حاصل مجموع قائمة الذيل، ثم أضفه إلى العنصر الموجود بعقدة الرأس. لاحِظ أنه من الممكن كتابة ذلك في خطوةٍ واحدة على النحو التالي return head.item + addItemsInList( head.next );. سنناقش الآن مشكلةً أخرى يَسهُل حلها باستخدام التعاود بينما يَصعُب قليلًا بدونه، حيث تتمثل المشكلة بطباعة جميع السلاسل النصية الموجودة ضمن قائمةٍ مترابطةٍ من السلاسل النصية بترتيبٍ معاكسٍ لترتيب حدوثها ضمن القائمة؛ يعني ذلك أن نطبع العنصر الموجود برأس القائمة head بعد طباعة جميع عناصر ذيل القائمة. يقودنا ذلك إلى البرنامج التعاودي recursive routine التالي: public static void printReversed( Node head ) { if ( head == null ) { // الحالة الأساسية: أن تكون القائمة فارغة // ليس هناك أي شيءٍ لطباعته return; } else { // الحالة التعاودية: القائمة غير فارغة printReversed( head.next ); // اطبع السلاسل النصية بالذيل بترتيب معكوس System.out.println( head.item ); // اطبع السلسلة النصية بعقدة الرأس } }لا بُدّ من الاقتناع بأن هذا البرنامج يعمل، مع التفكير بإمكانية تنفيذه دون استخدام التعاود recursion. سنناقش خلال بقية هذا المقال عددًا من العمليات الأكثر تعقيدًا على قائمةٍ مترابطةٍ من السلاسل النصية، حيث ستكون جميع البرامج الفرعية subroutines التي سنتناولها توابع نُسخ instance methods مُعرَّفةً ضمن الصنف StringList الذي نفذَّه الكاتب. يُمثِل أي كائنٍ من النوع StringList قائمةً مترابطةً من السلاسل النصية، حيث يَملُك الصنف متغير نسخة خاص private اسمه head من النوع Node، ويشير إلى العقدة الأولى ضمن القائمة أو يحتوي على القيمة null إذا كانت القائمة فارغة. تستطيع توابع النسخ instance methods المُعرَّفة بالصنف StringList الوصول إلى head مثل متغير عام global. يُمكِنك العثور على الشيفرة المصدرية للصنف StringList بالملف StringList.java، كما أنه مُستخدمٌ بالبرنامج التوضيحي ListDemo.java، الذي يُمكِّنك من رؤية طريقة استخدامه بوضوح. سنُلقِي نظرةً على إحدى التوابع المُعرَّفة بالصنف StringList للإطلاع على مثالٍ عن المعالجة البسيطة للقوائم واجتيازها، حيث يبحث التابع ذو النوع StringList عن سلسلةٍ نصيةٍ معينة ضمن قائمة؛ إذا كان searchItem مُمثِلًا للسلسلة النصية التي نبحث عنها، فعلينا موازنة searchItem مع كل عنصرٍ ضمن تلك القائمة، بحيث نتوقف عن المعالجة عند العثور على العنصر الذي نبحث عنه. انظر الشيفرة التالية: public boolean find(String searchItem) { Node runner; // مؤشر لاجتياز القائمة // ابدأ بفحص رأس القائمة runner = head; while ( runner != null ) { // 1 if ( runner.item.equals(searchItem) ) return true; runner = runner.next; // تحرك إلى العقدة التالية } // 2 return false; } // end find()حيث تشير كل من [1] و[2] إلى الآتي:
لاحِظ أنه من الممكن أن تكون القائمة فارغةً أي تكون قيمة head مساويةً للقيمة الفارغة null، لذلك ينبغي معالجة تلك الحالة معالجةً سليمةً؛ فإذا احتوى head المُعرَّف بالشيفرة بالأعلى على القيمة null، فلن يُنفَّذ متن حلقة التكرار while نهائيًا، أي لن يعالج التابع أي عقد nodes، وستكون القيمة المعادة هي القيمة false. هذا هو ما نريد فعله تمامًا عندما تكون القائمة فارغة؛ نظرًا لعدم امكانية حدوث searchItem ضمن قائمةٍ فارغة. الإضافة إلى قائمة مترابطةقد تكون مشكلة إضافة عنصرٍ جديدٍ إلى قائمةٍ مترابطة أكثر صعوبةً نوعًا ما، بالأخص عندما يكون من الضروري إضافة العنصر إلى منتصف القائمة، وتُعد هذه المشكلة أعقد معالجةٍ سنُجرِيها على بنى البيانات المترابطة linked data structures خلال هذا المقال بالكامل. تُحفَظ العناصر الموجودة بعُقد القائمة المترابطة في الصنف StringList مُرتَّبةً تصاعديًا، لذلك عند إضافة عنصرٍ جديدٍ إلى القائمة، لابُدّ من إضافته إلى الموضع الصحيح وفقًا لذلك الترتيب؛ وهذا يعني أننا سنضطَّر في أغلب الحالات إلى إضافة العنصر الجديد إلى مكانٍ ما بمنتصف القائمة أي بين عقدتين موجودتين بالفعل. لكي نتمكَّن من فعل ذلك، سيكون من الضروري تعريف متغيرين من النوع Node للإشارة إلى العقدتين اللتين ينبغي أن تقعا على جانبي العقدة الجديدة، حيث يُمثّل previous وrunner بالصورة التالية المتغيرين المذكورين. إلى جانب ذلك، سنحتاج إلى متغيرٍ آخر newNode للإشارة إلى العقدة الجديدة. لتمكين عملية الإضافة insertion، لا بُدّ من إلغاء الرابط الموجود من previous إلى runner، وإضافة رابطين جديدين من previous إلى newNode، ومن newNode إلى runner. بمجرد ضبط قيمة المتغيرين previous وrunner بحيث يُشيرا إلى العقد الصحيحة، يُمكِننا استخدام الأمر previous.next = newNode; لضبط previous.next لكي يشير إلى العقدة الجديدة، والأمر newNode.next = runner لضبط newNode.next لكي يُشير إلى المكان الصحيح. ولكن قبل أن نستطيع تنفيذ أي من تلك الأوامر، سنحتاج أولًا لضبط كلٍ من runner وprevious كما هو موضحٌ بالصورة السابقة. الفكرة ببساطة هي أن نبدأ من العقدة الأولى بالقائمة، ثم نتحرك على طول القائمة مرورًا بجميع العناصر الأقل من العنصر الجديد، ويجب علينا الانتباه جيدًا أثناء ذلك حتى لا نقع بمشكلة السقوط خارج نهاية القائمة؛ أي أننا لن نستطيع الاستمرار إذا وصل runner إلى نهاية القائمة وأصبحت قيمته تساوي null. لنفترض أن insertItem هو العنصر المطلوب إضافته إلى القائمة، ولنفترض أيضًا أنه ينتمي إلى مكانٍ ما بمنتصف القائمة، ستَتَمكَّن الشيفرة التالية من ضبط قيمة المتغيرين previous وrunner على نحوٍ صحيح. Node runner, previous; previous = head; // ابدأ من مقدمة القائمة runner = head.next; while ( runner != null && runner.item.compareTo(insertItem) < 0 ) { previous = runner; // "previous = previous.next" would also work runner = runner.next; }يستخدم المثال الموضح أعلاه تابع النسخة compareTo() -انظر إلى مقال السلاسل النصية String والأصناف Class والكائنات Object والبرامج الفرعية Subroutine في جافا- المُعرَّف بالصنف String لاختبار ما إذا كان العنصر الموجود بالعقدة node أقل من العنصر المطلوب إضافته. ليس هناك أي مشكلةٍ فيما سبق باستثناء افتراضنا الدائم بانتماء العقدة الجديدة إلى مكانٍ ما بمنتصف القائمة، وهو أمرٌ غير صحيح؛ فقد تكون قيمة العقدة المطلوب إضافتها insertItem أحيانًا أقل من قيمة العنصر الأول ضمن القائمة، وهنا لا بُدّ من إضافة العقدة الجديدة إلى رأس القائمة، وهو ما تفعله الشيفرة التالية. newNode.next = head; // Make newNode.next point to the old head. head = newNode; // Make newNode the new head of the list.من الممكن أيضًا أن تكون القائمة فارغة، وينبغي في هذه الحالة أن تكون newNode هي العقدة الأولى والوحيدة ضمن القائمة. يمكننا إنجاز ذلك ببساطةٍ من خلال تنفيذ تعليمة الإسناد التالية head = newNode، ويعالِج التعريف التالي للتابع insert()، المُعرَّف بالصنف StringList جميع تلك الاحتمالات. public void insert(String insertItem) { Node newNode; // عقدة تحتوي على العنصر الجديد newNode = new Node(); newNode.item = insertItem; // (N.B. newNode.next == null.) if ( head == null ) { // العنصر الجديد هو العنصر الأول والوحيد ضمن القائمة // اضبط head بحيث تشير إليه head = newNode; } else if ( head.item.compareTo(insertItem) >= 0 ) { // العنصر الجديد أقل من أول عنصر ضمن القائمة // لذلك ينبغي أن يُضبَط بحيث يكون رأسًا للقائمة newNode.next = head; head = newNode; } else { // ينتمي العنصر الجديد إلى مكان ما بعد العنصر الأول ضمن القائمة // ابحث عن موضعه المناسب وأضِفه إليه Node runner; // العقدة المستخدمة لاجتيار القائمة Node previous; // تُشير دائمًا إلى العقدة السابقة لـ runner runner = head.next; // ابدأ بفحص الموضع الثاني ضمن القائمة previous = head; while ( runner != null && runner.item.compareTo(insertItem) < 0 ) { // 1 previous = runner; runner = runner.next; } newNode.next = runner; // أضف newNode بعد previous previous.next = newNode; } } // end insert()يعني [1] حرّك previous وrunner على طول القائمة إلى أن يقع runner خارج القائمة، أو إلى أن يصل إلى عنصرٍ أكبر من أو يساوي insertItem. بانتهاء تلك الحلقة، سيشير previous إلى الموضع الذي ينبغي إضافة insertItem إليه. إذا كنت منتبهًا للمناقشة الموضحة أعلاه، فقد تكون لاحظت أن هناك حالةً خاصةً أخرى لم نذكرها، فما الذي سيحدث إذا كان علينا إضافة العقدة الجديدة إلى نهاية القائمة؟ وهذا يحدث إذا كانت جميع عناصر القائمة أقل من العنصر الجديد. في الواقع، يُعالِج البرنامج الفرعي subroutine المُعرَّف بالأعلى تلك الحالة أيضًا ضمن الجزء الأخير من تعليمة if؛ فإذا كان insertItem أكبر من جميع عناصر القائمة، فستنتهي حلقة while بعدما يجتاز runner القائمة بالكامل، وستُصبِح قيمته عندها مساويةً للقيمة الفارغة null. ولكن عند الوصول إلى تلك النقطة، سيكون previous ما يزال يشير إلى العقدة الأخيرة بالقائمة؛ ولهذا سنُنفِّذ التعليمة previous.next = newNode لإضافة newNode إلى نهاية القائمة. ونظرًا لأن runner يُساوِي القيمة الفارغة null، فسيَضبُط الأمر newNode.next = runner قيمة newNode.next إلى القيمة الفارغة null، وهو ما نحتاج إليه تمامًا لنتمكَّن من الإشارة إلى أن القائمة قد انتهت. الحذف من قائمة مترابطةتشبه عملية الحذف عملية الإدخال على الرغم من أنها أبسط نوعًا ما، حيث توجد حالاتٌ خاصة ينبغي أخذها بالحسبان؛ فإذا أردنا حذف العقدة الأولى بقائمة، فينبغي علينا ضبط المُتغيِّر head، وجعله يُشيِر إلى العقدة التي كانت تُعدّ مُسبقًا العقدة الثانية بتلك القائمة. بما أن المتغير head.next يشير بالأساس إلى العقدة الثانية بالقائمة، يُمكِننا إجراء التعديل المطلوب بتنفيذ التعليمة head = head.next، وعلينا أيضًا التأكُّد من أن البرنامج يعمل على نحوٍ سليم حتى وإن كانت قيمة head.next تُساوِي null. عندما لا يكون هناك سوى عقدةٍ واحدةٍ ضمن قائمةٍ معينة، فستصبح القائمة فارغةً. أما إذا كنا نريد حذف عقدةٍ بمنتصف قائمةٍ معينة، فعلينا ضبط قيمة المتغيرين previous وrunner كما فعلنا سابقًا؛ حيث يُشير runner إلى العقدة المطلوب حذفها، بينما يُشيِر previous إلى العقدة التي تسبق العقدة المطلوب حذفها ضمن تلك القائمة. بمجرد انتهائنا من ضبط قيمة المتغيرين، سيحذف الأمر previous.next = runner.next; العقدة المعنية، وسيتولى كانس المهملات garbage collector مسؤولية تحريرها. حاول رسم صورةٍ توضيحيةٍ لعملية الحذف. يُمكِننا الآن تعريف التابع delete() على النحو التالي: public boolean delete(String deleteItem) { if ( head == null ) { // القائمة فارغة، وبالتالي هي بالتأكيد لا تحتوي على deleteString return false; } else if ( head.item.equals(deleteItem) ) { // عثرنا على السلسلة النصية بأول عنصر بالقائمة. احذفه head = head.next; return true; } else { // إذا كانت السلسلة النصية موجودةً بالقائمة، فإنها موجودة بمكانٍ ما بعد // العنصر الأول، وعليه سنبحث عنه بتلك القائمة Node runner; // العقدة المستخدمة لاجتياز القائمة Node previous; // تُشير دائمًا إلى العقدة السابقة للعقدة runner runner = head.next; // ابدأ بفحص الموضع الثاني ضمن القائمة previous = head; while ( runner != null && runner.item.compareTo(deleteItem) < 0 ) { // 1 previous = runner; runner = runner.next; } if ( runner != null && runner.item.equals(deleteItem) ) { // يشير runner إلى العقدة المطلوب حذفها // احذف تلك العقدة بتعديل المؤشر بالعقدة السابقة previous.next = runner.next; return true; } else { // العنصر غير موجود بالقائمة return false; } } } // end delete()وتعني [1] حَرِك previous وrunner على طول القائمة إلى أن يقع runner خارج القائمة أو إلى أن يَصِل إلى عنصرٍ أكبر من أو يساوي deleteItem. بانتهاء تلك الحلقة، سيُشير runner إلى موضع العنصر المطلوب حذفه، إذا كان موجودًا بالقائمة. ترجمة -بتصرّف- للقسم Section 2: Linked Data Structures من فصل Chapter 9: Linked Data Structures and Recursion من كتاب Introduction to Programming Using Java. اقرأ أيضًا |