Abather

Members
  • عدد منشوراتي

    17
  • تاريخ الإنضمام

  • تاريخ اخر زياره

السمعه بالموقع

22 Excellent

1 متابع

عن العضو Abather

  • الرتبه
    مبدع مثابر
  • تاريخ الميلاد 06/15/90

معلومات عامة

  • الجنس
    ذكر
  • السكن
    الأحساء

اخر الزوار

1459 زياره للملف الشخصي
  1. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته بعد غيبة طويلة أعود بموضوع صغير نوعاً ما لكنه ذي فائدة كبيرة. في هذا الموضوع إن شاء الله سنتعرض لطريقة حفظ وفتح ملفات PDF (سأستخدم pdf في المثال لكن في الحقيقة بالامكان حفظ أي نوع من أنواع الملفات سواء ملفات صوتية أو صور أو فيديو). يجب أن يكون القارء ملم ببعض الأساسيات بكيفية ربط قاعدة البيانات (MySQL) مع ملف Java كذلك كيفية التعامل مع قاعدة البيانات من إنشاء، استعراض واضافات البيانات داخل قاعدة البيانات . وإن شاء الله سأوفر كل الموارد التي نحتاجها في موضوعنا هذا. بسم الله نبدأ. - هل حفظ الملفات داخل قاعدة البيانات الطريقة الوحيدة؟ بالطبع حفظ الملفات داخل قاعدة البيانات ليست الطريقة الوحيدة لحفظ الملفات داخل الخادم بل هناك طرق أخرى، مثلا يمكنك أن تحفظ الملفا داخل أي مكان في الخادم ومن ثم تحفظ المسار الكامل له داخل قاعدة البيانات، وقد يحبب الكثير هذه الطريقة؛ لكن لحفظ الملفات داخل قاعدة البيانات محاسنها (كما ولها مساوئها) فمثلاً في حال أردت أخد نسخة احتياطية لبياناتك ليس عليك سوى أخذ نسخة من قاعدة البيانات نفسها بدون نسخ أي ملفات أخرى، قد تضطر لتغيير مكان الملفات في مرحلة ما وفي هذه الحال يجب عليك أن تغيير المسار المحفوظ للملف داخل قاعدة البيانات (لا أقول بأن هذا صعب لكنه يحتاج إلى وقت)، كذلك فإن بياناتك محمية من الدخول أو الحذف أو التغيير غير المصرح به بما أنها داخل قاعدة البيانات. من العيوب التي قد تمنعك من حفظ الملفات داخل قاعدة البيانات هو الحجم المهول الذي ستقدوا عليه بعد فترة من الزمن، لكن لا أعتقد بأن هذا ذي أهمية كبيرة حيث أنك تريد المحافظة على كل بياناتك من الضياع فعند نسخك أو أخذك نسخة احتياطية لقاعدة البيانات فإنك تريد الحفاظ على الملفات كذلك ولكن لحل هذه المشكلة بالامكان حفظ الملفات داخل جدول مستقل لإقصائها من بعض العمليات. - ما هي الملفات التي تقبل قاعدة البيانات حفظها؟ قاعدة البيانات تحفظ الملفات على شكل ملف Binary (ثنائي) كبير؛ بالتالي فإن قاعدة البيانات قاددرة على حفظ أي نوع من أنواع الملفات مهما كان ملفات الفيديو مثل mp4. أو .mkv، ملفات الصوت مثل ,aac أو .m4a، الصور بشتى أنواعها أو المفات المكتبية. ببساطة أي ملف يمكن تخيله باستطاعتنا حفظه داخل قاعدة البيانات. - ما هو أقصى حجم للملف؟ قواعد البيانات MySQL تحتوي على أربع أنواع يمكن حفظ الملفات فيها، ما يمز كل نوع عن الآخر هو أقصى حجم يمكن حفظها دخلها فقط، أما طريقة التعامل معها في الحفظ والاسترجاع فمتماثله. 1. TINYBLOB: أقصى حجم هو (28 ما يعادل 256 بايت). 2. BLOB: أقصى حجم هو (216 ما يعادل 65 كيلوبايت). 3. MEDIUMBLOB: أقصى حجم هو (224 ما يعادل 16 ميجابايت). 4. LONGBLOB: أقصى حجم هو (232 ما يعادل 4 جيجابايت). يعتمد على أقصى حجم تحتاجه لحفظ الملف مثلا ملفات الصوت لن يتعدى حجم الملف الواحد في أسوء الأحوال 16 ميغا، ففي هذه الحالة سأختار نوع MEDIUMBLOB، أما لو كنت أحتاج مساحة أكبر أو أصغر فهنا سأختار غيره من الأنواع. - الأدوات والبرامج المستخدمة: - سيرفر قاعدة بيانات MySQL (بالإمكان استخدام XAMPP أو SQLite) -ملف jar والذي يربط قاعدة البيانات بمشروع Java. (متوفر في المرفقات نسخة 5.1.44 mysql-connector-java-5.1.44.zip) على فرض بأنك قاعد على ربط ملف jar بمشروعك في Java سوف أكمل الشرح. خطوات العمل: أولاً: إنشاء جدول في قاعدة البيانات: سنقوم بإن شاء ملف داخل قاعدة البيانات لنحفظ بداخله اسم الملف، ونوعه ( The file name extension). إذا الجدول (باسم myData) يحتوي على 4 أعمدة، رقم الملف (id), اسم الملف(fullname), نوع الملف (type), الملف (file): CREATE TABLE myData(id INT(11) NOT NULL PRIMARY KEY, fullname VARCHAR(35)NOT NULL, type VARCHAR(8) NOT NULL, file MEDIUMBLOB NOT NULL); ثانياً: حفظ ملف باستخدام Java: الآن انتهى عملنا داخل قاعدة البيانات وننتقل إلى Java وسنقوم بانشاء class نقوم بتسميته storeData, ونقوم بإنشاء العمليات الازمة بربطه بقاعدة البيانات: try { Class.forName("com.mysql.jdbc.Driver"); Connection connect = DriverManager.getConnection("jdbc:mysql://localhost/pro3alam","root", ""); } catch (SQLException e) {} catch (Exception e) {} بعد ذلك نجهز اوجكت من نوع PreparedStatement لنقوم باطلاق كود MySQL من خلاله: PreparedStatement preStat = connect.prepareStatement("INSERT INTO myData VALUES(?, ?, ?, ?)"); بعد ذلك نقوم بإنشاء ملف ونحدد موقع الملف الذي نريد حفظه لنفرض بأن الملف في سطح المكتب باسم test ونوع pdf ونقوم بحفظ اسمه ونوعه في متغيرين مختلفين كالتالي: File myFile = new File("/Users/abather/Desktop/test.pdf"); String fileName = myFile.getName().substring(0, myFile.getName().lastIndexOf(".")); String fileType = myFile.getName().substring(myFile.getName().lastIndexOf("."), myFile.getName().length()); للتفصيل أكثر في الأكواد السابقة فقد قمت بحفظ اسم الملف داخل المتغيير fileName عن طريق اخذ الجزء من بداية الاسم الى آخر نقطة أي قبل نوع الملف. ومن ثم قمت بحفظ نوع الملف داخل المتغيير fileType عن طريق اخذ الجزء من اسم الملف الذي يبدأ بآخر نقطة الى نهاية الاسم. من الآفضل حفظ نوع الملف على حدى عن اسم الملف في حال أردت تغيير الاسم أو حفظ أكثر من نوع واحد من الملفات داخل نفس الجدول. الآن ننتقل إلى النقطة ما قبل الأخيرة وهي بتجهيز الملف ليتم كتابته داخل القاعدة، ونقوم بتجهيزية عن طريق FileInputStream كما يوضح الكود التالي: FileInputStream inputstream = new FileInputStream(myFile); الآن اصبح الملف جاهزة لكتابته دخل قاعدة البيانات، نعود الى PreparedStatement التي قمنا بانشائها ونقوم بادخال البيانات التي نريد حفظها ومن ثم نقوم باطلاقها للكتابة داخل قاعدة البيانات كما يوضح الكود التالي: FileInputStream inputstream = new FileInputStream(myFile); preStat.setInt(1, 1); preStat.setString(2,fileName); preStat.setString(3,fileType); preStat.setBinaryStream(4,inputstream, inputstream.available()); preStat.execute(); كما هو واضح في الكود فقد اتممت اضافة القيم الى كل عامو في الجدول من رقم الملف واسمه ونوعه والملف نفسه. ويجب الحذر بأن نوع الميثود المستخدمة لكتابة الملفات هي setBinaryStream. وفي نهاية الكود بعد اطلاق PreparedStatement نكون قد حفظنا الملف داخل قاعدة البيانات. نعود لقاعدة البيانات ونبحث عن الملف لنتأكد من وجوده: ملاحظة: لا تحاول البحث عن الملف في حال كنت في Terminal لانه لن يكون سوى مجموعة من الرموز غير المفهومه. ثالثا: قراءة الملف المحفوظ داخل قاعدة البيانات: هنا سنقوم بقراءة الملف السابق من قاعدة البيانات وحفظه على سطح المكتب، في البداية سنقوم بانشاء class باسم restoreData. سنقوم بالخطوات السابقة بالاتصال بقاعدة البيانات، ومن ثم نقوم بإنشاء Statement والبحث عن الصف الذي حفظنا الملف بداخله وتخزينه داخل ResultSet كالتالي: Statement statement = connect.createStatement(); ResultSet result = statement.executeQuery("SELECT * FROM myData WHERE id=1"); الان سنقوم بحفظ اسم الملف ونوعه داخل متغير واحد مع تغير بسيط في الاسم ومن ثم إن شاء ملف File وجعل مساره يشير الى سطح المكتب كالتالي: result.next(); String fullName = result.getString(2) + "From DB" + result.getString(3); //سيكون هذا اسم الملف الذي سنسترده من قاعدة البيانات //بالامكان تغيير الاسم كيفما نريد ولكن المهم هو نوع الملف File myFile = new File("/Users/abather/Desktop/"+fullName); الان نقوم بانشاء اوبجكت من نوع FileOutputStream والذي سيقوم بكتابة البيانات داخل الملف ومن ثم حفظه على سطح المكتب، كذلك نحن بحاجة لمتغير من نوع Inputstream لحفظ البيانات من داخل الناتج من قاعدة البيانات وآخر من نوع byte والذي سيكون بمثابة المكان الذي سنخزن فيه البيانات مؤقتاً لكتابتها داخل الملف : FileOutputStream outputStream = new FileOutputStream(myFile); InputStream inputStream = result.getBinaryStream(4); byte[] buffer = new byte[1024]; while (inputStream.read(buffer) > 0) { outputStream.write(buffer); } هكذا نكون قد قمنا بحفظ الملف واسترجاعه من قاعدة البيانات. أسأل الله العلي العظيم أن أكون قد وفقت لإصال المعلومة بشكل مبسط وسلس. واي استفسار حول الموضوع أنا بالخدمة. كذلك السورس كود تجدونه في المرفقات لحفظ واسترجاع الملفات من قاعدة البيانات. storeData.java restoreData.java
  2. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته بعد غيبة طويلة أعود بموضوع صغير نوعاً ما لكنه ذي فائدة كبيرة. في هذا الموضوع إن شاء الله سنتعرض لطريقة حفظ وفتح ملفات PDF (سأستخدم pdf في المثال لكن في الحقيقة بالامكان حفظ أي نوع من أنواع الملفات سواء ملفات صوتية أو صور أو فيديو). يجب أن يكون القارء ملم ببعض الأساسيات بكيفية ربط قاعدة البيانات (MySQL) مع ملف Java كذلك كيفية التعامل مع قاعدة البيانات من إنشاء، استعراض واضافات البيانات داخل قاعدة البيانات . وإن شاء الله سأوفر كل الموارد التي نحتاجها في موضوعنا هذا. بسم الله نبدأ. - هل حفظ الملفات داخل قاعدة البيانات الطريقة الوحيدة؟ بالطبع حفظ الملفات داخل قاعدة البيانات ليست الطريقة الوحيدة لحفظ الملفات داخل الخادم بل هناك طرق أخرى، مثلا يمكنك أن تحفظ الملفا داخل أي مكان في الخادم ومن ثم تحفظ المسار الكامل له داخل قاعدة البيانات، وقد يحبب الكثير هذه الطريقة؛ لكن لحفظ الملفات داخل قاعدة البيانات محاسنها (كما ولها مساوئها) فمثلاً في حال أردت أخد نسخة احتياطية لبياناتك ليس عليك سوى أخذ نسخة من قاعدة البيانات نفسها بدون نسخ أي ملفات أخرى، قد تضطر لتغيير مكان الملفات في مرحلة ما وفي هذه الحال يجب عليك أن تغيير المسار المحفوظ للملف داخل قاعدة البيانات (لا أقول بأن هذا صعب لكنه يحتاج إلى وقت)، كذلك فإن بياناتك محمية من الدخول أو الحذف أو التغيير غير المصرح به بما أنها داخل قاعدة البيانات. من العيوب التي قد تمنعك من حفظ الملفات داخل قاعدة البيانات هو الحجم المهول الذي ستقدوا عليه بعد فترة من الزمن، لكن لا أعتقد بأن هذا ذي أهمية كبيرة حيث أنك تريد المحافظة على كل بياناتك من الضياع فعند نسخك أو أخذك نسخة احتياطية لقاعدة البيانات فإنك تريد الحفاظ على الملفات كذلك ولكن لحل هذه المشكلة بالامكان حفظ الملفات داخل جدول مستقل لإقصائها من بعض العمليات. - ما هي الملفات التي تقبل قاعدة البيانات حفظها؟ قاعدة البيانات تحفظ الملفات على شكل ملف Binary (ثنائي) كبير؛ بالتالي فإن قاعدة البيانات قاددرة على حفظ أي نوع من أنواع الملفات مهما كان ملفات الفيديو مثل mp4. أو .mkv، ملفات الصوت مثل ,aac أو .m4a، الصور بشتى أنواعها أو المفات المكتبية. ببساطة أي ملف يمكن تخيله باستطاعتنا حفظه داخل قاعدة البيانات. - ما هو أقصى حجم للملف؟ قواعد البيانات MySQL تحتوي على أربع أنواع يمكن حفظ الملفات فيها، ما يمز كل نوع عن الآخر هو أقصى حجم يمكن حفظها دخلها فقط، أما طريقة التعامل معها في الحفظ والاسترجاع فمتماثله. 1. TINYBLOB: أقصى حجم هو (28 ما يعادل 256 بايت). 2. BLOB: أقصى حجم هو (216 ما يعادل 65 كيلوبايت). 3. MEDIUMBLOB: أقصى حجم هو (224 ما يعادل 16 ميجابايت). 4. LONGBLOB: أقصى حجم هو (232 ما يعادل 4 جيجابايت). يعتمد على أقصى حجم تحتاجه لحفظ الملف مثلا ملفات الصوت لن يتعدى حجم الملف الواحد في أسوء الأحوال 16 ميغا، ففي هذه الحالة سأختار نوع MEDIUMBLOB، أما لو كنت أحتاج مساحة أكبر أو أصغر فهنا سأختار غيره من الأنواع. - الأدوات والبرامج المستخدمة: - سيرفر قاعدة بيانات MySQL (بالإمكان استخدام XAMPP أو SQLite) -ملف jar والذي يربط قاعدة البيانات بمشروع Java. (متوفر في المرفقات نسخة 5.1.44 mysql-connector-java-5.1.44.zip) على فرض بأنك قاعد على ربط ملف jar بمشروعك في Java سوف أكمل الشرح. خطوات العمل: أولاً: إنشاء جدول في قاعدة البيانات: سنقوم بإن شاء ملف داخل قاعدة البيانات لنحفظ بداخله اسم الملف، ونوعه ( The file name extension). إذا الجدول (باسم myData) يحتوي على 4 أعمدة، رقم الملف (id), اسم الملف(fullname), نوع الملف (type), الملف (file): CREATE TABLE myData(id INT(11) NOT NULL PRIMARY KEY, fullname VARCHAR(35)NOT NULL, type VARCHAR(8) NOT NULL, file MEDIUMBLOB NOT NULL); ثانياً: حفظ ملف باستخدام Java: الآن انتهى عملنا داخل قاعدة البيانات وننتقل إلى Java وسنقوم بانشاء class نقوم بتسميته storeData, ونقوم بإنشاء العمليات الازمة بربطه بقاعدة البيانات: try { Class.forName("com.mysql.jdbc.Driver"); Connection connect = DriverManager.getConnection("jdbc:mysql://localhost/pro3alam","root", ""); } catch (SQLException e) {} catch (Exception e) {} بعد ذلك نجهز اوجكت من نوع PreparedStatement لنقوم باطلاق كود MySQL من خلاله: PreparedStatement preStat = connect.prepareStatement("INSERT INTO myData VALUES(?, ?, ?, ?)"); بعد ذلك نقوم بإنشاء ملف ونحدد موقع الملف الذي نريد حفظه لنفرض بأن الملف في سطح المكتب باسم test ونوع pdf ونقوم بحفظ اسمه ونوعه في متغيرين مختلفين كالتالي: File myFile = new File("/Users/abather/Desktop/test.pdf"); String fileName = myFile.getName().substring(0, myFile.getName().lastIndexOf(".")); String fileType = myFile.getName().substring(myFile.getName().lastIndexOf("."), myFile.getName().length()); للتفصيل أكثر في الأكواد السابقة فقد قمت بحفظ اسم الملف داخل المتغيير fileName عن طريق اخذ الجزء من بداية الاسم الى آخر نقطة أي قبل نوع الملف. ومن ثم قمت بحفظ نوع الملف داخل المتغيير fileType عن طريق اخذ الجزء من اسم الملف الذي يبدأ بآخر نقطة الى نهاية الاسم. من الآفضل حفظ نوع الملف على حدى عن اسم الملف في حال أردت تغيير الاسم أو حفظ أكثر من نوع واحد من الملفات داخل نفس الجدول. الآن ننتقل إلى النقطة ما قبل الأخيرة وهي بتجهيز الملف ليتم كتابته داخل القاعدة، ونقوم بتجهيزية عن طريق FileInputStream كما يوضح الكود التالي: FileInputStream inputstream = new FileInputStream(myFile); الآن اصبح الملف جاهزة لكتابته دخل قاعدة البيانات، نعود الى PreparedStatement التي قمنا بانشائها ونقوم بادخال البيانات التي نريد حفظها ومن ثم نقوم باطلاقها للكتابة داخل قاعدة البيانات كما يوضح الكود التالي: FileInputStream inputstream = new FileInputStream(myFile); preStat.setInt(1, 1); preStat.setString(2,fileName); preStat.setString(3,fileType); preStat.setBinaryStream(4,inputstream, inputstream.available()); preStat.execute(); كما هو واضح في الكود فقد اتممت اضافة القيم الى كل عامو في الجدول من رقم الملف واسمه ونوعه والملف نفسه. ويجب الحذر بأن نوع الميثود المستخدمة لكتابة الملفات هي setBinaryStream. وفي نهاية الكود بعد اطلاق PreparedStatement نكون قد حفظنا الملف داخل قاعدة البيانات. نعود لقاعدة البيانات ونبحث عن الملف لنتأكد من وجوده: ملاحظة: لا تحاول البحث عن الملف في حال كنت في Terminal لانه لن يكون سوى مجموعة من الرموز غير المفهومه. ثالثا: قراءة الملف المحفوظ داخل قاعدة البيانات: هنا سنقوم بقراءة الملف السابق من قاعدة البيانات وحفظه على سطح المكتب، في البداية سنقوم بانشاء class باسم restoreData. سنقوم بالخطوات السابقة بالاتصال بقاعدة البيانات، ومن ثم نقوم بإنشاء Statement والبحث عن الصف الذي حفظنا الملف بداخله وتخزينه داخل ResultSet كالتالي: Statement statement = connect.createStatement(); ResultSet result = statement.executeQuery("SELECT * FROM myData WHERE id=1"); الان سنقوم بحفظ اسم الملف ونوعه داخل متغير واحد مع تغير بسيط في الاسم ومن ثم إن شاء ملف File وجعل مساره يشير الى سطح المكتب كالتالي: result.next(); String fullName = result.getString(2) + "From DB" + result.getString(3); //سيكون هذا اسم الملف الذي سنسترده من قاعدة البيانات //بالامكان تغيير الاسم كيفما نريد ولكن المهم هو نوع الملف File myFile = new File("/Users/abather/Desktop/"+fullName); الان نقوم بانشاء اوبجكت من نوع FileOutputStream والذي سيقوم بكتابة البيانات داخل الملف ومن ثم حفظه على سطح المكتب، كذلك نحن بحاجة لمتغير من نوع Inputstream لحفظ البيانات من داخل الناتج من قاعدة البيانات وآخر من نوع byte والذي سيكون بمثابة المكان الذي سنخزن فيه البيانات مؤقتاً لكتابتها داخل الملف : FileOutputStream outputStream = new FileOutputStream(myFile); InputStream inputStream = result.getBinaryStream(4); byte[] buffer = new byte[1024]; while (inputStream.read(buffer) > 0) { outputStream.write(buffer); } هكذا نكون قد قمنا بحفظ الملف واسترجاعه من قاعدة البيانات. أسأل الله العلي العظيم أن أكون قد وفقت لإصال المعلومة بشكل مبسط وسلس. واي استفسار حول الموضوع أنا بالخدمة. كذلك السورس كود تجدونه في المرفقات لحفظ واسترجاع الملفات من قاعدة البيانات. storeData.java restoreData.java
  3. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته تعرفنا في بداية سلسلة مواضيعنا على طريقة من طرق تخزين البيانات وهي القوائم سواء الأحادية أو الثنائية، كما ونعلم بأن هناك طريقة أخرى احفظ مجموعة البيانات وهي أبسط طريقة والتي تتمثل في المصفوفات سواء المصفوفات العادية أو مصفوفات القوائم. في هذا الدرس سنتعرف على أحد أفضل الطرق في حفظ البيانات والتي تعتبر من الأفضل من ناحية العرض أو البحث وغيرها من الأمور المتعلقة بهذه المجموعة من البيانات. كما هو الحال في القوائم فإن الأشجار لديها أكثر من نوع وكل واحد منهم له خواصة واستخداماته. في هذا الدرس سنتعرف على أحد أنواع الأشجار وهو الأشجار الثنائية. ماذا نقصد بالأشجار الثنائية (Binary Trees): هو نوع من أنواع تمثيل البيانات ويتكون من مكونين أساسيين هما العقد (Nodes) و الأقصان (Arcs). عندما نقوم بتمثيل هذه الشجرة فإننا نقلب مفهومها فيكون الجذر (Root) في الأعلى والأقصان في الأسفل. سميت بالأشجار الثنائية لأنه كل عقد لديه عقيدين يرتبط فيهم وهما أبنائه ولديه والد واحد. هناك بعض المصطلحات التي يجب أن نتعرف عليها قبل الشروع الى صلب الموضوع: - العقد (Node): وهو كل عنصر موجود في الشجرة، وهو المكان الذي يتم حفظ البيانات داخله. أنواع خاصة من العقد (Nodes): - الجذر (Root): وهو العنصر الذي ليس لديه والد أي أنه أعلى عنصر في الشجرة. وكل شجرة تحتوي على جذر واحد فقط. - الأوراق (Leaves): وهذه هي مجموعة العقد التي ليس لديها أي أولاد. أي أنها في طرف الشجرة. - القصن (Arc): هو الرابط الذي يربط العقد بين بعضها وهو ذي اتجاه واحد من الأعلى الى الأسفل. يختلف مفهوم الشجرة الثنائية Binary Tree حيث أن بعضها لا تحقق بعض الشروط مما يؤدي بها الى أن تكون عبارة عن قائمة عادية لكن الأنواع الأخرى تحتوي على شروط التي تجعلها شجرة فعالة بأكبر قدر ممكن و تسمى بـ Binary Search Tree وهذا اساس جميع ما يلي. تمثيل الشجرة الثنائية (Binayr Tree): ١. عن طريق المصفوفات (ArrayLists): وهذه الطريقة لديها مشاكلها كما ذكراناها سابقاً. ٢. عن طريق القوائم الثنائية (Linked lists): وهذه هي الطريق الأمثل لتمثيل الشجر. أكواد الشجرة الثنائية (Binary Tree): أ- إنشاء كلاس خاص بالعقد (Nodes) : هذا الكلاس يسمى بـ BSTNode وهو كالتالي: public class BSTNode { protected int key; protected BSTNode right, left; public BSTNode(){ this(0); } public BSTNode(int k){ this(k, null, null); } public BSTNode(int k, BSTNode r, BSTNode l){ this.key = k; this.right = r; this.left = l; } public void addRight(BSTNode r){ this.right = r; } public void addLeft(BSTNode l){ this.left = l; } } هذا الكلاس يحتوي على المتغيرات التالية: - Key: وهو من نوع و int وهو المكان الذي يتم حفظ البيانات داخله. - right & left: وهما القصنان (Arcs) والذان يربطان العقد بأبنيه اليمين والشمال. كما نرى فإننا نستطيع إنشاء عنصر من هذا الكلاس سواء كان في بيانات أو لا. لديه أولاد أولا. كما وأنه لدينا ميثودين نستخدمها لإضافة ابن لليمين أو لليسار. ب- انشاء كلاس الشجرة الثنائية (Binary Search Tree): public class BST { private BSTNode root = null; public BST(){ } public void clear(){ this.root =null; } public boolean isEmpty(){ return root == null; } public void visit(BSTNode n){ System.out.println(n.key + " "); } } هنا نقوم بانشاء الهيكلة الأساسية للشجرة ويحتوي على التالي: - root: الجذر وهو رأس الشجرة والوحيد الذي يربطنا بكامل عناصر الشكرة. وفي بداية انشاء الشجرة يكون الجذر فاضي. لدينا الكثير من الميثود لكن سنذكر هنا الأشياء الأساسية ونترك الباقي للدرس القادم بحيث أنها تحتوي على الكثير من التفاصيل: * clear(): تستخدم لتفريغ الشجرة من البيانات؛ وبما أن الجذر هو الرابط الوحيد لعناصر الشجرة فإن حذف الجذر سيؤدي الى حذف الشجرة كلها. *isEmpty(): وستخدم للاستعمال اذا ما كانت الشجرة خالية أو لا. ترسل true في حال كانت الشجرة خالية. *visit(): تستخدم لطباعة قيمة القيمة داخل العقد المرسل. خلال الشرح القادم إن شاء الله سنتطرق لاضافة وحذف عناصر من الشجرة، كذلك كيف نقوم بطباعة جميع عناصر الشجرة. اسأل الله العلي العظيم بأني وفقت لاصال المعلومة بابسط طريقة في حفظ الله
  4. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته تعرفنا في بداية سلسلة مواضيعنا على طريقة من طرق تخزين البيانات وهي القوائم سواء الأحادية أو الثنائية، كما ونعلم بأن هناك طريقة أخرى احفظ مجموعة البيانات وهي أبسط طريقة والتي تتمثل في المصفوفات سواء المصفوفات العادية أو مصفوفات القوائم. في هذا الدرس سنتعرف على أحد أفضل الطرق في حفظ البيانات والتي تعتبر من الأفضل من ناحية العرض أو البحث وغيرها من الأمور المتعلقة بهذه المجموعة من البيانات. كما هو الحال في القوائم فإن الأشجار لديها أكثر من نوع وكل واحد منهم له خواصة واستخداماته. في هذا الدرس سنتعرف على أحد أنواع الأشجار وهو الأشجار الثنائية. ماذا نقصد بالأشجار الثنائية (Binary Trees): هو نوع من أنواع تمثيل البيانات ويتكون من مكونين أساسيين هما العقد (Nodes) و الأقصان (Arcs). عندما نقوم بتمثيل هذه الشجرة فإننا نقلب مفهومها فيكون الجذر (Root) في الأعلى والأقصان في الأسفل. سميت بالأشجار الثنائية لأنه كل عقد لديه عقيدين يرتبط فيهم وهما أبنائه ولديه والد واحد. هناك بعض المصطلحات التي يجب أن نتعرف عليها قبل الشروع الى صلب الموضوع: - العقد (Node): وهو كل عنصر موجود في الشجرة، وهو المكان الذي يتم حفظ البيانات داخله. أنواع خاصة من العقد (Nodes): - الجذر (Root): وهو العنصر الذي ليس لديه والد أي أنه أعلى عنصر في الشجرة. وكل شجرة تحتوي على جذر واحد فقط. - الأوراق (Leaves): وهذه هي مجموعة العقد التي ليس لديها أي أولاد. أي أنها في طرف الشجرة. - القصن (Arc): هو الرابط الذي يربط العقد بين بعضها وهو ذي اتجاه واحد من الأعلى الى الأسفل. يختلف مفهوم الشجرة الثنائية Binary Tree حيث أن بعضها لا تحقق بعض الشروط مما يؤدي بها الى أن تكون عبارة عن قائمة عادية لكن الأنواع الأخرى تحتوي على شروط التي تجعلها شجرة فعالة بأكبر قدر ممكن و تسمى بـ Binary Search Tree وهذا اساس جميع ما يلي. تمثيل الشجرة الثنائية (Binayr Tree): ١. عن طريق المصفوفات (ArrayLists): وهذه الطريقة لديها مشاكلها كما ذكراناها سابقاً. ٢. عن طريق القوائم الثنائية (Linked lists): وهذه هي الطريق الأمثل لتمثيل الشجر. أكواد الشجرة الثنائية (Binary Tree): أ- إنشاء كلاس خاص بالعقد (Nodes) : هذا الكلاس يسمى بـ BSTNode وهو كالتالي: public class BSTNode { protected int key; protected BSTNode right, left; public BSTNode(){ this(0); } public BSTNode(int k){ this(k, null, null); } public BSTNode(int k, BSTNode r, BSTNode l){ this.key = k; this.right = r; this.left = l; } public void addRight(BSTNode r){ this.right = r; } public void addLeft(BSTNode l){ this.left = l; } } هذا الكلاس يحتوي على المتغيرات التالية: - Key: وهو من نوع و int وهو المكان الذي يتم حفظ البيانات داخله. - right & left: وهما القصنان (Arcs) والذان يربطان العقد بأبنيه اليمين والشمال. كما نرى فإننا نستطيع إنشاء عنصر من هذا الكلاس سواء كان في بيانات أو لا. لديه أولاد أولا. كما وأنه لدينا ميثودين نستخدمها لإضافة ابن لليمين أو لليسار. ب- انشاء كلاس الشجرة الثنائية (Binary Search Tree): public class BST { private BSTNode root = null; public BST(){ } public void clear(){ this.root =null; } public boolean isEmpty(){ return root == null; } public void visit(BSTNode n){ System.out.println(n.key + " "); } } هنا نقوم بانشاء الهيكلة الأساسية للشجرة ويحتوي على التالي: - root: الجذر وهو رأس الشجرة والوحيد الذي يربطنا بكامل عناصر الشكرة. وفي بداية انشاء الشجرة يكون الجذر فاضي. لدينا الكثير من الميثود لكن سنذكر هنا الأشياء الأساسية ونترك الباقي للدرس القادم بحيث أنها تحتوي على الكثير من التفاصيل: * clear(): تستخدم لتفريغ الشجرة من البيانات؛ وبما أن الجذر هو الرابط الوحيد لعناصر الشجرة فإن حذف الجذر سيؤدي الى حذف الشجرة كلها. *isEmpty(): وستخدم للاستعمال اذا ما كانت الشجرة خالية أو لا. ترسل true في حال كانت الشجرة خالية. *visit(): تستخدم لطباعة قيمة القيمة داخل العقد المرسل. خلال الشرح القادم إن شاء الله سنتطرق لاضافة وحذف عناصر من الشجرة، كذلك كيف نقوم بطباعة جميع عناصر الشجرة. اسأل الله العلي العظيم بأني وفقت لاصال المعلومة بابسط طريقة في حفظ الله
  5. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته استأنافا لما بدأنا عنه حول مواضيع هياكل البيانات سنتقطرق اليوم على نوع من أنواع الـ Methods واللتي لها علاقة بما يليها من مواضيع حول هياكل البيانات، كما ولها طبيقات رياضية كثيرة، والتي سنذكر منه ها هنا. ماذا نعني بـ Recursion ؟ المعنى الحرفي لكلمة Recursion هو الإستدعاء الذاتي وهذا المعنى لوحده يكفي لتعريفها، فبكل بساطة هو نوع من أنواع الـMethods والتي تقوم بنداء نفسها، ولهذه Method هيكلة وأساسيات لابد من توافرها في هذه Method. الشكل العام للـRecursion Methods: public void Test1(int i){ //أي عدد من اسطر الأكواد if(){//الحالة الأساسية والتي تقوم بإنهاء النداء الذاتي } Test1(/*any Value To pass*/);//النداء الذاتي //أي عدد من أسطر الأكواد } الأمر المهم الذي يجب عدم نسيانه هو الحالة الأساسية وهي عبارة عن شرط هذا الشرط يجعل لنداء الميثود لنفسها نهاية فليس من المعقول أن نقوم بنداء إلى مالنهاية. وهذا الشرط شبيه بالشرط الذي نضعه لإنها loop. وعند نسيان هذا الشرط سوف نواجه نداء لا نهائي من ثم انهيار التطبيق. لفهم معنى Recursion أفضل سوف نقفز مباشرة الى عدد من الأمثلة: ١. مضروب العدد Factorial (س!): ونعني به هنا هو مجموع حاصل ضرب جميع الأرقام من 1 الى العدد س. ويمكننا حل هذه العملية الحسابية بكل بساطة باستخدام التالي: public int Factorial (int i){ if(i == 0){ return 1; } else { return i * Factorial (i-1); } } هذه الميثود تأخذ عدد من نوع int وتقوم بإرجاع عدد من نوع int كذلك. أولا علينا تحديد الحالة الأساسية وفي هذه الحالة ستكون عندما يكون المضروب 0 فإن الناتج هو 1، أي عندما نرسل للميثود 0 فإنها تعطينا الناتج 1. في حال لم نقم بإرسال 0 الى الميثود فإنها ستذهب للخيار الآخر وهو نداء الميثود لنفسها لكن هذا النداء يحتوي على بعض الأمور التي يجب أخذها بعين الاعتبار، اولها بأننا قمنا بتحديث قيمة i فقمنا بإنقاصها 1؛ وهذا التحديث مهم حيث أنه في حال نسيانه سنواجه نداء لانهائي. الأمر الآخر بأننا قمنا بضرب العدد الحالي بسابقه. لنفرض بأننا قمنا بنداء هذه الميثود كالتالي: System.out.println(Factorial(4)); في النداء الأول بما أن 4 لا تساوي 0 فإننا سنقوم بالخطوة الثانية: 4 * Factorial(4-1) وهذا يرسلنا الى النداء الثاني، ومن هذا النداء يتولد لنا: 3 * Factorial(3-1) وندخل بعدها للنداء الثالث: 2 * Factorial(2-1) ومنه للنداء الرابع: 1 * Factorial(1-1) في النداء الخامس قيمة i تساوي 0 وهذا يحقق شرطنا فعندها سنقوم بإعادة 1 الى آخر نداء لنا أي أننا سنقوم بالتالي وليكن في البال بأننا في حال قمنا بأكثر من لنداء لنفس الميثود فإنا النداءات سوف تترب في stack أي أن آخر نداء سوف يحصل على الإجابة قبل غيره. وهذا ينتج لنا التالي: ٢. المجموع للعدد س: كما فعلنا في مثالنا السابق سنقوم هاهنا باستدعاء متكرر لنفس الميثود والتي ستقوم بدورها بجمع الأعداد من 0 إلى س. public int SUM(int i){ if(i == 0){ return 0; } else{ return i + SUM(i -1); } } ٣. طباعة الأعداد: هنا المستخدم يرسل رقم (س) ونقوم بطباعة الأرقام من س الى 1 بشكل تنازلي : public void printInt(int i){ if(i == 1){ System.out.println(1); }else{ System.out.println(i); printInt(i-1); } } أما في حال أردنا الطباعة بشكل تصاعدي فكل ما علينا فعله هو عكس آخر سطري كود لينتج لدينا: public void printInt(int i){ if(i == 1){ System.out.println(1); }else{ printInt(i-1); System.out.println(i); } } ملاحظة: نداء الميثود لنفسها لا يقتصر على الأرقام فحسب (كما سنرى في المواضيع القادمة بإذن الله) بل إنه أوسع من ذلك؛ لكن لتبسيط الأمور استعنت بمجموعة من الأمثلة تحتوي على أرقام فهي أبسط للفهم، كذلك في حال الملاحظة فإن نداء الميثود لنفسها شبيه بالـ loop الى حد بعيد. اتمنى من الله أني وفقت لإصال المعلومة في حفظ الله
  6. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته استأنافا لما بدأنا عنه حول مواضيع هياكل البيانات سنتقطرق اليوم على نوع من أنواع الـ Methods واللتي لها علاقة بما يليها من مواضيع حول هياكل البيانات، كما ولها طبيقات رياضية كثيرة، والتي سنذكر منه ها هنا. ماذا نعني بـ Recursion ؟ المعنى الحرفي لكلمة Recursion هو الإستدعاء الذاتي وهذا المعنى لوحده يكفي لتعريفها، فبكل بساطة هو نوع من أنواع الـMethods والتي تقوم بنداء نفسها، ولهذه Method هيكلة وأساسيات لابد من توافرها في هذه Method. الشكل العام للـRecursion Methods: public void Test1(int i){ //أي عدد من اسطر الأكواد if(){//الحالة الأساسية والتي تقوم بإنهاء النداء الذاتي } Test1(/*any Value To pass*/);//النداء الذاتي //أي عدد من أسطر الأكواد } الأمر المهم الذي يجب عدم نسيانه هو الحالة الأساسية وهي عبارة عن شرط هذا الشرط يجعل لنداء الميثود لنفسها نهاية فليس من المعقول أن نقوم بنداء إلى مالنهاية. وهذا الشرط شبيه بالشرط الذي نضعه لإنها loop. وعند نسيان هذا الشرط سوف نواجه نداء لا نهائي من ثم انهيار التطبيق. لفهم معنى Recursion أفضل سوف نقفز مباشرة الى عدد من الأمثلة: ١. مضروب العدد Factorial (س!): ونعني به هنا هو مجموع حاصل ضرب جميع الأرقام من 1 الى العدد س. ويمكننا حل هذه العملية الحسابية بكل بساطة باستخدام التالي: public int Factorial (int i){ if(i == 0){ return 1; } else { return i * Factorial (i-1); } } هذه الميثود تأخذ عدد من نوع int وتقوم بإرجاع عدد من نوع int كذلك. أولا علينا تحديد الحالة الأساسية وفي هذه الحالة ستكون عندما يكون المضروب 0 فإن الناتج هو 1، أي عندما نرسل للميثود 0 فإنها تعطينا الناتج 1. في حال لم نقم بإرسال 0 الى الميثود فإنها ستذهب للخيار الآخر وهو نداء الميثود لنفسها لكن هذا النداء يحتوي على بعض الأمور التي يجب أخذها بعين الاعتبار، اولها بأننا قمنا بتحديث قيمة i فقمنا بإنقاصها 1؛ وهذا التحديث مهم حيث أنه في حال نسيانه سنواجه نداء لانهائي. الأمر الآخر بأننا قمنا بضرب العدد الحالي بسابقه. لنفرض بأننا قمنا بنداء هذه الميثود كالتالي: System.out.println(Factorial(4)); في النداء الأول بما أن 4 لا تساوي 0 فإننا سنقوم بالخطوة الثانية: 4 * Factorial(4-1) وهذا يرسلنا الى النداء الثاني، ومن هذا النداء يتولد لنا: 3 * Factorial(3-1) وندخل بعدها للنداء الثالث: 2 * Factorial(2-1) ومنه للنداء الرابع: 1 * Factorial(1-1) في النداء الخامس قيمة i تساوي 0 وهذا يحقق شرطنا فعندها سنقوم بإعادة 1 الى آخر نداء لنا أي أننا سنقوم بالتالي وليكن في البال بأننا في حال قمنا بأكثر من لنداء لنفس الميثود فإنا النداءات سوف تترب في stack أي أن آخر نداء سوف يحصل على الإجابة قبل غيره. وهذا ينتج لنا التالي: ٢. المجموع للعدد س: كما فعلنا في مثالنا السابق سنقوم هاهنا باستدعاء متكرر لنفس الميثود والتي ستقوم بدورها بجمع الأعداد من 0 إلى س. public int SUM(int i){ if(i == 0){ return 0; } else{ return i + SUM(i -1); } } ٣. طباعة الأعداد: هنا المستخدم يرسل رقم (س) ونقوم بطباعة الأرقام من س الى 1 بشكل تنازلي : public void printInt(int i){ if(i == 1){ System.out.println(1); }else{ System.out.println(i); printInt(i-1); } } أما في حال أردنا الطباعة بشكل تصاعدي فكل ما علينا فعله هو عكس آخر سطري كود لينتج لدينا: public void printInt(int i){ if(i == 1){ System.out.println(1); }else{ printInt(i-1); System.out.println(i); } } ملاحظة: نداء الميثود لنفسها لا يقتصر على الأرقام فحسب (كما سنرى في المواضيع القادمة بإذن الله) بل إنه أوسع من ذلك؛ لكن لتبسيط الأمور استعنت بمجموعة من الأمثلة تحتوي على أرقام فهي أبسط للفهم، كذلك في حال الملاحظة فإن نداء الميثود لنفسها شبيه بالـ loop الى حد بعيد. اتمنى من الله أني وفقت لإصال المعلومة في حفظ الله
  7. بسم الله الرحمن الرحيم استكمالا لما بدئناه حول هياكل البيانات وكيفية تمثيلها واستعراضها في هذا الموضوع بإذن الله سنتطرق لنوع من أنواع استعراض البيانات المهمة، سبق وتحدثنا عن stack أما في هذا الموضوع فسنتكلم عن Queues (طابور البيانات). معنى Queues؟ الـ Queues عبارة عن طابور انتظار هذا الطابور يزداد حجمه بإضافة البيانات في آخره ويقل بإخراج البيانات من الأمام. المعنى العام شبيه بالطوابير العادية حيث أن من يأتي أولاً يخرج أولا والمتأخر يقف في آخر الصف. إذا على عكس stack فإن Queues لديه نهاية ومقدمة، النهاية لدخول البيانات والمقدمة لخروجها. ملاحظة: * خلال الشرح سأستخدم كلمة طابور للدلالة على Queue. ** كما وسنستخدم الكثير من Func الموجودة بالقوائم لذلك يجب فهم القوائم أولاً. ماذا نحتاج لإنشاء طابور Queues؟ هناك شبه كبير بين Queues والـ stuck الا أنه هناك أختلاف فجميع الـ func موجودة ولكن مع تغير طفيف: -إضافة عنصر جديد الى الطابور. - حذف أول عنصر في الطابور. - الاستعلام عن بداية الطابور. - تصفية الطابور Queues (حذف جميع العناصر). - فحص إذا ما كان الطابور خالٍ. الأكواد: لدينا طريقتين لتمثيل Queues أحدها باستخدام المصفوفات والآخرى باستخدام القوائم، وكما اسلفنا فإن استخدام القوائم يتفوق على المصفوفات من ناحية الوقت والمساحة المستخدمة (يمكن مراجعة الموضوع السابق) لذلك في شرحنا هذا سنستخدم القوائم الموجودة في: java.util.LinkedList - انشاء class باسم Queues: import java.util.LinkedList; public class Queue { LinkedList<Integer> queue = new LinkedList<Integer>(); } هنا قمنا بإنشاء class ليحتوي Queues وقمنا بإنشاء قائمة لحفظ الملفات داخلها. - تصفية الطابور من العناصر(clear): public void clear(){ queue.clear(); } وهنا queue.clear() تصفي القائمة من جميع العناصر فحينها تكون queue خالية من العناصر. - هل يحتوي الطابور على عناصر (isEmpty): public boolean isEmpty(){ return queue.isEmpty(); } هنا المنادي يبعث باستعلام يستفسر فيه اذا كان الطابور خالي أو يوجد به عناصر ويجاب عليه بـ: - True: في حال خلو الطابور Queue من العناصر. - False: في حال وجود عناصر في الطابور Queue (على الأقل عنصر واحد). - اضافة عنصر الى الطابور: public void enqueue(int a){ queue.add(a); } هذه func تستخدم لإضافة عنصر جديد الى الطابور، وكما اشرانا سابقاً فإن العناصر تضاف الى نهاية الطابور فقط. - الاستعلام عن أول عنصر في الطابور: public int firstEl(){ return queue.getFirst(); } هنا نقوم بالاستعلام عن أول عنصر في الطابور أي العنصر في مقدمة الطابور. - اخراج أول عنصر في الطابور: public int dequeue(){ return queue.removeFirst(); } هنا نقوم باخراج أول عنصر من الطابور ونعيد قيمته الى المنادي. أين نستخدم Queue: - تستخدم في تطبيقات خدمة العملاء فعندها نضيف آخر واصل في آخر الطابور ونقوم بخدمة الأول واخراجه من الطابور. - يتسخدم في ترتيب العمليات في CPU بعض الأحيان يستعان بـ Queue تقدم المهم أولاً. - الاستعلام عن العناصر في Tree (عبارة عن طريقة لحفظ البيانات كما القوائم والمصفوفات) وبعض عمليتها والتي سنتطرق لها لاحقاً. ملاحظة: * ملف الأكواد مدرج في المرفقات( Queue.java ). اتمنى من الله أن يكون قد وفقني لإصال المعلومة بشكل بسيط. في حفظ الله
  8. بسم الله الرحمن الرحيم استكمالا لما بدئناه حول هياكل البيانات وكيفية تمثيلها واستعراضها في هذا الموضوع بإذن الله سنتطرق لنوع من أنواع استعراض البيانات المهمة، سبق وتحدثنا عن stuck أما في هذا الموضوع فسنتكلم عن Queues (طابور البيانات). معنى Queues؟ الـ Queues عبارة عن طابور انتظار هذا الطابور يزداد حجمه بإضافة البيانات في آخره ويقل بإخراج البيانات من الأمام. المعنى العام شبيه بالطوابير العادية حيث أن من يأتي أولاً يخرج أولا والمتأخر يقف في آخر الصف. إذا على عكس stuck فإن Queues لديه نهاية ومقدمة، النهاية لدخول البيانات والمقدمة لخروجها. ملاحظة: * خلال الشرح سأستخدم كلمة طابور للدلالة على Queue. ** كما وسنستخدم الكثير من Func الموجودة بالقوائم لذلك يجب فهم القوائم أولاً. ماذا نحتاج لإنشاء طابور Queues؟ هناك شبه كبير بين Queues والـ stuck الا أنه هناك أختلاف فجميع الـ func موجودة ولكن مع تغير طفيف: -إضافة عنصر جديد الى الطابور. - حذف أول عنصر في الطابور. - الاستعلام عن بداية الطابور. - تصفية الطابور Queues (حذف جميع العناصر). - فحص إذا ما كان الطابور خالٍ. الأكواد: لدينا طريقتين لتمثيل Queues أحدها باستخدام المصفوفات والآخرى باستخدام القوائم، وكما اسلفنا فإن استخدام القوائم يتفوق على المصفوفات من ناحية الوقت والمساحة المستخدمة (يمكن مراجعة الموضوع السابق) لذلك في شرحنا هذا سنستخدم القوائم الموجودة في: java.util.LinkedList - انشاء class باسم Queues: import java.util.LinkedList; public class Queue { LinkedList<Integer> queue = new LinkedList<Integer>(); } هنا قمنا بإنشاء class ليحتوي Queues وقمنا بإنشاء قائمة لحفظ الملفات داخلها. - تصفية الطابور من العناصر(clear): public void clear(){ queue.clear(); } وهنا queue.clear() تصفي القائمة من جميع العناصر فحينها تكون queue خالية من العناصر. - هل يحتوي الطابور على عناصر (isEmpty): public boolean isEmpty(){ return queue.isEmpty(); } هنا المنادي يبعث باستعلام يستفسر فيه اذا كان الطابور خالي أو يوجد به عناصر ويجاب عليه بـ: - True: في حال خلو الطابور Queue من العناصر. - False: في حال وجود عناصر في الطابور Queue (على الأقل عنصر واحد). - اضافة عنصر الى الطابور: public void enqueue(int a){ queue.add(a); } هذه func تستخدم لإضافة عنصر جديد الى الطابور، وكما اشرانا سابقاً فإن العناصر تضاف الى نهاية الطابور فقط. - الاستعلام عن أول عنصر في الطابور: public int firstEl(){ return queue.getFirst(); } هنا نقوم بالاستعلام عن أول عنصر في الطابور أي العنصر في مقدمة الطابور. - اخراج أول عنصر في الطابور: public int dequeue(){ return queue.removeFirst(); } هنا نقوم باخراج أول عنصر من الطابور ونعيد قيمته الى المنادي. أين نستخدم Queue: - تستخدم في تطبيقات خدمة العملاء فعندها نضيف آخر واصل في آخر الطابور ونقوم بخدمة الأول واخراجه من الطابور. - يتسخدم في ترتيب العمليات في CPU بعض الأحيان يستعان بـ Queue تقدم المهم أولاً. - الاستعلام عن العناصر في Tree (عبارة عن طريقة لحفظ البيانات كما القوائم والمصفوفات) وبعض عمليتها والتي سنتطرق لها لاحقاً. ملاحظة: * ملف الأكواد مدرج في المرفقات( Queue.java ). اتمنى من الله أن يكون قد وفقني لإصال المعلومة بشكل بسيط. في حفظ الله تم ترقية هذا الطرح المميز الى صفحة المقالات
  9. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته استكمال لما بدأته من شرح هياكل البيانات القوائم أحادية الرابط كذلك قام الأخ @Hana Alalwi بشرح القوائم ثنائية الرابط هنا: في هذا الشرح سوف نتطرق للـ stacks. ماذا نعني بـ Stack؟ هي عبارة عن خط انتظار لمجموعة من البيانات، ما يميز هذا الخط بأنه مفتوح من اتجاه واحد فقط أي أن البيانات تدخل وتخرج من بوابة واحدة. يطلق على stack بـ LIFO وهذا الرمز يعني بأن آخر عنصر دخل إلى stack هو أول عنصر يقادرها، وهذا بديهي بما أنه لا يوجد لدينا الى فتحة واحدة لإدخال وإخراج العناصر. stack يمكننا أن نشبهها بحاملة الأقراص المدمجة فآخر قرص تقوم بوضعه هو أول قرص تقوم بإخراجه، أو كمجموعة كتب متراصة فوق بعضها البعض: لماذا نستخدم stack؟ يمكننا استخدام stack في عدة مواقف هذه بعض منها: - في حالة وجود بيانات ونريد عرضهم بالمعكوس، فعندها ندفع هذه العناصر الى stack وعند إخراجهم فإن آخر عنصر سوف يخرج أولاً. - تستخدم في لغات البرمجة لتتحقق من وجود تطابق بين الرموز المدخلة (مثلاً للتأكد بأن كل { لديها } ). - تستخدم لإضافة الأعداد الكبيرة باستخدام بعض الخوارزميات. ماهي الوظائف التي نحتاجها في stack ؟ push: وهذه العملية تستخدم لإضافة عناصر إلى الـ stack. pop: تستخدم لإخراج أول عنصر (آخر عنصر اضيف) من stack. isEmpty: تستخدم لفحص اذا ما كانت stack خالية أو لا. clear: تستخدم لحذف جميع العناصر في stack. topEl: تستخدم للاستعلام عن أول عنصر (آخر عنصر مضاف) في stack. ملاحظة: من الممكن أن تختلف المسميات للـ functions لكن هذه العناصر الأساسية لإنشاء stack كذلك سنستعين بالقوائم (القائمة أحادية الرابط) لإنشاء stack (يمكننا أن نقوم بإنشائها عن طريق المصفوفات). مثال على push و pop*: الأكواد: import java.util.LinkedList; public class Stack { private LinkedList<Integer> stackList = new LinkedList<Integer>(); } هذا السطر لإنشاء قائمة ونوع البيانات التي تحتويها integer (بالإمكان تغير نوع البيانات إلى أي نوع آخر) كما نلاحظة فإن القائمة خاصة (private) وذلك لمنع أي أوبجكت من خارج الـ class من التغير عليها. public Stack(){ } هذه كونستركتر يستخدم لإنشاء stack . public void clear(){ stackList.clear(); } تستخدم لحذف جميع العناصر الموجودة في stack. public boolean isEmpty(){ return stackList.isEmpty(); } تستخدم للإستعلام اذا ما كانت الـ stack خالية أو لا، في حال كانت خالية تقوم بإرسال (False) الى المنادي. في حال كانت غير خالية ترسل (True) للمنادي. public Integer topEl(){ if(!isEmpty()) return stackList.getLast(); else throw new java.util.EmptyStackException(); } تستخدم للإستعلام عن أول عنصر (آخر عنصر مضاف) موجود في stack وتقوم بإعادة قيمة العنصر الموجود في الأعلى للمنادي . كما نلاحظ فإننا قمنا بوضع شرط لتفادي الحصول على خطأ ففي حال عدم وجود عناصر نقوم بإرسال خطأ من أنفسنا. public void push(int i){ stackList.add(i); } اضافة عنصر جديد الى أعلى الـ stack. public Integer pop(){ if(!isEmpty()) { return stackList.removeLast(); }else throw new java.util.EmptyStackException(); } نستخدم هذه الـ function لإخراج أعلى عنصر موجود في stack وحذفه منها. كما في السابق ولتجنب الوقوع في stack خاليها فإننا سنتأكد من وجود عناصر ومن ثم نقوم بالعملية. ملاحظة: لقد استعنا بالكثير من وظائف القوائم لذلك يجب أن تلم بها لتتضح الصورة أكثر. لماذا لا نستخدم المصفوفات بدل القوائم؟ للوهلة الأولى يخيل إلينا بأنه لا يوجد أي فرق في استخدام المصفوفات بدل القوائم لكن في الحقيقة هناك الكثير من الفروقات الجوهدية والتي يتضح أثرها مع البيانات الكثيرة فكلما زاد حجم البيانات اتضح لدينا الفرق بينهما وهذه بعض الفروق: - هدر مساحة في الذاكرة: فعندما نقوم بانشاء مصفوفة حتى وإن كانت ArrayList فإن النظام يقوم بحجز مساحة (هذه المساحة ثابته لا تزيد ولا تنقص) لها وقد نكون لسنا بحاجة لهذه المساحة وبهذا نكون قد اهدرنا مكان في الذاكرة، علىالعكس من ذلك فإن القوائم لا تشغل من الذاكرة سوى المساحة التي تحتاجة بدون الحاجة للحجز بتاتاً. - الحاجة لإعادة نسخ البيانات: كما اشرنا سابقاً فإن حجم المصفوفة ثابت لا يتغيرفي حال امتلائ المصفوفة والحاجة الى مساحة أكبر سيقوم النظام بعمل مصفوفة جديدة ذات حجم أكبر وينسخالبيانات السابقة اليها وفي هذا هدر للوقت حيث أن نسخ البيانات لو كانت كثيرة يحتاجة، وكما اشرنا سابقا فإن القوائم لا تحتاج الى هذا الأمر. هذا مثال بسيط سنستخدم فيه stack التي قمنا بإنشائها لنعسك مجموعة من الأرقام المعطاه: public static void main(String[] args) { Stack myStack = new Stack(); int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.out.println("العنصار بترتيبها في المصفوفة:"); for(int i =0; i < 9; i++){ myStack.push(a[i]); System.out.print(myStack.topEl() + " "); } System.out.println("\n"+"العناصر بعد وضعها في stack:"); for(int i = 0; i < 9; i++) System.out.print(myStack.pop() + " "); } في البداية قمنا بإضافة عناصر المصفوفة الى Stack وكذلك وفي نفس loop قمنا بطباعة أعلى عنصر موجود وهو آخر عنصر قمنا بضافته ومن ثما قمنا باخراج العناصر من stack واحد تلو الآخر مع طباعتهم. وهذا هو الناتج النهائي لدينا: اتمنى من أن أكون وفقت لإيصال المعلومة بشكل سهل وسلس ملاحظة: ملف الأكواد مرفق في الأسفل ويحتوي على التعليقات التوضيحية. في أمان الله Stack.java *المصدر: Data Structures And Algorithms In Java, Adam Drozdek , Second Edition
  10. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته استكمال لما بدأته من شرح هياكل البيانات القوائم أحادية الرابط كذلك قام الأخ @Hana Alalwi بشرح القوائم ثنائية الرابط هنا: في هذا الشرح سوف نتطرق للـ stacks. ماذا نعني بـ Stack؟ هي عبارة عن خط انتظار لمجموعة من البيانات، ما يميز هذا الخط بأنه مفتوح من اتجاه واحد فقط أي أن البيانات تدخل وتخرج من بوابة واحدة. يطلق على stack بـ LIFO وهذا الرمز يعني بأن آخر عنصر دخل إلى stack هو أول عنصر يقادرها، وهذا بديهي بما أنه لا يوجد لدينا الى فتحة واحدة لإدخال وإخراج العناصر. stack يمكننا أن نشبهها بحاملة الأقراص المدمجة فآخر قرص تقوم بوضعه هو أول قرص تقوم بإخراجه، أو كمجموعة كتب متراصة فوق بعضها البعض: لماذا نستخدم stack؟ يمكننا استخدام stack في عدة مواقف هذه بعض منها: - في حالة وجود بيانات ونريد عرضهم بالمعكوس، فعندها ندفع هذه العناصر الى stack وعند إخراجهم فإن آخر عنصر سوف يخرج أولاً. - تستخدم في لغات البرمجة لتتحقق من وجود تطابق بين الرموز المدخلة (مثلاً للتأكد بأن كل { لديها } ). - تستخدم لإضافة الأعداد الكبيرة باستخدام بعض الخوارزميات. ماهي الوظائف التي نحتاجها في stack ؟ push: وهذه العملية تستخدم لإضافة عناصر إلى الـ stack. pop: تستخدم لإخراج أول عنصر (آخر عنصر اضيف) من stack. isEmpty: تستخدم لفحص اذا ما كانت stack خالية أو لا. clear: تستخدم لحذف جميع العناصر في stack. topEl: تستخدم للاستعلام عن أول عنصر (آخر عنصر مضاف) في stack. ملاحظة: من الممكن أن تختلف المسميات للـ functions لكن هذه العناصر الأساسية لإنشاء stack كذلك سنستعين بالقوائم (القائمة أحادية الرابط) لإنشاء stack (يمكننا أن نقوم بإنشائها عن طريق المصفوفات). مثال على push و pop*: الأكواد: import java.util.LinkedList; public class Stack { private LinkedList<Integer> stackList = new LinkedList<Integer>(); } هذا السطر لإنشاء قائمة ونوع البيانات التي تحتويها integer (بالإمكان تغير نوع البيانات إلى أي نوع آخر) كما نلاحظة فإن القائمة خاصة (private) وذلك لمنع أي أوبجكت من خارج الـ class من التغير عليها. public Stack(){ } هذه كونستركتر يستخدم لإنشاء stack . public void clear(){ stackList.clear(); } تستخدم لحذف جميع العناصر الموجودة في stack. public boolean isEmpty(){ return stackList.isEmpty(); } تستخدم للإستعلام اذا ما كانت الـ stack خالية أو لا، في حال كانت خالية تقوم بإرسال (False) الى المنادي. في حال كانت غير خالية ترسل (True) للمنادي. public Integer topEl(){ if(!isEmpty()) return stackList.getLast(); else throw new java.util.EmptyStackException(); } تستخدم للإستعلام عن أول عنصر (آخر عنصر مضاف) موجود في stack وتقوم بإعادة قيمة العنصر الموجود في الأعلى للمنادي . كما نلاحظ فإننا قمنا بوضع شرط لتفادي الحصول على خطأ ففي حال عدم وجود عناصر نقوم بإرسال خطأ من أنفسنا. public void push(int i){ stackList.add(i); } اضافة عنصر جديد الى أعلى الـ stack. public Integer pop(){ if(!isEmpty()) { return stackList.removeLast(); }else throw new java.util.EmptyStackException(); } نستخدم هذه الـ function لإخراج أعلى عنصر موجود في stack وحذفه منها. كما في السابق ولتجنب الوقوع في stack خاليها فإننا سنتأكد من وجود عناصر ومن ثم نقوم بالعملية. ملاحظة: لقد استعنا بالكثير من وظائف القوائم لذلك يجب أن تلم بها لتتضح الصورة أكثر. لماذا لا نستخدم المصفوفات بدل القوائم؟ للوهلة الأولى يخيل إلينا بأنه لا يوجد أي فرق في استخدام المصفوفات بدل القوائم لكن في الحقيقة هناك الكثير من الفروقات الجوهدية والتي يتضح أثرها مع البيانات الكثيرة فكلما زاد حجم البيانات اتضح لدينا الفرق بينهما وهذه بعض الفروق: - هدر مساحة في الذاكرة: فعندما نقوم بانشاء مصفوفة حتى وإن كانت ArrayList فإن النظام يقوم بحجز مساحة (هذه المساحة ثابته لا تزيد ولا تنقص) لها وقد نكون لسنا بحاجة لهذه المساحة وبهذا نكون قد اهدرنا مكان في الذاكرة، علىالعكس من ذلك فإن القوائم لا تشغل من الذاكرة سوى المساحة التي تحتاجة بدون الحاجة للحجز بتاتاً. - الحاجة لإعادة نسخ البيانات: كما اشرنا سابقاً فإن حجم المصفوفة ثابت لا يتغيرفي حال امتلائ المصفوفة والحاجة الى مساحة أكبر سيقوم النظام بعمل مصفوفة جديدة ذات حجم أكبر وينسخالبيانات السابقة اليها وفي هذا هدر للوقت حيث أن نسخ البيانات لو كانت كثيرة يحتاجة، وكما اشرنا سابقا فإن القوائم لا تحتاج الى هذا الأمر. هذا مثال بسيط سنستخدم فيه stack التي قمنا بإنشائها لنعسك مجموعة من الأرقام المعطاه: public static void main(String[] args) { Stack myStack = new Stack(); int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.out.println("العنصار بترتيبها في المصفوفة:"); for(int i =0; i < 9; i++){ myStack.push(a[i]); System.out.print(myStack.topEl() + " "); } System.out.println("\n"+"العناصر بعد وضعها في stack:"); for(int i = 0; i < 9; i++) System.out.print(myStack.pop() + " "); } في البداية قمنا بإضافة عناصر المصفوفة الى Stack وكذلك وفي نفس loop قمنا بطباعة أعلى عنصر موجود وهو آخر عنصر قمنا بضافته ومن ثما قمنا باخراج العناصر من stack واحد تلو الآخر مع طباعتهم. وهذا هو الناتج النهائي لدينا: اتمنى من أن أكون وفقت لإيصال المعلومة بشكل سهل وسلس ملاحظة: ملف الأكواد مرفق في الأسفل ويحتوي على التعليقات التوضيحية. في أمان الله Stack.java *المصدر: Data Structures And Algorithms In Java, Adam Drozdek , Second Edition تم ترقية هذا الطرح المميز الى صفحة المقالات
  11. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته ...، أولاً اعتذر جداً عن التأخر في طرح الشرح الذي كان من المفترض يكون بعد أسبوع من الموضوع السابق تطرقنا في شرحنا السابق الى مفهوم القائمة أحادية الرابط وشرحنا استخداماته وطريقة عملها نظريا شرحنا هذا سيكون عمليا من الدرجة الأولى بحيث سيكون كله حول الأكواد وتفصيلها. ١. إنشاء العقدة Node: كما نعلم بأن Node هي الوحدة الأساسية لبناء قائمة أحادية الرابط أو القوائم بشكل عام ونستطيع بأن نصفها بأنها الحجر الذي يستخدم لبنائها. لذلك يجب علينا بنائها ابتداءً. public class SLLNode { public int info; public SLLNode next; public SLLNode(int i){ this(i, null); } public SLLNode(int i, SLLNode n){ this.info = i; this.next = n; } } هذا الكلاس والذي سمي بـ SSLNode هو الذي يصف لنا كل عقدة على حدى. وكل عقدة تتكون من info وهو المتغير الذي يحتوي على البيانات (المعلومة) وفي حالتنا هذه تكون البيانات من نوع int. وكذلك next وهو عبارة عن الرابط الذي يصل العقدة الحالية بالعقدة التي تليها ومن الطبيعي أن يكون من نوع SLLNode. هذا الكلاس يحتوي على two Constructor وهما عبارة عن وظائف تستخدم لإنشاء العقد Nodes. الأولى تستخدم لإنشاء عقدة لكنها لا ترتبط بأخرى كما نرى فيها فقد قمنا باستدعاء الميثود الثانية وقمنا بوضع null مكان الرابط. أما الثانية فتنشأ عقدة ترتبط بأخرى. (قمت بتفصيلها في الموضوع السابق) ٢. إنشاء القائمة أحادية الرابط: الكلاس السابق يمكننا من إنشاء عقدة واحدة لكن في هذا الكلاس سنتمكن من إنشاء قائمة كاملة تتكون من عدد من العقد Nodes. أ. مؤشر لأول عنصر وآخر عنصر بالقائمة: لكي نسهل عملية التنقل داخل القائمة سنقوم بإنشاء مؤشرين أحدهما يشير إلى أول عنصر بالقائمة (head: الرأس)، والآخر يشير إلى آخر عنصر بالقائمة (tail: الذيل). هذين المؤشرين عبارة عن متغيريين من نوع SSNode. protected SLLNode head, tail; في بداية إنشاء القائمة (أو في حالة كانت القائمة خالية) فإن هذين العنصريين يكونان خاليين: public SLList(){ head = tail = null; } كما يتبين في هذه الـ Constructor. ب. فحص القائمة إذا كانت خالية أو لا: هذه الـ Method مهمة جداً ولها أستخدام في جل الـ Methods الأخرى، ومهمتها هي فحص إذا ما كانت القائمة خالية أو لا. وتقوم بإرجاع قيمة Boolean إما true أو false. public boolean isEmpty(){ return head == null; //or return tail == null; } بكل بساطة في حال كان الرأس خالي فإن القائمة خالية كذلك، بالإمكان الإستعانة بالذيل كذلك فمن المستحيل أن يكون أحدهما خالي دون الآخر. ب. إضافة عنصر جديد إلى الرأس (بداية القائمة): public void addToHead(int el){ head.next = head; head = new SLLNode(el); if (tail = null){ tail = head; } } أولا نجعل الرأس يشير إلى نفسه لكي نربط العنصر الجديد بالعنصر الأول السابق. عندها نقوم بإنشاء عنصر Node ونجعله الرأس الجديد في حالة كانت القائمة خالية نجعل الذيل يشير إلى نفس العنصر الذي يشير له الرأس. ملاحظة: عندما يشير الرأس والذيل إلى نفس العنصر فهذا يعني بأن لدينا عنصر واحد فقط في القائمة. ج. إضافة عنصر جديد إلى الذيل (نهاية القائمة): public void addToTail(int el){ if(!isEmpty()){ tail.next = new SLLNode(el); tail = tail.next; }else{ head = tail = new SLLNode(el); } } نفحص القائمة إذا كانت خالية: - في حالة كانت غير خالية: ننشأ عنصر جديد ونجعل الذيل الحالي يشير إليه، ومن ثم نجعله الذيل الجديد. - في حال كانت القائمة خالية: ننشأ عنصر جديد ونجعل الرأس والذيل يشيران إليه. د. حذف العنصر الموجود بالرأس (أول عنصر): public int deleteHead(){ int el = head.info; if(head == tail){ head = tail = null; }else{ head = head.next; } return el; } هذه الـ method تقوم بحذف العنصر الموجود بالرأس ومن ثم ترجع قيمته. أولا نحفظ القيمة داخل متغير el لكي لا تضيع بعد حذف العنصر. ومن ثم نفحص إذا كانت القائمة: - تحتوي على عنصر واحد: فعندها نجعل الرأس والذيل يسيران إلى لا شيء null وتكون القائمة حينها خالية. - تحتوي على أكثر من عنصر: فعندها نجعل العنصر الذي بعد الرأس هو الرأس الجديد، وفي هذه الحالة سيكون العنصر لا يوجود ما يشير إليه ويحذف. ومن ثم ترجع القيمة التي تم حذفها. هـ- حذف العنصر الموجود بالذيل (آخر عنصر): public int deleteTail(){ int el = tail.info; if(tail == head){ head = tail = null; }else{ SLLNode temp; for(temp = head; temp.next != tail; temp = temp.next); tail = temp; tail.next = null; } return el; } كما في السابق نحتفظ بقيمة العنصر لكي نقوم بإعادته بعد الإنتهاء من عملية الحذف لكن الحذف من الذيل ليس بسهولة الحذف من الرأس. نفحص التالي: - في حالة أن القائمة تحتوي على عنصر واحد فقط: فعندها نحذف القائمة كلها بجعل الرأس والذيل null. - في حالة أن القائمة تحتوي على أكثر من عنصر: أولا علينا أن نبحث عن العنصر السابق للذيل (العنصر قبل الأخير)، وذلك بأستخدام for loop قبلها علينا أن ننشأ مؤشر مؤقت باسم temp وفي بداية الـ loop نجعله يشير إلى الرأس، وننقله من عنصر إلى آخر إلى أن يصل إلى العنصر ما قبل الأخير وذلك بفحص إذا ما كان temp.next أي العنصر الذي يليه هو tail فعندها نكون وصلنا إلى العنصر ما قبل الأخير. بعد أن نجد العنصر ما قبل الأخير نجعل الذيل الجديد ومن ثم نجعل الذيل يشير إلى null لكي يتم حذف العنصر الأخير تماماً. و. طباعة جميع عناصر القائمة: public void printAll(){ System.out.print("["); for(SLLNode temp = head; temp != null; temp = temp.next){ System.out.print(temp.info + " "); } System.out.println("]"); } كما شرحنا سابقا نستخدم for loop للتنقل بين عناصر القائمة وطباعة قيمة كل عنصر على الشاشة. ز. فحص وجود العنصر في القائمة أو لا: public boolean isInList(int el){ if(isEmpty()){ return false; }else{ SLLNode temp; for(temp = head; temp.info != el && temp.next != null; temp = temp.next); return temp != null; } } للبحث عن وجود قيمة داخل القائمة من عدمه يجب علينا أن نتنقل داخل القائمة عنصر بعنصر، وسنستخدم الأسلوب السابق وهو باستخدام for loop. ولكن قبلها يجب علينا أن نتأكد من أن القائمة غير خالية. الـ for loop ستتوقف في إحدى حالتين في حالة وجدة العنصر داخل القائمة وذلك ببطلان الشرط (tmep.info != el) أو في حالة وصول temp إلى نهاية القائمة. عندها سنرجع القية المعاكسة لهذا الشرط (tmep != null) في حالة وصول الـ temp إلى نهاية القائمة بدون أن يجد العنصر فعندا سيكون temp يساوي null وفي هذه الحالة سيرسل false أما في حالة إجاد العنصر فعندها temp لن يكون null ويرسل true. ح. حذف عنصر من القائمة: public void delete(int el){ if(!isEmpty() && isInList()){ if(head == tail && head.info == el){ head = tail = null; }else if(el == head.info){ head = head.next; }else{ SLLNode pre, temp; for(temp = head.next, pre = head; temp.info != el; pre = temp, temp = temp.next); if(temp != null){ pre.next = temp.next; if(temp == tail){ tail = pre; } } } } } في حالة أردنا حذف عنصر من القائمة علينا أن نفكر في جميع الإحتمالات وهي كالتالي: - هل القائمة غير خالية؟! وهل العنصر في القائمة ولتححق سنستخدم isEmpty و isInList، في حالة لم تكن القائمة خالية وكان العنصر بالقائمة ننتقل للخطوة التالية: * هل القائمة تحتوي على عنصر واحد؟!: في هذه الحالة نقوم بحذف هذا العنصر ونجعل القائمة خالية. * هل القائمة تحتوي على أكثر من عنصر والعنصر المراد حذفة بالرأس؟: عنذها نحذف الرأس كما أشرنا في الفقرة (د). *الخيار الأخير هو أن العنصر موجود داخل القائمة: في هذه الحالة نحتاج للبحث عن العنصر بنفس الأسلوب السابق لكننا هنا على خلاف الخطوات السابقة نحتاج إلى متغيرين مؤقتين أحدهما يشير إلى العنصر المراد حذفه والآخر يشير إلى العنصر الذي يسبقه. ولكي نقوم بذلك فعند إنشاء temp وهو العنصر المؤقت الذي سنبحث عن طريقه للعنصر المراد حذفه نجعل pre العنصر الذي يسبقه وذلك كما يلي: pre = head , temp = pre.next كما نعلم فنحن لسنا بحاجة لفحص الرأس فقد تأكدنا من أن العنصر ليس بالرأس. تنتهي for loop في حال إجاد العنصر المراد حذف مع العلم بأننا لن نصل إلى نهاية القائمة لأننا تأكدنا من أن العنصر موجود في القائمة في الخطوة الأولى. وعند نهاية كل دورة من for loop نقوم بتحديث قيمتي pre and temp. بعد ذلك نجعل الـ pre يشير للعنصر الذي يلي temp أي يشير للعنصر الذي بعد العنصر المراد حذفه عندها لن يكون هناك ما يشير إليه ويحذف. ** تأكد أخير في حال أن العنصر الذي حذفنها هو الأخير فعندها يجب أن نجعل الذيل يشير إلى null. #شرح مفصل للـ for loop المستخدمة مع القوائم بشكل عام: لأهميتها القصوى سوف أفصلها أكثر، إبتداءً بالشكل العام لها: for (SLLNode temp = head; temp != null; temp = temp.next); أولا نقوم بإنشاء متغير مؤقت ونسميه بأي أسم كان هذا المتغير في بداية الـ loop سيشير لأول عنصر بالقائمة وهو الرأس head بعد ذلك يتأكد من تحقق الشرط وهو أن هذا المتغير لا يساوي null (أي أنه غير خالي)، ومن ثم يحدث قيمة المتغير وذلك بجعله العنصر التالي له عن طريق (temp = temp.next) ومن ثم يقوم بالتأكد من الشرط مجدداً هكذا حتى يصل إلى أن temp مساوي لـ tail وعندها يسنتقل للعنصر الذي يليه وهو في الحقيقة null لأنه tail.next دائما يساوي null وهكذا تنتهي for loop. ملاحظة: قمت بوضع ملفات للـ SLLNode and SLList classes في المرفقات تحتوي على تعليقات توضيحية. أتمنى من الله أن أكون قد وفقت في تفصيل موضوعنا هذا في حفظ الله إلى لقاء آخر SLList.java SLLNode.java
  12. float max = 0, min = 0; int maxIndex = 0, minIndex = 0; for(int s = 0; s<avg.length; s++){ if(avg[s] > max){ max = avg[s]; maxIndex = s; } } for(int s = 0; s<avg.length; s++){ if(min == 0 ){ min = avg[s]; minIndex = s; }else if(min > avg[s]){ min = avg[s]; minIndex = s; } } System.out.println("Highest Average is " + name[maxIndex]); System.out.println("lowest Average is "+name[minIndex]); طبعا هذا خليه في الخارج مو في داخل for loop الأولى.
  13. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته ...، أولاً اعتذر جداً عن التأخر في طرح الشرح الذي كان من المفترض يكون بعد أسبوع من الموضوع السابق تطرقنا في شرحنا السابق الى مفهوم القائمة أحادية الرابط وشرحنا استخداماته وطريقة عملها نظريا شرحنا هذا سيكون عمليا من الدرجة الأولى بحيث سيكون كله حول الأكواد وتفصيلها. ١. إنشاء العقدة Node: كما نعلم بأن Node هي الوحدة الأساسية لبناء قائمة أحادية الرابط أو القوائم بشكل عام ونستطيع بأن نصفها بأنها الحجر الذي يستخدم لبنائها. لذلك يجب علينا بنائها ابتداءً. public class SLLNode { public int info; public SLLNode next; public SLLNode(int i){ this(i, null); } public SLLNode(int i, SLLNode n){ this.info = i; this.next = n; } } هذا الكلاس والذي سمي بـ SSLNode هو الذي يصف لنا كل عقدة على حدى. وكل عقدة تتكون من info وهو المتغير الذي يحتوي على البيانات (المعلومة) وفي حالتنا هذه تكون البيانات من نوع int. وكذلك next وهو عبارة عن الرابط الذي يصل العقدة الحالية بالعقدة التي تليها ومن الطبيعي أن يكون من نوع SLLNode. هذا الكلاس يحتوي على two Constructor وهما عبارة عن وظائف تستخدم لإنشاء العقد Nodes. الأولى تستخدم لإنشاء عقدة لكنها لا ترتبط بأخرى كما نرى فيها فقد قمنا باستدعاء الميثود الثانية وقمنا بوضع null مكان الرابط. أما الثانية فتنشأ عقدة ترتبط بأخرى. (قمت بتفصيلها في الموضوع السابق) ٢. إنشاء القائمة أحادية الرابط: الكلاس السابق يمكننا من إنشاء عقدة واحدة لكن في هذا الكلاس سنتمكن من إنشاء قائمة كاملة تتكون من عدد من العقد Nodes. أ. مؤشر لأول عنصر وآخر عنصر بالقائمة: لكي نسهل عملية التنقل داخل القائمة سنقوم بإنشاء مؤشرين أحدهما يشير إلى أول عنصر بالقائمة (head: الرأس)، والآخر يشير إلى آخر عنصر بالقائمة (tail: الذيل). هذين المؤشرين عبارة عن متغيريين من نوع SSNode. protected SLLNode head, tail; في بداية إنشاء القائمة (أو في حالة كانت القائمة خالية) فإن هذين العنصريين يكونان خاليين: public SLList(){ head = tail = null; } كما يتبين في هذه الـ Constructor. ب. فحص القائمة إذا كانت خالية أو لا: هذه الـ Method مهمة جداً ولها أستخدام في جل الـ Methods الأخرى، ومهمتها هي فحص إذا ما كانت القائمة خالية أو لا. وتقوم بإرجاع قيمة Boolean إما true أو false. public boolean isEmpty(){ return head == null; //or return tail == null; } بكل بساطة في حال كان الرأس خالي فإن القائمة خالية كذلك، بالإمكان الإستعانة بالذيل كذلك فمن المستحيل أن يكون أحدهما خالي دون الآخر. ب. إضافة عنصر جديد إلى الرأس (بداية القائمة): public void addToHead(int el){ head.next = head; head = new SLLNode(el); if (tail = null){ tail = head; } } أولا نجعل الرأس يشير إلى نفسه لكي نربط العنصر الجديد بالعنصر الأول السابق. عندها نقوم بإنشاء عنصر Node ونجعله الرأس الجديد في حالة كانت القائمة خالية نجعل الذيل يشير إلى نفس العنصر الذي يشير له الرأس. ملاحظة: عندما يشير الرأس والذيل إلى نفس العنصر فهذا يعني بأن لدينا عنصر واحد فقط في القائمة. ج. إضافة عنصر جديد إلى الذيل (نهاية القائمة): public void addToTail(int el){ if(!isEmpty()){ tail.next = new SLLNode(el); tail = tail.next; }else{ head = tail = new SLLNode(el); } } نفحص القائمة إذا كانت خالية: - في حالة كانت غير خالية: ننشأ عنصر جديد ونجعل الذيل الحالي يشير إليه، ومن ثم نجعله الذيل الجديد. - في حال كانت القائمة خالية: ننشأ عنصر جديد ونجعل الرأس والذيل يشيران إليه. د. حذف العنصر الموجود بالرأس (أول عنصر): public int deleteHead(){ int el = head.info; if(head == tail){ head = tail = null; }else{ head = head.next; } return el; } هذه الـ method تقوم بحذف العنصر الموجود بالرأس ومن ثم ترجع قيمته. أولا نحفظ القيمة داخل متغير el لكي لا تضيع بعد حذف العنصر. ومن ثم نفحص إذا كانت القائمة: - تحتوي على عنصر واحد: فعندها نجعل الرأس والذيل يسيران إلى لا شيء null وتكون القائمة حينها خالية. - تحتوي على أكثر من عنصر: فعندها نجعل العنصر الذي بعد الرأس هو الرأس الجديد، وفي هذه الحالة سيكون العنصر لا يوجود ما يشير إليه ويحذف. ومن ثم ترجع القيمة التي تم حذفها. هـ- حذف العنصر الموجود بالذيل (آخر عنصر): public int deleteTail(){ int el = tail.info; if(tail == head){ head = tail = null; }else{ SLLNode temp; for(temp = head; temp.next != tail; temp = temp.next); tail = temp; tail.next = null; } return el; } كما في السابق نحتفظ بقيمة العنصر لكي نقوم بإعادته بعد الإنتهاء من عملية الحذف لكن الحذف من الذيل ليس بسهولة الحذف من الرأس. نفحص التالي: - في حالة أن القائمة تحتوي على عنصر واحد فقط: فعندها نحذف القائمة كلها بجعل الرأس والذيل null. - في حالة أن القائمة تحتوي على أكثر من عنصر: أولا علينا أن نبحث عن العنصر السابق للذيل (العنصر قبل الأخير)، وذلك بأستخدام for loop قبلها علينا أن ننشأ مؤشر مؤقت باسم temp وفي بداية الـ loop نجعله يشير إلى الرأس، وننقله من عنصر إلى آخر إلى أن يصل إلى العنصر ما قبل الأخير وذلك بفحص إذا ما كان temp.next أي العنصر الذي يليه هو tail فعندها نكون وصلنا إلى العنصر ما قبل الأخير. بعد أن نجد العنصر ما قبل الأخير نجعل الذيل الجديد ومن ثم نجعل الذيل يشير إلى null لكي يتم حذف العنصر الأخير تماماً. و. طباعة جميع عناصر القائمة: public void printAll(){ System.out.print("["); for(SLLNode temp = head; temp != null; temp = temp.next){ System.out.print(temp.info + " "); } System.out.println("]"); } كما شرحنا سابقا نستخدم for loop للتنقل بين عناصر القائمة وطباعة قيمة كل عنصر على الشاشة. ز. فحص وجود العنصر في القائمة أو لا: public boolean isInList(int el){ if(isEmpty()){ return false; }else{ SLLNode temp; for(temp = head; temp.info != el && temp.next != null; temp = temp.next); return temp != null; } } للبحث عن وجود قيمة داخل القائمة من عدمه يجب علينا أن نتنقل داخل القائمة عنصر بعنصر، وسنستخدم الأسلوب السابق وهو باستخدام for loop. ولكن قبلها يجب علينا أن نتأكد من أن القائمة غير خالية. الـ for loop ستتوقف في إحدى حالتين في حالة وجدة العنصر داخل القائمة وذلك ببطلان الشرط (tmep.info != el) أو في حالة وصول temp إلى نهاية القائمة. عندها سنرجع القية المعاكسة لهذا الشرط (tmep != null) في حالة وصول الـ temp إلى نهاية القائمة بدون أن يجد العنصر فعندا سيكون temp يساوي null وفي هذه الحالة سيرسل false أما في حالة إجاد العنصر فعندها temp لن يكون null ويرسل true. ح. حذف عنصر من القائمة: public void delete(int el){ if(!isEmpty() && isInList()){ if(head == tail && head.info == el){ head = tail = null; }else if(el == head.info){ head = head.next; }else{ SLLNode pre, temp; for(temp = head.next, pre = head; temp.info != el; pre = temp, temp = temp.next); if(temp != null){ pre.next = temp.next; if(temp == tail){ tail = pre; } } } } } في حالة أردنا حذف عنصر من القائمة علينا أن نفكر في جميع الإحتمالات وهي كالتالي: - هل القائمة غير خالية؟! وهل العنصر في القائمة ولتححق سنستخدم isEmpty و isInList، في حالة لم تكن القائمة خالية وكان العنصر بالقائمة ننتقل للخطوة التالية: * هل القائمة تحتوي على عنصر واحد؟!: في هذه الحالة نقوم بحذف هذا العنصر ونجعل القائمة خالية. * هل القائمة تحتوي على أكثر من عنصر والعنصر المراد حذفة بالرأس؟: عنذها نحذف الرأس كما أشرنا في الفقرة (د). *الخيار الأخير هو أن العنصر موجود داخل القائمة: في هذه الحالة نحتاج للبحث عن العنصر بنفس الأسلوب السابق لكننا هنا على خلاف الخطوات السابقة نحتاج إلى متغيرين مؤقتين أحدهما يشير إلى العنصر المراد حذفه والآخر يشير إلى العنصر الذي يسبقه. ولكي نقوم بذلك فعند إنشاء temp وهو العنصر المؤقت الذي سنبحث عن طريقه للعنصر المراد حذفه نجعل pre العنصر الذي يسبقه وذلك كما يلي: pre = head , temp = pre.next كما نعلم فنحن لسنا بحاجة لفحص الرأس فقد تأكدنا من أن العنصر ليس بالرأس. تنتهي for loop في حال إجاد العنصر المراد حذف مع العلم بأننا لن نصل إلى نهاية القائمة لأننا تأكدنا من أن العنصر موجود في القائمة في الخطوة الأولى. وعند نهاية كل دورة من for loop نقوم بتحديث قيمتي pre and temp. بعد ذلك نجعل الـ pre يشير للعنصر الذي يلي temp أي يشير للعنصر الذي بعد العنصر المراد حذفه عندها لن يكون هناك ما يشير إليه ويحذف. ** تأكد أخير في حال أن العنصر الذي حذفنها هو الأخير فعندها يجب أن نجعل الذيل يشير إلى null. #شرح مفصل للـ for loop المستخدمة مع القوائم بشكل عام: لأهميتها القصوى سوف أفصلها أكثر، إبتداءً بالشكل العام لها: for (SLLNode temp = head; temp != null; temp = temp.next); أولا نقوم بإنشاء متغير مؤقت ونسميه بأي أسم كان هذا المتغير في بداية الـ loop سيشير لأول عنصر بالقائمة وهو الرأس head بعد ذلك يتأكد من تحقق الشرط وهو أن هذا المتغير لا يساوي null (أي أنه غير خالي)، ومن ثم يحدث قيمة المتغير وذلك بجعله العنصر التالي له عن طريق (temp = temp.next) ومن ثم يقوم بالتأكد من الشرط مجدداً هكذا حتى يصل إلى أن temp مساوي لـ tail وعندها يسنتقل للعنصر الذي يليه وهو في الحقيقة null لأنه tail.next دائما يساوي null وهكذا تنتهي for loop. ملاحظة: قمت بوضع ملفات للـ SLLNode and SLList classes في المرفقات تحتوي على تعليقات توضيحية. أتمنى من الله أن أكون قد وفقت في تفصيل موضوعنا هذا في حفظ الله إلى لقاء آخر SLList.java SLLNode.java تم ترقية هذا الطرح المميز الى صفحة المقالات
  14. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته إن شاء الله في هذه السلسلة من المواضيع سنتطرق لهيكلة البيانات بأنواعها المختلفة مع أن هذه الدروس لا ترتبط بأي لغة إلا أن اللغة المستخدمة للتطبيق العملي هي Java، يجب أن يكون القارئ ملم بأساسيات اللغة، كذلك سأستخدم في أغلب الأحيان المصطلحات الإنجليزية لصعوبة ترجمتها في بعض الحالات. قبل الشروع في الموضوع ماذا نقصد بهيكلة البيانات؟ المعنى هنا كبير جداً فقصودنا يحتوي على تشريح كامل لكل خصائص أي نوع من أنواع حفظ البيانات فعلى سبيل المثال عندما نتحدث عن المصفوفات واللتي تعتبر من أبسط أنواع حفظ البيانات فعندها سنتطرق لطريقة إنشاء المصفوفة طريقة حفظ أو حذف عنصر من المصفوفة وكذلك كيف لنا أن نبحث عن عنصر في المصفوفة وهكذا. من البديهي أن هناك أنواع مختلفة لحفظ البيانات ولكل منها خائصها وعيبوبها، في موضوعنا هذا سوف نتخطى المصفوفات والتي كما أشرت مسبقاً بأنها تعتبر من أبسط أنواع حفظ البيانات، وسنشرع مباشرة إلى القوائم أحادية الرابط. القوائم أحادية الرابط لماذا نحن بحاجة إلى القوائم بما أنه لدينا المصوفوفات؟ للمصفوفات عيبين رئسيين فعندما نريد تغيير حجم المصفوفة فإننا بحاجة إلى إنشاء مصفوفة جديدة ومن ثم نقوم بنسخ جميع عناصر المصفوفة القديمة إلى المصفوفة الجديدة ذات الحجم الجديد، كذلك عناصر المصفوفات مخزنة في الذاكرة في مواقع متتالية لذلك عندما نريد إضافة عنصر جديد أو حذف عنصر ما فهذا يضطرنا إلى تحريك بقية العناصر في حال كانت عناصر المصفوفة مرتبة بشكل ما. آفضل حل لهاتين المشكلتين هي القوائم وأبسط شكل من أشكال القوائم هي "القوائم أحادية الرابط". ماذا نعني بالقوائم أحادية الرابط؟! لنفرض بأن لدينا صندوق هذا يحتوي على قسمين القسم الأول نخزن فيه ما نريد أما القسم الثاني فيحتوي على فتحة لكي نستطيع أن نربط هذا الصندوق بالصندوق الذي بعده، في القوائم نطلق على هذا الصندوق أسم "العقدة" كل عقدة تحتوي على مكان لحفظ البيانات ورابط يربطها بما بعدها من العقد. عند تجميع عدد من العقد مع بعضها نطلق على هذا التجمع بإسم قائمة وبما أن كل عقدة ترتبط فقط بالعقدة اللتي تليها فنطلق عليها أحادية الرابط. بعد ان انتهينا من التعريفات وفهم معنى، لنترك النظري قليلاً ونتجه إلى العملي كما نعلم فإن القائمة أحادية الرابط تتكون من عدد من العقد، والعقدة الواحدة تحتوي على مكونين رئيسين (البيانات المخزنة و الرابط الذي يربط العقدة بما بعدها). إذاً علينا أولاً ان نقوم بإنشاء Class للعقدة الواحد ونوضح داخله مواصفات هذه العقدة واللتي هي مكان لحفظ البيانات (للتسهيل ستكون البيانات من نوع int) ومكان لربط هذه العقدة بالعقدة اللتي تليها (يجب أن يكون نوع هذا الرابط عقدة) لماذا يجب أن يكن الرابط من نفس نوع العقدة؟ بكل بساطة الرابط ما هو إلا سهم يشير إلى شيء ما لذلك يجب أن يعرف إلى ماذا يشير. هذا هو الكود اللذي سننتهي به: public class SLLNode { public int info; public SLLNode next; public SLLNode(int i){ this(i, null); } public SLLNode(int i, SLLNode n){ this.info = i; this.next = n; } } اسم الـ Class : SLLNode وهو اختصار لـ Single Linked Lists Node == عقدة لقائمة أحادية الرابط ويحتوي على متغيرين: info: وهو من نوع int ، وهو المكان اللذي سنقوم بحفظ البيانات داخله. next: وهو من نوع SLLNode وهو الجزء اللذي سيربط هذه العقدة بالعقدة اللتي تليها هذا الـ Class يحتوي على اثنتين من الـ Constructors : الأولى: public SLLNode(int i){ this(i, null); } وهذه تمكننا من إنشاء عقدة بتخزين المعلومات فقط بدون الحاجة لربطها بالعقدة اللتي تليها، وداخلها كما نرى نداء للـ Constructor الأخرى بإعطائها البيانات المدخلة + Null لخانة الرابط اللذي يربط العقدة للعقدة اللتي تليها. لماذا لا نقوم بإعطاء العقدة رابط للعقدة اللتي تليها؟! هذا مهم ففي حالة آخر عقدة لن يكون هناك عقدة بعدها فلا يوجد ما ترتبط به لذلك نضع علامة بأنه لا يوجد شيء بعد هذه العقدة وهذه العلامة هي null . الثانية: public SLLNode(int i, SLLNode n){ this.info = i; this.next = n; } وهذه تمكننا من إنشاء عقدة بإعطائها البيانات التي ستحفظها ورابط للعقدة اللتي تليها. هذه هي نهاية الجزء الأول من درسنا إن شاء الله سأكمل موضوع القوائم أحادية الرابط في الموضوع التالي مع العلم بأن باب النقاش مفتوح لأي نقطة لتوظيحها أكثر ولإزالة أي لبس حولها.
  15. بسم الله الرحمن الرحيم السلام عليكم ورحمة الله وبركاته إن شاء الله في هذه السلسلة من المواضيع سنتطرق لهيكلة البيانات بأنواعها المختلفة مع أن هذه الدروس لا ترتبط بأي لغة إلا أن اللغة المستخدمة للتطبيق العملي هي Java، يجب أن يكون القارئ ملم بأساسيات اللغة، كذلك سأستخدم في أغلب الأحيان المصطلحات الإنجليزية لصعوبة ترجمتها في بعض الحالات. قبل الشروع في الموضوع ماذا نقصد بهيكلة البيانات؟ المعنى هنا كبير جداً فقصودنا يحتوي على تشريح كامل لكل خصائص أي نوع من أنواع حفظ البيانات فعلى سبيل المثال عندما نتحدث عن المصفوفات واللتي تعتبر من أبسط أنواع حفظ البيانات فعندها سنتطرق لطريقة إنشاء المصفوفة طريقة حفظ أو حذف عنصر من المصفوفة وكذلك كيف لنا أن نبحث عن عنصر في المصفوفة وهكذا. من البديهي أن هناك أنواع مختلفة لحفظ البيانات ولكل منها خائصها وعيبوبها، في موضوعنا هذا سوف نتخطى المصفوفات والتي كما أشرت مسبقاً بأنها تعتبر من أبسط أنواع حفظ البيانات، وسنشرع مباشرة إلى القوائم أحادية الرابط. القوائم أحادية الرابط لماذا نحن بحاجة إلى القوائم بما أنه لدينا المصوفوفات؟ للمصفوفات عيبين رئسيين فعندما نريد تغيير حجم المصفوفة فإننا بحاجة إلى إنشاء مصفوفة جديدة ومن ثم نقوم بنسخ جميع عناصر المصفوفة القديمة إلى المصفوفة الجديدة ذات الحجم الجديد، كذلك عناصر المصفوفات مخزنة في الذاكرة في مواقع متتالية لذلك عندما نريد إضافة عنصر جديد أو حذف عنصر ما فهذا يضطرنا إلى تحريك بقية العناصر في حال كانت عناصر المصفوفة مرتبة بشكل ما. آفضل حل لهاتين المشكلتين هي القوائم وأبسط شكل من أشكال القوائم هي "القوائم أحادية الرابط". ماذا نعني بالقوائم أحادية الرابط؟! لنفرض بأن لدينا صندوق هذا يحتوي على قسمين القسم الأول نخزن فيه ما نريد أما القسم الثاني فيحتوي على فتحة لكي نستطيع أن نربط هذا الصندوق بالصندوق الذي بعده، في القوائم نطلق على هذا الصندوق أسم "العقدة" كل عقدة تحتوي على مكان لحفظ البيانات ورابط يربطها بما بعدها من العقد. عند تجميع عدد من العقد مع بعضها نطلق على هذا التجمع بإسم قائمة وبما أن كل عقدة ترتبط فقط بالعقدة اللتي تليها فنطلق عليها أحادية الرابط. بعد ان انتهينا من التعريفات وفهم معنى، لنترك النظري قليلاً ونتجه إلى العملي كما نعلم فإن القائمة أحادية الرابط تتكون من عدد من العقد، والعقدة الواحدة تحتوي على مكونين رئيسين (البيانات المخزنة و الرابط الذي يربط العقدة بما بعدها). إذاً علينا أولاً ان نقوم بإنشاء Class للعقدة الواحد ونوضح داخله مواصفات هذه العقدة واللتي هي مكان لحفظ البيانات (للتسهيل ستكون البيانات من نوع int) ومكان لربط هذه العقدة بالعقدة اللتي تليها (يجب أن يكون نوع هذا الرابط عقدة) لماذا يجب أن يكن الرابط من نفس نوع العقدة؟ بكل بساطة الرابط ما هو إلا سهم يشير إلى شيء ما لذلك يجب أن يعرف إلى ماذا يشير. هذا هو الكود اللذي سننتهي به: public class SLLNode { public int info; public SLLNode next; public SLLNode(int i){ this(i, null); } public SLLNode(int i, SLLNode n){ this.info = i; this.next = n; } } اسم الـ Class : SLLNode وهو اختصار لـ Single Linked Lists Node == عقدة لقائمة أحادية الرابط ويحتوي على متغيرين: info: وهو من نوع int ، وهو المكان اللذي سنقوم بحفظ البيانات داخله. next: وهو من نوع SLLNode وهو الجزء اللذي سيربط هذه العقدة بالعقدة اللتي تليها هذا الـ Class يحتوي على اثنتين من الـ Constructors : الأولى: public SLLNode(int i){ this(i, null); } وهذه تمكننا من إنشاء عقدة بتخزين المعلومات فقط بدون الحاجة لربطها بالعقدة اللتي تليها، وداخلها كما نرى نداء للـ Constructor الأخرى بإعطائها البيانات المدخلة + Null لخانة الرابط اللذي يربط العقدة للعقدة اللتي تليها. لماذا لا نقوم بإعطاء العقدة رابط للعقدة اللتي تليها؟! هذا مهم ففي حالة آخر عقدة لن يكون هناك عقدة بعدها فلا يوجد ما ترتبط به لذلك نضع علامة بأنه لا يوجد شيء بعد هذه العقدة وهذه العلامة هي null . الثانية: public SLLNode(int i, SLLNode n){ this.info = i; this.next = n; } وهذه تمكننا من إنشاء عقدة بإعطائها البيانات التي ستحفظها ورابط للعقدة اللتي تليها. هذه هي نهاية الجزء الأول من درسنا إن شاء الله سأكمل موضوع القوائم أحادية الرابط في الموضوع التالي مع العلم بأن باب النقاش مفتوح لأي نقطة لتوظيحها أكثر ولإزالة أي لبس حولها. تم ترقية هذا الطرح المميز الى صفحة المقالات

عالم البرمجة

عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل