لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 03/02/22 في كل الموقع
-
لقد قمت بتنصيب bootstrap-rtl-scss الاصدار 4.6.1 وقمت باستوراد ملف التنسيقات الخاص بيه في ملف index.js وكان يعمل بشكل جيد ولكن ال carousel التي استخدمتها من خلال المكتبة كانت لا تعمل تظهر ولكن لا تعمل فما السبب ؟1 نقطة
-
السلام عليكم لدي قالب ووردبرس أعمل على إنشاء قالب ابن له ولكن لا يظهر في قائمة القوالب أولاً أضفت المجلد child الى ملفات القالب ثم أنشأت ملفين style.css & functions.php في ملف style.css أضفت الكود التالي /* Theme Name: Child Theme URI: http://example.com Description: هذا هو قالب ابن من القالب الأب الأساسي Author: WPEnjoy Author URI: https://wpenjoy.com Template: enjoytube Version: 1.0.2 */ وفي ملف functions.php أضفت الكود التالي : add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_styles' ); function my_theme_enqueue_styles() { wp_enqueue_style( 'child-style', get_stylesheet_uri(), array( 'parenthandle' ), wp_get_theme()->get('Version') // this only works if you have Version in the style header ); } فما المشكلة في القالب ؟1 نقطة
-
1 نقطة
-
كيف اقوم بإنشاء نظام الدخول و التحقق من صحة اسم المستخدم و كلمة المرور ( احرف كبيرة و صغيرة و رموز و الارقام ) و صحة البريد الالكتروني ( عدم وجود المسافات و احرف و ارقام ) ؟؟1 نقطة
-
أحاول إنشاء خدمة API ، وكنت أتساءل عن كيفية إتاحة خدمة رفع الملفات عن طريق Flask-Restful. بإستخدام فلاسك Flask فقط يمكنني الحصول على الملف من خلال الكود التالي: class Upload(Resource): def post(self): img = request.files['image'] if img: # Store image ... else: return {"error": True, "message": "there is no file" } وفي HTML يتم عرض حقل إدخال لرفع الملف: <input type="file" name="img"> ولكن كيف أقوم بإستقبال ملف بإستخدام إضافة Flask-Restful عبر واجهة برمجية API وليس من خلال عنصر input؟1 نقطة
-
لو سمحت انا لسه مبتدئ في البرمجه هل اكتفي بدراسه فديوهات هذا الدورة جيدا والتطبيق عليها وساحقق مستوي جيدا في النهاية ام ام هناك شيء اخر بجانب الفديوهات يجب علي ان افعله مثلا كقراءة كتب او الاطلع علي موسوعه حسوب ارجو جواب مفصل لانني مشتت بعض الشئ مع العلم ان هناك اشياء كثيرة مثلا لم تذكر في هذه الدورات ام اكتفي بهذه الدرة ومذاكرتها جيدا وفقط1 نقطة
-
لدي ملف عرض view يحتوي على مكون معين وأريد إستخدام نفس المكون في ملف عرض آخر ، لا أريد ان أقوم بنسخ الكود في أكثر من مكان. هل توجد طريقة تمكنني من إستدعاء ملف عرض view بداخل ملف عرض آخر؟1 نقطة
-
اسف قمت بالتجريب لا تعمل ولم اجد اي شفرات جافا سكريبت لها علي موقع bootstrap !1 نقطة
-
ربما لم تقمم بإضافة كود الجافا سكربت الخاص carousel فهو مطلوب حتى يعمل وأيضاً في التوثيق يخبرك بوتستراب أنه يجب إضافته . $('#myCarousel').on('slide.bs.carousel', function () { // do something... }) قم بعمل التالي ، مع ملاحظة تغير اسم الكلاس إلى اسم كلاس carousel الذي لديك . <script type="text/javascript"> $('.carousel').carousel({ interval: 2000 }) </script>1 نقطة
-
اشعر بصعوبه بالغه فى اتمام الدوره وتعلم البرمجه حيث انه لايوجد لدى اى وقت فراغ بسبب زيادة وقت العمل احيانا ادرس الدوره بمقدار 3 ساعات فى الشهر تقريبا وانقطع كثيرا حتى الان مازلت فى المسار الاول بالرغم من مرور مايقرب من 5 شهور او اكثر على اشتراكى فى الدوره كنت متحمس جدا فى البدايه لكن وجدت صعوبه فى شرح الماده العلميه وبدأت ابحث عن شروح اخرى فى اليوتيوب الرجاء النصيحيه وهل يمكننى استرداد اموالى الخاصه بالدوره1 نقطة
-
يمكنك إنشاء ملف context processor داخل django و يقوم النظام تلقائياً بإستدعاء ذلك الملف و تنفيذه في كل request يتم على السيرفر حيثُ يمكن إتباع الخطوات الآتيه : قم بإنشاء ملف يحمل الإسم الذي تفضله على سبيل المثال private_context_processor.py داخل التطبيق قم بفتح الملف و استدعي النموذج المراد التطبيق عليه من ملف models.py كالتالي : from .models import Category def categories_everywhere(request): my_categories = Category.objects.all() return {'my_categories': my_categories} 3 . نقوم بتسجيل السطر 'home.private_context_processor.categories_everywhere' داخل اعدادات المشروع settings.py في قسم TEMPLATES كالتالي : TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': ['templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.template.context_processors.i18n', 'django.contrib.messages.context_processors.messages', 'home.private_context_processor.categories_everywhere', ], }, }, ] التفاصيل للسطر الذي تم تسجيلة : home هو إسم التطبيق الذي تم إنشاء ملف private_context_processor.py به private_context_processor هو اسم الملف الذي قمنا بإنشائه categories_everywhere هي الدالة التي كتبناها داخل الملف 4. ثم نذهب لأي ملف html داخل اي تطبيق في المشروع بشكل عام و نستدعي الـ context الذي يقوم بالتعويض عن الداله و يرجع بها في تلك الحاله my_categories حيثُ يُمكنك عمل for loop أو اي أمر من أوامر بايثون المتوافقة مع ال templates داخل التطبيق1 نقطة
-
لجعل مُتغير متاحاً في كل القوالب بشكل إفتراضي يجب إنشاء مُعالج سياق (context processor) جديد وإضافته إلى إعدادات جانغو. لفِعل ذلك نقوم بإنشاء ملف بايثون داخل تطبيق جانغو ونقوم بتسميته my_context_processors.py. داخل الملف نقوم بكتابة الشفرة التالية: from myapp.models import Category def categories_processor(request): categories = Category.objects.all() return {'categories': categories} # أنشأنا متغير جديد فيه كل المُنتجات ثم نُضيف my_context_processors داخل ملف الإعدادات: TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'OPTIONS': { 'loaders': [ ... ], 'context_processors': [ 'django.template.context_processors.debug', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', "myapp.my_context_processors.categories_processor" # نُضيفه هنا ], }, } ] بهذا الشكل يكون categories مًتواجداً في كل القوالب.1 نقطة
-
هناك مكتبة كاملة في django تقوم بذلك الأمر و هي django import export يمكن تثبيتها عن طريق الأمر التالي في موجة الأوامر بعد تفعيل البيئة الإفتراضية virtualenv pip install django-import-export ثم قم بإضافة المكتبه كتطبيق ضمن التطبيقات المثبته في المشروع بملف settings.py داخل خانة INSTALLED_APPS 'import_export' قم بالتوجه لملف admin.py داخل التطبيق الذي يوجد به النموذج المراد تصدير أو إستيراد البيانات له ثم قم بإستيراد السطر التالي لإستدعاء مكتبة import export from import_export.admin import ImportExportModelAdmin و لكن في تلك الحاله لا يمكن تسجيل النموذج بالشكل الإفتراضي الخاص به حيثُ يجب تغيير طريقة تسجيلة على سبيل المثال أريد تسجيل النموذج Volunteering في ملف admin.py حيثُ نقوم بتسجيله بالشكل التالي بالطريقة الإفتراضية from django.contrib import admin from .models import Volunteering from import_export.admin import ImportExportModelAdmin admin.site.register(Volunteering) لكن في حال أردت إستخدام مكتبة import export يجب تعديل طريقة التسجيل لتكن بالشكل التالي : from django.contrib import admin from .models import Volunteering from import_export.admin import ImportExportModelAdmin @admin.register(Volunteering) class VolImportExport(ImportExportModelAdmin): pass ملحوظه : يمكن تعديل إسم الـ class بالإسم الذي تريد فهو متغير أما ما بين الأقواس فهو ثابت كذلك ال decorator فوق ال class ثابت أيضاً ثم تقوم بفتح لوحة تحكم الموقع الخاص بك بشكل إفتراضي ثم تتجه للنموذج الذي تريد فيه استدعاء به مكتبة import export فستجد بجانب " إضافة " انه ظهر أزرار جديده " تصدير , إستيراد " مثل الصورة المرفقة حيثُ يمكنك تصدير او استيراد البيانات للنموذج مباشرة عن طريق العديد من الصيغ مثل csv , xls , xlsx , json , yaml و غيرها الكثير1 نقطة
-
يُمكنك إنشاء أمر في جانغو يقوم بقراءة ملف ال CSV وإدخال المعلومات داخل النموذج Product. لإنشاء الأمر نقوم بإنشاء مجلد داخل تطبيق جانغو ونقوم بتسميته management، داخل هذا المجلد نقوم بإنشاء مجلد آخر اسمه commands، وبداخله نقوم بإنشاء ملف بايثون يحمل الإسم insert_products_from_csv.py. نقوم بوضع هذه الشفرة داخل الملف. from django.core.management.base import BaseCommand import csv class Command(BaseCommand): help = 'إنشاء كائنات إنطلاقاً من ملف csv' def add_arguments(self, parser): parser.add_argument('--path', type=str, help="مسار الملف") def handle(self, *args, **options): # نجلب مسار الملف file_path = options['path'] # نقرأ الملف with open(file_path, 'rb') as csv_file: reader = csv.reader(csv_file, delimiter=';') for row in reader: # نستخرج معلومات المُنتج من كل سطر product_id = row[0] product_name = row[1] product_price = row[2] product_description = row[3] # نحفظ المعلومات في النموذج person, created = Person.objects.update_or_create( id=product_id, name=product_name, price=product_price, description=product_description ) لتشغيل الأمر نكتب python manage.py insert_products_from_csv --path "هنا نضع مسار الملف كاملاً"1 نقطة
-
تَعرَّضنا حتى الآن إلى بعض الأمثلة على معالجة المصفوفات في المقال السابق، ولكن غالبيتها كان مُجرد أمثلة بسيطة على معالجة عناصر مصفوفة من البداية إلى النهاية أو جَلْب عشوائي لقيمة عنصر ضِمْن مصفوفة. سنَفْحَص بهذا القسم وما يليه من أقسام بعضًا من الأمور الآخرى الأكثر تشويقًا. أمثلة على المعالجة لابُدّ أن تكون حريصًا فيما يتعلَّق بالفهارس من خارج نطاق المصفوفة. لنَفْترِض مثلًا أن lines عبارة عن مصفوفة من النوع String[]، وأننا نريد مَعرِفة ما إذا كانت تلك المصفوفة تحتوي على أية عناصر مكررة بموضعين متتاليين. ينبغي إذًا أن نَفْحَص الاختبار lines.equals(lines[i+1]) لأي فهرس i. اُنظر المحاولة الخاطئة التالية: boolean dupp = false; // Assume there are no duplicates for ( int i = 0; i < list.length; i++ ) { if ( lines[i].equals(lines[i+1]) ) { // THERE IS AN ERROR HERE! dupp = true; // we have found a duplicate! break; } } تبدو حلقة التكرار for بالأعلى مثل الكثير من الحلقات التي تعرضنا لها من قبل، لذلك ما هي المشكلة إذًا؟ يحدث الخطأ عندما تسند القيمة النهائية إلى i بالحلقة أي عندما i تساوي lines.length-1. في هذه الحالة، i+1 تساوي lines.length. لكن فهرس العنصر الأخير بالمصفوفة هو lines.length-1، لذلك lines.length ليست فهرس صالح. يعني ذلك أن lines[i+1] يتسبب بحدوث اعتراض من النوع ArrayIndexOutOfBoundsException. يمكنك إصلاح ذلك بسهولة عن طريق إيقاف حلقة التكرار قبل i+1 من النطاق المسموح به، كالتالي: boolean dupp = false; // Assume there are no duplicates for ( int i = 0; i < list.length - 1 ; i++ ) { if ( lines[i].equals(lines[i+1]) ) { dupp = true; // we have found a duplicate! break; } } يظهر أحيانًا نوع شبيه من الأخطاء عند العمل مع المصفوفات المملوءة جزئيًا (partially full arrays) -اُنظر القسم الفرعي 3.8.4-، ولكنه قد لا يَكُون بهذا الوضوح. إذا كان جزء معين فقط من المصفوفة مملوءًا بحيث يُستخدَم عداد (counter) لمعرفة عدد الخانات المُستخدَمة فعليًا ضِمْن تلك المصفوفة. لا تتعلَّق المشكلة في تلك الحالة بمحاولة الوصول لعنصر بموضع خارج المصفوفة ولكنها تتعلق بفحص جزء من المصفوفة قيد الاستخدام بالفعل. عندما يُحاوِل البرنامج الوصول إلى مَوْضِع خارج المصفوفة، ينهار (crash) البرنامج على الأقل مما يَسمَح لك برصد المشكلة لكن في حالة المصفوفات المملوءة جزئيًا، لن تتمكن من رصد الخطأ بسهولة. لقد رأينا الكيفية التي يُمكِننا بها أن نُضيف عناصر إلى مصفوفة مملوءة جزئيًا، ولكننا لم نَتعرَّض لطريقة حَذْف تلك العناصر؟ لنَفْترِض مثلًا أننا نَكْتُب برنامجًا عبارة عن لعبة يستطيع اللاعبون الانضمام إليها ومغادرتها بأي وقت. يُمكِننا إذًا أن نُنشِئ الصَنْف Player لتمثيل كل لاعب موجود باللعبة. سنُخزِّن بطبيعة الحال قائمة اللاعبين الموجودين باللعبة داخل مصفوفة من النوع Player[]، وليَكُن اسمها هو playerList. نظرًا لأن عدد اللاعبين قد يَتَغيَّر بأي وقت، سنُطبِق نمط المصفوفة المملوءة جزئيًا، لذا سنُعرِّف مُتغيِّر playerCt لتَخْزِين عدد اللاعبين الموجودين باللعبة. بفَرْض عدم احتمالية وجود أكثر من 10 لاعبين بأي لحظة، يُمكِننا إذًا أن نُصرِّح عن المُتْغيِّرات كالتالي: Player[] playerList = new Player[10]; // Up to 10 players. int playerCt = 0; // At the start, there are no players. بَعْد انضمام بعض اللاعبين إلى اللعبة، سيُصبِح المُتْغيِّر playerCt أكبر من الصفر كما ستُخزَّن الكائنات (objects) المُمثِلة للاعبين داخل عناصر المصفوفة playerList[0] و playerList[1] .. وحتى playerList[playerCt-1]. لاحِظ أننا لم نُشِر إلى العنصر playerList[playerCt]. يُمثِل المُتْغيِّر playerCt كُلًا من عدد العناصر الفعلية الموجودة داخل المصفوفة، وكذلك فهرس (index) الخانة التالية المتاحة بالمصفوفة. تُضيِف الشيفرة التالية كائنًا جديدًا newPlayer إلى اللعبة: playerList[playerCt] = newPlayer; // Put new player in next // available spot. playerCt++; // And increment playerCt to count the new player. في المقابل، قد يَكُون حَذْف لاعب من اللعبة أصعب قليلًا، لأننا لا نُريد بالطبع أن نَترُك مَوْضِع اللاعب المطلوب حَذْفه فارغًا. لنَفْترِض مثلًا أننا نُريد حَذْف اللاعب الموجود بالفهرس k داخل المصفوفة playerList. يَعنِي ذلك أن عدد الخانات المُستخدَمة فعليًا بالمصفوفة سيُصبِح أقل بمقدار الواحد. إذا لم يَكُن ترتيب اللاعبين داخل المصفوفة مُهِمًا، يُمكنِنا إذًا أن نَنقِل اللاعب الأخير الموجود فعليًا داخل المصفوفة إلى المَوْضِع k ثُمَّ نُنقِص قيمة playerCt بمقدار الواحد كالتالي: playerList[k] = playerList[playerCt - 1]; playerCt--; لم يَعُد اللاعب الذي كان موجودًا مُسْبَقًا بالمَوْضِع k موجودًا بالمصفوفة، فقد حَذْفناه ببساطة من القائمة. في المقابل، أصبح اللاعب الذي كان موجودًا مُسْبَقًا بالموضع playerCt - 1 موجودًا بالمصفوفة مرتين، ولكن نظرًا لأننا انقصنا المُتْغيِّر playerCt بمقدار الواحد، فإن واحدة منهما فقط تَقَع ضِمْن الجزء المُستخدَم فعليًا بالمصفوفة. يَحمِل أي عنصر بالمصفوفة في العموم قيمة ما، ولكن القيم من الموضع 0 وحتى playerCt - 1 هي فقط الصالحة أي يُمكِننا معالجتها. حاول أن تُفكِر بما سيَحدُث إذا كان اللاعب المطلوب حَذْفه هو اللاعب الأخير بالمصفوفة، وهل ترى أن الشيفرة بالأعلى ستَظِل صالحة؟ إذا كان ترتيب اللاعبين بالمصفوفة مُهِمًّا (ربما لأنه يُسمَح لهم باللعب وفقًا لترتيب تَخْزِينهم بالمصفوفة)، ينبغي عندها تحريك كل لاعب موجود من المَوْضِع k+1 وحتى آخر مَوْضِع مملوء فعليًا بالمصفوفة إلى المَوْضِع الذي يَسبِقُه أي سيَحلّ مثلًا اللاعب بالمَوْضِع k+1 محلّ اللاعب بالمَوْضِع k الذي أصبح خارج اللعبة للتو، وبدوره سيَملئ اللاعب بالمَوْضِع k+2 البقعة التي تركها اللاعب k+1 للتو، وهكذا. اُنظر الشيفرة التالية: for (int i = k+1; i < playerCt; i++) { playerList[i-1] = playerList[i]; } playerCt--; تُوضِح الصورة التالية طريقتي حَذْف عنصر -اللاعب "C" تحديدًا- من مصفوفة مملوءة جزئيًا (partially full array): يترك ذلك السؤال مفتوحًا عما سيَحدُث إذا كانت المصفوفة المملوءة جزئيًا (partially full) مملوءة بالكامل بينما تَرغَب بإضافة عناصر جديدة إليها؟ بالطبع، لا يُمكِننا أن نُغيِّر حجم المصفوفة (array size)، ولكن يُمكِننا أن نُنشِئ مصفوفة جديدة أكبر ثُمَّ نَنَسَخ البيانات الموجودة بالمصفوفة القديمة إلى المصفوفة الجديدة. ينقلنا ذلك إلى السؤال التالي: ما الذي يعنيه نَسخ مصفوفة بالأساس؟ لنَفْترِض أن A و B عبارة عن مُتْغيِّري مصفوفة (array variables) لهما نفس النوع الأساسي (base type). تُشير A بالفعل إلى مصفوفة، ونريد الآن جَعْل B تُشيِر إلى نُسخة من A. أول ما ينبغي أن تُدركه هو أن تَعْليمَة الإِسْناد (assignment statement) التالية: B = A; لا تُنشِئ نسخة من A. نظرًا لأن المصفوفات عبارة عن كائنات (objects)، يَحمِل أي مُتْغيِّر مصفوفة (array variable) -إن لم يَكُن فارغًا- مؤشِرًا (pointer) إلى مصفوفة. تَنَسَخ تَعْليمَة الإِسْناد (assignment) بالأعلى ذلك المُؤشر من A إلى B، وبالتالي سيُشيِر كُلًا من A و B إلى نفس ذات المصفوفة، فيُعدّ مثلًا كُلًا من A[0] و B[0] أسماءً مختلفة لنفس عناصر المصفوفة (array element). في المقابل، إذا كنا نريد جَعْل B يُشيِر إلى نسخة من A، ينبغي إذًا أن نُنشِئ مصفوفة جديدة كليًا ثُمَّ نَنَسخ العناصر من A إلى B. بِفَرض أن A و B من النوع double[]، يُمكِننا إذًا كتابة ما يَلي لإنشاء نسخة من A: double[] B; B = new double[A.length]; // Make a new array with the same length as A. for ( int i = 0; i < A.length; i++ ) { B[i] = A[i]; } لكي نَتَمكَّن من إضافة عناصر جديدة إلى مصفوفة مملوءة جزئيًا (partially full array) ممتلئة، ينبغي أن نُنشِئ مصفوفة جديدة أكبر، وعادةً ما تَكُون بحجم يُساوِي ضعف المصفوفة الحالية. لما كان مُتْغيِّر المصفوفة هو ما يَسمَح لنا بالوصول إلى البيانات التي أصبحت موجودة بالمصفوفة الجديدة، ينبغي إذًا أن نُعدِّل ذلك المُتْغيِّر لكي يُشير إلى المصفوفة الجديدة. يُمكِننا أن نُجرِي ذلك لحسن الحظ بتَعْليمَة إِسْناد (assignment statement) بسيطة. بِفَرْض اِستخدَام playerList و playerCt لتَخْزِين اللاعبين باللعبة -كالمثال بالأعلى-، تُوضِح الشيفرة التالية طريقة إضافة لاعب جديد newPlayer إلى اللعبة حتى لو كانت المصفوفة playerList ممتلئة: if ( playerCt == playerList.length ) { // The number of players is already equal to the size of the array. // The array is full. Make a new array that has more space. Player[] temp; // A variable to point to the new array. temp = new Player[ 2*playerList.length ]; // Twice as big as the old array. for ( int i = 0; i < playerList.length; i++ ) { temp[i] = playerList[i]; // Copy item from old array into new array. } playerList = temp; // playerList now points to the new, bigger array. } // At this point, we know that there is room in the array for newPlayer. playerList[playerCt] = newPlayer; playerCt++; الآن، لم يَعُد هناك أي مُتْغيِّر يُشير إلى المصفوفة القديمة، ولذلك يَتَولَّى كانس المُهملات (garbage collector) مُهِمَة تَحرِيرها. بعض التوابع القياسية للمصفوفات تحتاج الكثير من البرامج إلى عمليات مثل نَسْخ مصفوفة، ولهذا تُوفِّر الجافا العديد من التوابع (methods) القياسية لمعالجة المصفوفات. هذه التوابع مُعرَّفة كتوابع ساكنة (static methods) بصَنْف اسمه Arrays ضِمْن حزمة java.util. اُنظر المثال التالي: Arrays.copyOf( list, lengthOfCopy ) تُعيد الدالة copyOf مصفوفة جديدة طولها يُساوِي قيمة المعامل lengthOfCopy، وبحيث تحتوي على عناصر منسوخة من المُعامل list. إذا كان lengthOfCopy أكبر من list.length، ستَحمِل الخانات الإضافية بالمصفوفة الجديدة قيمها الافتراضية (أي صفر في حالة المصفوفات العددية، وnull في حالة مصفوفات الكائنات [array objects]، ..إلخ). في المقابل، إذا كان lengthOfCopy أقل من list.length، تُنسَخ العناصر بمقدار ما يَتَناسَب مع حجم المصفوفة الجديدة. على سبيل المثال، إذا كانت A مصفوفة، فإن الشيفرة التالية: B = Arrays.copyOf( A, A.length ); تَضبُط B لكي تُشيِر إلى نُسخة مُتطابِقة مع A، أما الشيفرة التالية: playerList = Arrays.copyOf( playerList, 2*playerList.length ); يُمكِنها أن تُستخدَم لمضاعفة حجم المساحة المتاحة بمصفوفة مملوءة جزئيًا (partially full array). علاوة على ذلك، قد نَستخدِم نفس ذلك التابع Arrays.copyOf لإنقاص حجم مصفوفة مملوءة جزئيًا، وهو ما يُساعِدنا على تَجنُّب وجود خانات إضافية كثيرة غَيْر مُستخدَمة. يُمكِننا أن نُطبِق ذلك أثناء حَذْف لاعب k من قائمة اللاعبين كالتالي: playerList[k] = playerList[playerCt-1]; playerCt--; if ( playerCt < playerList.length/4 ) { // More than 3/4 of the spaces are empty. Cut the array size in half. playerList = Arrays.copyOf( playerList, playerList.length/2 ); } يحتوي الصَنْف Arrays في الواقع على نُسخ مختلفة من التابع copyOf: نُسخة لكل نوع أساسي (primitive type)، بالإضافة إلى نُسخة آخرى للكائنات (objects). ملحوظة: عند نَسْخ مصفوفة كائنات (objects)، تُنسَخ فقط مؤشرات (pointers) الكائنات لا محتوياتها إلى المصفوفة الجديدة، ويُمثِل ذلك عمومًا القاعدة العامة لإِسْناد مؤشر (pointer) إلى آخر. إذا كان كل ما تُريده هو مُجرد نسخة بسيطة من مصفوفة بنفس حجمها الأصلي، فهناك طريقة سهلة للقيام بذلك. في الواقع، تَملُك أي مصفوفة تابع نُسخة (instance method) اسمه هو clone(). يُنشِئ ذلك التابع نُسخة من المصفوفة. يُمكِنك مثلًا أن تَستخدِم الشيفرة التالية لإنشاء نسخة من مصفوفة من النوع int: int[] B = A.clone(); يَحتوِي الصَنْف Array على توابع (methods) مفيدة آخرى سنَعرِض بعضًا منها هنا. بالمثل من التابع Arrays.copyOf، تَتَوفَّر نُسخ متعددة من كل تلك التوابع للأنواع المختلفة من المصفوفات: Arrays.fill( array, value ): تَملَئ مصفوفة كاملة بقيمة محددة. لابُدّ أن يَكُون value من نوع مُتوافق مع النوع الأساسي للمصفوفة array. إذا كان numlist عبارة عن مصفوفة من النوع double[] مثلًا، ستَملَئ التَعْليمَة Arrays.fill(numlist,17) جميع عناصر تلك المصفوفة بالقيمة 17. Arrays.fill( array, fromIndex, toIndex, value ): تَملَئ جزءًا من المصفوفة array من الفهرس fromIndex حتى الفهرس toIndex-1 بالقيمة value. لاحِظ أن الفهرس toIndex غَيْر مُضمَّن. Arrays.toString( array ): عبارة عن دالة (function) تُعيِد سِلسِلة نصية من النوع String مُكوَّنة من قيم المصفوفة array مفصولة بفاصلة (comma) ومُحاطَة بأقواس مربعة. تُحوَّل القيم إلى سَلاسِل نصية (strings) بنفس الطريقة التي تُحوَّل بها تلك القيم أثناء طباعتها. Arrays.sort( array ): تُعيد ترتيب جميع القيم الموجودة بمصفوفة ترتيبًا تصاعديًا. لاحِظ أنها لا تَعمَل مع جميع المصفوفات، فلابُدّ أن تكون قيم المصفوفة قابلة للموازنة لمعرفة أيهما أصغر. افتراضيًا، تَعمَل مع المصفوفات من النوع String والأنواع البسيطة (primitive types) باستثناء النوع boolean. سنناقش خوارزميات ترتيب المصفوفات (array sorting) بالقسم 7.4. Arrays.sort( array, fromIndex, toIndex ): تُرتِّب العناصر من array[fromIndex] إلى array[toIndex-1]. Arrays.binarySearch( array, value ): تبحث تلك الدالة عن value داخل array، وتُعيد قيمة من النوع int عبارة عن فهرس (index) عنصر المصفوفة المُتضمِّن للقيمة المُمرَّرة إذا كانت موجودة. إذا لم تَكُن تلك القيمة موجودة بالمصفوفة، تُعيد تلك الدالة القيمة -1. ملحوظة: لابُدّ أن تَكُون المصفوفة مُرتَّبة ترتيبًا تصاعديًا. سنُناقِش خوارزمية البحث الثنائي (binary search) بالقسم 7.4. 7.2.3 برنامج RandomStrings صَمَّمنا -بالقسم الفرعي 6.2.4- برنامج واجهة مُستخدِم رسومية (GUI) يَعرِض نُسخًا مُتعددة من سِلسِلة نصية بمواضع عشوائية وبألوان وخطوط عشوائية، وعندما يَنقُر المُستخدِم على نافذة البرنامج، تَتَغيَّر كُلًا من مَواضِع وألوان وخطوط تلك السَلاسِل النصية (strings) إلى قيم عشوائية. لنَفْترِض الآن أننا نُريد أن نُنشِئ تحريكة (animation) تتحرك خلالها تلك السَلاسِل عبر النافذة. سنحتاج في تلك الحالة إلى تَخْزِين خاصيات كل سِلسِلة نصية منها لكي نَتَمكَّن من إعادة رسمها بكل إطار ضِمْن التحريكة. يُمكِنك الإطلاع على نسخة البرنامج الجديدة بالملف RandomStringsWithArray.java. سنَرسِم 25 سِلسِلة نصية. لكل سِلسِلة نصية منها، ينبغي أن نُخزِّن إحداثيات مَوْضِعها (x,y) ولون ونوع الخط المُستخدَم للرَسْم. لكي نَتَمكَّن من تَحرِيك السَلاسِل النصية، سنُخزِّن سرعة الحركة الخاصة بكل سِلسِلة نصية بهيئة عددين dx و dy. بكل إطار (frame)، ينبغي أن تزداد قيمة الإحداثي x لكل سِلسِلة نصية بمقدار dx الخاص بتلك السِلسِلة بينما ستزداد قيمة الإحداثي y بمقدار dy المناظر. سنحتاج إذًا إلى الست مصفوفات التالية لتَخْزِين بيانات البرنامج: double[] x = new double[25]; double[] y = new double[25]; double[] dx = new double[25]; double[] dy = new double[25]; Color[] color = new Color[25]; Font[] font = new Font[25]; ستُملَئ هذه المصفوفات بقيم عشوائية. يَرسَم التابع draw() -المسئول عن رَسْم الحاوية (canvas)- السَلاسِل النصية. تُرسَم كل سِلسِلة نصية i بإحداثيات نقطة تُساوِي (x,y) وبلون يُساوِي color وبنوع خط يُساوِي font. لم نَستخدِم كُلًا من dx و dy بالتابع draw()، وإنما سنَستخدِمهما أثناء تَحدِيث البيانات أثناء رَسْم الإطار التالي. يُمكِننا إذًا كتابة تعريف التابع draw() كالتالي: public void draw() { GraphicsContext g = canvas.getGraphicsContext2D(); g.setFill(Color.WHITE); // (Fill with white, erasing previous picture.) g.fillRect(0,0,canvas.getWidth(),canvas.getHeight()); for (int i = 0; i < 25; i++) { g.setFill( color[i] ); g.setFont( font[i] ); g.fillText( MESSAGE, x[i], y[i] ); g.setStroke(Color.BLACK); g.strokeText( MESSAGE, x[i], y[i] ); } } يَستخدِم هذا الأسلوب المصفوفات المتوازية (parallel arrays) حيث قُسِّمت بيانات رسالة واحدة بين عدة مصفوفات، ولكي تَرَى جميع البيانات المُتعلِّقة برسالة واحدة، ستحتاج إلى وَضْع المصفوفات إلى جانب بعضها كخطوط مُتوازية، وبحيث تَقَع العناصر برقم المَوضِع i ضِمْن تلك المصفوفات إلى جوار بعضها البعض. قد لا يُعدّ اِستخدَام المصفوفات المتوازية (parallel arrays) بهذا المثال البسيط خطأً فادحًا، ولكنها في العموم لا تَتَبِع الفلسفة كائنية التوجه (object-oriented) فيما يَتَعلَّق بتَخْزِين البيانات المُرتبطة معًا ضِمْن كائن واحد. إذا اتبعنا تلك القاعدة، فإننا لن نحتاج إلى تَخيُل العلاقة بين تلك البيانات؛ لأنها ستَكُون موجودة بمكان واحد بالفعل. سنُعرِّف الصَنْف StringData لهذا الغرض تحديدًا: private static class StringData { // Info needed to draw one string. double x,y; // location of the string; double dx,dy; // velocity of the string; Color color; // color of the string; Font font; // the font that is used to draw the string } سنَستخدِم مصفوفة من النوع StringData[] لتَخْزِين بيانات النُسخ المختلفة من الرسالة النصية. تُصرِّح التَعْليمَة التالية عن مصفوفة stringData بهيئة مُتْغيِّر نسخة (instance variable): StringData[] stringData; ستَكُون قيمة المُتْغيِّر stringData فارغة بالبداية. ينبغي إذًا أن نُنِشئ مصفوفة ونملأها بالبيانات ثم نُسنِدها (assign) إليه. لاحِظ أن كل عنصر بتلك المصفوفة عبارة عن كائن (object) من النوع StringData، والذي ينبغي أن نُنشِئه قبل اِستخدَامه. يُنشِئ البرنامج الفرعي (subroutine) التالي المصفوفة ويملؤها ببيانات عشوائية: private void createStringData() { stringData = new StringData[25]; for (int i = 0; i < 25; i++) { stringData[i] = new StringData(); stringData[i].x = canvas.getWidth() * Math.random(); stringData[i].y = canvas.getHeight() * Math.random(); stringData[i].dx = 1 + 3*Math.random(); if (Math.random() < 0.5) // 50% chance that dx is negative stringData[i].dx = -stringData[i].dx; stringData[i].dy = 1 + 3*Math.random(); if (Math.random() < 0.5) // 50% chance that dy is negative stringData[i].dy = -stringData[i].dy; stringData[i].color = Color.hsb( 360*Math.random(), 1.0, 1.0 ); stringData[i].font = fonts[ (int)(5*Math.random()) ]; } } يُستدعَى التابع (method) السابق داخل start() وكذلك عندما يَنقُر المستخدم على الزر لإنشاء بيانات عشوائية جديدة. يُمكِننا الآن أن نَستخدِم حَلْقة تَكْرار for لرَسْم السلاسل النصية كالتالي: for (int i = 0; i < 25; i++) { g.setFill( stringData[i].color ); g.setFont( stringData[i].font ); g.fillText( MESSAGE, stringData[i].x, stringData[i].y ); g.setStroke(Color.BLACK); g.strokeText( MESSAGE, stringData[i].x, stringData[i].y ); } أو قد نَستخدِم حَلْقة التَكْرار for-each لكي نَتَجنَّب التَعامُل مع فهارس المصفوفة (array indices) كالتالي: for ( StringData data : stringData ) { g.setFill( data.color ); g.setFont( data.font); g.fillText( MESSAGE, data.x, data.y ); g.setStroke( Color.BLACK ); g.strokeText( MESSAGE, data.x, data.y ); } أثناء كل تَكْرار (iteration)، سيَحمِل المُتْغيِّر المُتحكِّم بالحلقة data نسخة من إحدى قيم المصفوفة. تلك القيمة عبارة عن مَرجِع (reference) إلى كائن (object) من النوع StringData المُتْضمِّن لمُتْغيِّرات النُسخة color و font و x و y. ناقشنا التحريكات (animations) بالقسم الفرعي 6.3.5. يُمكِنك أيضًا الإطلاع على شيفرة البرنامج بالكامل بالملف RandomStringsWithArray.java لمعرفة طريقة تّنْفيِذ التحريكة (animation). ينبغي أن يختار البرنامج RandomStringsWithArray نوع الخط المُستخدَم لرَسْم الرسالة عشوائيًا من خمسة خطوط محتملة. بالنسخة الأصلية من البرنامج، صَرَّحنا عن خمسة مُتْغيِّرات أسمائها هي font1 و font2 و font3 و font4 و font5 من النوع Font لتمثيل تلك الخطوط ثُمَّ اِستخدَمنا تَعْليمَة switch لاختيار إحدى تلك الخطوط عشوائيًا كالتالي: Font randomFont; // One of the 5 fonts, chosen at random. int rand; // A random integer in the range 0 to 4. fontNum = (int)(Math.random() * 5); switch (fontNum) { case 0: randomFont = font1; break; case 1: randomFont = font2; break; case 2: randomFont = font3; break; case 3: randomFont = font4; break; case 4: randomFont = font5; break; } سنُخزِّن الخطوط الخمسة داخل مصفوفة fonts بالنسخة الجديدة من البرنامج. صَرَّحنا عنها بهيئة مُتْغيِّر نسخة (instance variable) من النوع Font[] كالتالي: Font[] fonts; اِستخدمنا الباني (constructor) للإنشاء الفعليّ للمصفوفة بصيغة مُصنَّفة النوع (array literal) -اُنظر القسم الفرعي 7.1.3- كالتالي: fonts= new Font[] { Font.font("Times New Roman", FontWeight.BOLD, 20), Font.font("Arial", FontWeight.BOLD, FontPosture .ITALIC, 28), Font.font("Verdana", 32), Font.font(40), Font.font("Times New Roman", FontWeight.BOLD, FontPosture .ITALIC, 60) }; يُسهِل ذلك من عملية الاختيار العشوائي لنوع الخط المُستخدَم كالتالي: Font randomFont; // One of the 5 fonts, chosen at random. int fontIndex; // A random number in the range 0 to 4. fontIndex = (int)(Math.random() * 5); randomFont = fonts[ fontIndex ]; وبذلك نَكُون قد اِستخدَمنا أسطر قليلة من الشيفرة بدلًا من تَعْليمَة switch. يُمكِننا حتى أن نَضُم تلك الأسطر إلى سطر واحد كالتالي: Font randomFont = fonts[ (int)(Math.random() * 5) ]; يُعدّ ذلك تطبيقًا نمطيًا على المصفوفات (arrays). لاحِظ أن التَعْليمَة السابقة تَستخدِم خاصية الجَلْب العشوائي (random access) بمعنى اختيار فهرس مصفوفة (array index) بشكل عشوائي لجَلْب قيمة العنصر بذلك الفهرس مباشرةً. لنَفْحَص مثالًا آخر: عادةً ما تُخزَّن الأشهر بهيئة أعداد صحيحة: 1 و 2 و 3 .. وحتى 12. ستحتاج أحيانًا إلى تَحْوِيل تلك الأعداد إلى أسماء الأشهر المناظرة: يناير و فبراير و .. وحتى ديسمبر. يُمكِننا ببساطة أن نَستخدِم مصفوفة لإجراء ذلك التَحْوِيل. تُصرِّح (declare) التَعْليمَة التالية عن المصفوفة وتُهيِئها (initialize) كالتالي: static String[] monthName = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; إذا كان mnth عبارة عن مُتْغيِّر يَحمِل عددًا صحيحًا يتراوح بين 1 و 12، فإن monthName[mnth-1] يُمثِل اسم الشهر المناظر. ينبغي أن نُضيِف "-1" لأن الأشهر مُرقَّمة بدءًا من 1 بينما عناصر المصفوفة (array elements) مُرقَّمة بدءًا من صفر. المصفوفات الديناميكية (Dynamic Arrays) لقد ناقشنا الكيفية التي يُمكِننا بها اِستخدَام المصفوفات المملوءة جزئيًا (partially full array) لتَخْزِين قائمة من اللاعبين داخل لعبة، بحيث تَكبُر تلك القائمة أو تَتقلَّص أثناء تَشْغِيل اللعبة. يُطلَق الاسم "ديناميكي (dynamic)" على أي قائمة (list) يُمكِن أن يَتَغيَّر حجمها أثناء تّنْفِيذ البرنامج. تُعدّ القوائم الديناميكية شائعة الاستخدام، لذلك ربما من الأفضل كتابة صَنْف (class) يُمثِل ذلك المفهوم، وهو ما سيُمكِّننا من تَجنُّب إعادة كتابة نفس ذات الشيفرة بكل مرة نُريد فيها اِستخدَام هيكل بياني (data structure) مُشابِه. سنُصمِّم إذًا شيئًا مشابهًا للمصفوفة باستثناء أن يَكُون حجمها (size) قابلًا للتَغيِير. فَكِر بالعمليات التي نحتاج إلى إِجرائها على مصفوفة ديناميكية (dynamic array)، مثلًا: إضافة عنصر إلى نهاية المصفوفة حَذْف عنصر بمَوِضْع معين ضِمْن المصفوفة جَلْب قيمة إحدى عناصر المصفوفة ضَبْط قيمة إحدى عناصر المصفوفة جَلْب عدد العناصر الموجودة حاليًا بالمصفوفة عندما نُصمِّم ذلك الصَنْف، ستُصبِح تلك العمليات توابع نسخة (instance methods) داخل الصَنْف. سنُطبِق أيضًا نمط المصفوفة المملوءة جزئيًا (partially full array) لتَخْزِين العناصر بالمصفوفة الديناميكية (dynamic) أي ستُخزَّن العناصر بالنهاية داخل مصفوفة عادية. علاوة على ذلك، لابُدّ أن نُقرِّر ما ينبغي أن يَحدُث عند محاولة جَلْب عنصر مصفوفة غَيْر موجود، فقد نُبلِّغ مثلًا عن اِعتراض من النوع ArrayIndexOutOfBoundsException. بِفْرَض كَوْن عناصر المصفوفة من النوع int، يُمكِننا كتابة الشيفرة التالية: import java.util.Arrays; /** * Represents a list of int values that can grow and shrink. */ public class DynamicArrayOfInt { private int[] items = new int[8]; // partially full array holding the ints private int itemCt; /** * Return the item at a given index in the array. * Throws ArrayIndexOutOfBoundsException if the index is not valid. */ public int get( int index ) { if ( index < 0 || index >= itemCt ) throw new ArrayIndexOutOfBoundsException("Illegal index, " + index); return items[index]; } /** * Set the value of the array element at a given index. * Throws ArrayIndexOutOfBoundsException if the index is not valid. */ public void set( int index, int item ) { if ( index < 0 || index >= itemCt ) throw new ArrayIndexOutOfBoundsException("Illegal index, " + index); items[index] = item; } /** * Returns the number of items currently in the array. */ public int size() { return itemCt; } /** * Adds a new item to the end of the array. The size increases by one. */ public void add(int item) { if (itemCt == items.length) items = Arrays.copyOf( items, 2*items.length ); items[itemCt] = item; itemCt++; } /** * Removes the item at a given index in the array. The size of the array * decreases by one. Items following the removed item are moved up one * space in the array. * Throws ArrayIndexOutOfBoundsException if the index is not valid. */ public void remove(int index) { if ( index < 0 || index >= itemCt ) throw new ArrayIndexOutOfBoundsException("Illegal index, " + index); for (int j = index+1; j < itemCt; j++) items[j-1] = items[j]; itemCt--; } } // end class DynamicArrayOfInt ينبغي أن تَكُون الشيفرة بالأعلى واضحة. ربما قد تَتَساءل مع ذلك عن سبب اختيار العدد 8 كحجم مبدئي للمصفوفة. في الحقيقة، أنه مجرد رقم عشوائي أي يُمكِنك أن تختار أي عدد صحيح آخر، ولن يُؤثِر ذلك على الصَنْف (class). في العموم، لا تبدأ بمصفوفة ضخمة جدًا، فهي بالفعل مُصمَّمة لتُكيف حجمها مع عدد العناصر المطلوب. بالمثال ReverseInputNumbers.java، اِستخدَمنا مصفوفة مملوءة جزئيًا (partially full array) من النوع int لطباعة قائمة من الأعداد المُدْخَلة من قِبَل المُستخدِم بترتيب معاكس. اِستخدَمنا تحديدًا مصفوفة عادية طولها يُساوِي 100 لحَمْل الأعداد. في الواقع، قد يَكُون حجم المصفوفة كبيرًا جدًا أو صغيرًا جدًا مما قد يَتَسبَّب بحُدوث اِعترَاض (exception). نستطيع الآن إعادة كتابة البرنامج باستخدام الصَنْف DynamicArrayOfInt، والذي سيَتَكيَف مع أي عدد معقول من المُدْخَلات. يُمكِنك الإطلاع على شيفرته بالملف ReverseWithDynamicArray.java. على الرغم من أنه برنامج بسيط جدًا، لكن بإمكانك تطبيق نفس المبدأ بأي تطبيق آخر لا يُمكِنك تَوقُّع مقدار البيانات التي قد يحتاجها مقدمًا. تستطيع هياكل البيانات الديناميكية (dynamic data structure) عمومًا أن تَتَكيَف مع أي مقدار من البيانات، ولكن بالطبع بما يتناسب مع مساحة الذاكرة (memory) المُتاحة للبرنامج. يُعدّ الصَنف بالأعلى مثالًا جيدًا نوعًا ما على المصفوفات الديناميكية، ولكنه يُعانِي من مشكلة صغيرة. لنَفْترِض مثلًا أننا نريد أن نُنشِئ مصفوفة ديناميكية من النوع String. نظرًا لأن الصَنْف DynamicArrayOfInt مُصمَّم للتَعامُل مع النوع int فقط، لا يُمكِننا بطبيعة الحال أن نَستخدِم كائنًا (object) منه لحَمْل سلاسل نصية (strings). يبدو أننا سنضطّر إذًا إلى كتابة صَنْف جديد DynamicArrayOfString. بالمثل، إذا أردنا أن نُنشِئ مصفوفة ديناميكية لتَخْزِين لاعبين من النوع Player، سنضطّر إلى كتابة صَنْف جديد DynamicArrayOfPlayer، وهكذا. يَعنِي ذلك ضرورة كتابة صَنْف جديد لكل نوع من البيانات، وهو ما لا يُمكِن أن يَكُون أمرًا منطقيًا. في الواقع، توفِّر الجافا الصَنْف القياسي ArrayList كحلّ لتلك المشكلة حيث يُمثِل مصفوفة ديناميكية (dynamic arrays) يُمكِنها التَعامُل مع أي نوع من البيانات. ترجمة -بتصرّف- للقسم Section 2: Array Processing من فصل Chapter 7: Arrays and ArrayLists من كتاب Introduction to Programming Using Java.1 نقطة
-
لا شيء يجعلك تتأكّد أنّك على الطريق الصحيح بفكرة مشروعك أكثر من وجود شخص يدفع المال مقابل الحصول على منتجك أو خدمتك. سواءً كنت تدير مطعمًا، أو تبني موقعًا تجاريًا على الإنترنت، أو تُصمّم تطبيقًا، أو حتّى إن كنت تريد إنشاء شركة استشارات خاّصة بك، فإنّ أوّل بضعة عملاء لك هم من سيحدّدون حرفيًا حياة أو موت مشروعك. فلماذا تنتظر بعد ما أنفقت كل ما أنفقته من الساعات الطوال والأموال والمجهود في بناء مشروعك؟ ماذا تنتظر لتجد أول عملاء أوفياء؟ دعونا ننظر إلى بعض الأسباب التي من أجلها يجب أن تحاول الوُصول إلى جُمهور قبل الشروع في أيّ شيء آخر، ونتعرف على كيفية تحقيق ذلك. لماذا جمهورك هم سِرّ بناء مشروعك؟ سنأخذ مثالًا عمليًا لتوضيح الأمر. "بات فلين" هو رائد أعمال أمريكي، ومدوّن، وبودكاستر (مدوّن صوتي)، وهو مشهور بموقعه SmartPassiveIncome، وهو يتكسّب من وراء توفير المحتوى المبدع والأدوات الفعّالة لجمهوره المُستهدَفِين. لكنّه لم يكن هو الذي بدأ ذلك، وإنّما جمهوره هم الذين طلبوا منه! في 2008 فُصِل "بات" من عمله كمهندس معماري، فقرّر حينها أن يستغلّ وقته ومجهوده في تنمية مواهبه ومهاراته، وبدأ الدراسة من أجل الحصول على شهادة LEED (شهادة في الريادة في تصميمات الطاقة والبيئة، وهو نظام مُعترَف به دوليًا كمقياس تصميم وإنشاء وتشغيل مبانٍ مراعية للبيئة). في تلك الأثناء قام بإنشاء موقع ليشارك فيه التجارب التي تعلّمها في مسيرة حياته: "كنت أفعل كلّ ما هو مرتبط بشهادة LEED، ولم أتخيل نفسي كرائد أعمال". هذا ما صرّح به "بات" لموقع Forbes في 2014 كانت بداية ذلك العمل هو كتاب إلكتروني يشارك فيه دروسه التعليمية. عندما نشر الكتاب لجمهوره أدرّ عليه أرباحا أكثر من 7000 دولار في الشهر الأوّل فقط. أين تجد جمهورك الأوّل؟ قام "بات" باختبار صلاحية فكرة مشروعه عن طريق الكتابة على المُدَوَّنات، ولكن ماذا تفعل أنت قبل أن تبدأ حتى؟ أو ماذا لو كنت تشعر أن التدوين ليس بالشيء الذي تُجيده؟ هناك طرق أخرى للتواصل مع جمهور من نفس ميولك، ومعرفة ما إذا كان هناك حاجة حقيقيّة إلى منتجك. يصف "بات" الأمر بأنه بحث عن ثلاثة أشياء: الناس: من هم المشاهير الموجودون في مجالك؟ ما هي أشهر حسابات تويتر، وأشهر حسابات فيسبوك، ومن هم مدراء أشهر المجموعات على فيسبوك؟ من هم مُلّاك أشهر المُدَوَّنات العاديّة والمدونات الصوتيّة (البودكاست)؟ الأماكن: ما هي المواقع، والمنتديات، والاجتماعات والمؤتمرات التي يزورها جمهورك المُستهدَف؟ المُنتَجات: ما هي المنتجات أو الخدمات الموجودة بالفعل في السوق أمام جمهورك المُستهدَف؟ ليس من الضروري أن تكون تلك المنتجات رائجة في السوق، فمجرد وجودها أمر مهم. ويوضح "بات" بشكل أعمق في السطور التالية: ماذا يمكن أن تفعله أفضل من ذلك؟ إذا كنت تريد طرح منتج مادي، فاذهب إلى موقع أمازون واطّلع على تقييمات منافسيك. لا تتوقف أهمية هذه التقييمات عند كونها حقيقيّة، ولكنّها أيضًا ستوضّح لك المُميّزات والعيوب في ذلك المنتج. بالتالي ليكون منتجك أفضل فاحرص على تواجد تلك المُميّزات عندك، وتأكد من عدم تواجد تلك العيوب. خطوات متكاملة لتحويل جمهورك من مجرد متابعين إلى أول عملاء يشترون منك والآن بما أن لديك فكرة واضحة عمّن تبحث عنهم، قد حان الوقت لتحويل أولئك الأشخاص إلى عملاء محتملين. يبدأ ذلك عن طريق التعرف عليهم وبناء علاقات شخصيّة جيّدة معهم. وكما يقول "بات"، عندما تبدأ يكون لك هذه الميزة التنافسيّة: فالخطوة التالية بطبيعة الحال هي التعرف على هؤلاء الأشخاص الذين يستطيعون مساعدتك، والبدء في بناء وتقوية علاقات حقيقيّة معهم. إليك -خطوة بخطوة- كيفية تحقيق ذلك: الخطوة الأولى: قم بصياغة فقرة قصيرة تعريفيّة تُمثّل فكرتك بأفضل شكل ممكن يقترح "رايان روبنسون" (رائد الأعمال ومؤسس دورة " اختبار الصلاحية في30 يومًا") البدء في بناء علاقاتك بكتابة فقرة قصيرة تُمثّل فكرتك بأفضل شكل ممكن أو ما يعرف بحديث المصعد (لأنه لابد أن يستغرق وقتًا كالذي يستغرقه مصعد في الوصول إلى الطابق المطلوب) ابدأ بالتفكير حول موقعك من السوق الذي اكتشفته نتيجة بحثك وتحليلك السابق عن الأشياء الثلاثة (الناس- الأماكن- المنتجات) ماذا يَنْقُص السوق؟ أين يحتاج الناس لأكبر مساعدة؟ أين يمكن أن تقدّم قيمة أكبر من الموجودة حاليًا؟ هذه الأسئلة الأساسيّة ستفتح لك الآفاق لكتابة أفضل فقرة تعبّر فيها عن منتجك أو خدمتك. بالنسبة لـ"رايان"، أراد إنشاء مشروع جديد عن التنزّه على الأقدام (الهايكنج) والرحلات الخلويّة في كاليفورنيا. خلال بحثه عن منافسيه، وجد أن محتوى معظم المواقع المُختصّة بالتنزّه على الأقدام والرحلات الخلويّة في كاليفورنيا متواضع إلى حد ما، فالصور كانت في الغالب صور أرشيفيّة، والمحتوى المكتوب كان يغلب عليه التعميم، وإفادته ضعيفة للمُتنزّهين الراغبين في التقاط صور مذهلة في رحلاتهم. لذا فقد قام بالاعتماد على هذه الميزة وصاغ فقرته التعريفيّة كالتالي: في النهاية هذا ليس بأفضل شيء ممكن، ولكن يعتبر جيدًا كِفايةً كبداية. الخطوة الثانية: كوّن مجموعة من الأشخاص الذين يمُدّونك بالآراء والنقد في وقت مبكّر لكي تسير الأمور بسهولة، يُفضّل أن يكون أوّل أناس تبحث عنهم ليساعدوك بهذا الشأن هم من تعرفهم مسبقًا؛ وذلك ليكونوا على استعداد للاستماع لك والاهتمام بك. لكنّ هذا لا يعني بالضرورة أن يكون أبَواك مثلًا من ضمنهم (إلّا إذا كانوا بالفعل ضمن السوق الذي تستهدفه)، وعلى أيّة حال فإنّ الأصدقاء وأفراد العائلة ممّن سيستخدمون منتجك أو خدمتك سيكونون ممتازين بخصوص إعطائك الآراء والتقييم. دعونا نلقي نظرة أخرى على بعض الرسائل الأُولى التي أرسلها "رايان" في البداية: يمكنك البدء بهذه المجموعات: الأصدقاء والعائلة زملاء العمل الحاليّين والسابقين المتخصّصين الآخرين الذين عملت معهم من قبل زملاء الدراسة الحاليّين والقدامى مُدرّسيك وأساتذتك معارفك في النوادي والمجموعات المختلفة وغيرها يمكنك أيضًا مراسلة الأشخاص الموجودين في جهات الاتصال في هاتفك أو على بريدك الإلكتروني، أو موقع لينكد إن، وكذلك أصدقائك على الفيسبوك، ومتابعيك على تويتر وغيرهم من معارفك وزملاءك الذين تظنّ أنهم قد يكونوا مُهتمّين بما تعمل عليه. بعد ذلك انتقل لشريحة أخرى، وذلك بسؤال هؤلاء الأشخاص الذين أبدوا اهتماما بفكرتك عن أصدقائهم ومعارفهم الذين قد يكونون هم أيضًا لديهم اهتمام بمشروعك. قم بعد ذلك بالمتابعة مع أولئك الأشخاص ولا تُهملهم فهم يُمثّلون ثروتك الحقيقيّة التي ستبدأ بها مشروعك. يمكنك تتبُّع وتفقّد هذه العملية عن طريق استخدام برنامج مثل "إكسل" أو "جداول بيانات جوجل "، فتُتابع عن طريق ذلك قاعدة جمهورك المتنوّعة من الأشخاص الذين راسلتهم، ومن يحتاجون المتابعة معهم، ومن هم معك بالفعل. الخطوة الثالثة: توَسَّع خارج دائرة الأصدقاء والعائلة الآن بعدما زاد الحماس، حان الوقت للنظر خارج منطقة الأمان بالنسبة لك، والتحرك في اتّجاهات مختلفة لتوسيع علاقاتك. إذا لم يكن لديك شبكة واسعة من العلاقات، أو إذا كنت تستهدف سوقًا لا تعرف الكثير من الناس فيه، فإن هذه الخطوة مُهمّة لك. إذًا فأين يمكن أن تجد تلك العلاقات؟ عليك بمواقع التواصل الاجتماعي والتجمّعات الموجودة على الإنترنت. فمواقع مثل فيسبوك، ولينكد إن، وريديت، وقورة، كلها تجَمّعات تُوفّر أمامك مجموعات مُخصّصة لفكرة مشروعك وسوقك المُستهدَف. ومفتاح النجاح في تلك التجمّعات هو القدرة على التحدّث مع رُوّادها بلباقة وذكاء، بدون مضايقة أو إلحاح أو إزعاج. على سبيل المثال، لا تَأمر أحدًا بالدخول إلى موقعك، ولا أن يملأ استمارة ولا أن يفعل أيّ شيء آخر. بدلًا من ذلك، حاول فقط أن تكون مُساهمًا فعّالًا وجزءًا من المجموعة التي تستهدفها. خذ كمثال هذا النص القادم الذي استلهمه "رايان" من أحد مستشاريه وهو "راميت سيثي"، وهو قد يعطيك فكرة عن كيفية بدء المحادثات مع الأشخاص الجدد: بمجرد ما يبدأ الناس في التجاوب معك، قم بإثراء النقاش بينكم، وشجعهم على التحدّث باستفاضة أكثر، وحفزهم على الإتيان بكل ماعندهم، وشارك معهم أفكارك بخصوص هذا الأمر. عندما تنتهي المحادثة بينكم، قم بالاستمرار في التواصل مع الأشخاص الذين كانوا أكثر نشاطًا في المناقشة؛ فسيكون لديهم سبب قويّ للرغبة في التواصل معك مرة أخرى. قم بتكرار هذه العمليّة إلى أن تبْنِي مجموعة جيّدة من الأشخاص الذين تأخذ آرائهم باستمرار، على الأقل من 25 إلى 50 شخصًا، فتستطيع بذلك أن تجد تنوّعا في الآراء وإثراء في النقد. هناك آلاف المجتمعات التي يمكنك الانضمام إليها. هذه بعض منها ممّا يمكنك البدء به: المجتمعات المستقّلة على الإنترنت: المنتدى المفتوح لأصحاب المشاريع الصغيرة بموقع AmericanExpress (مُتعدّد المجالات) Hsoub I/O (مجمتع عربي شامل يتضمن كافة المجالات) Angel.co (يركّز على مجتمع الشركات الناشئة) Inbound.org (يركّز على التسويق) GrowthHackers (يركّز على النمو والتسويق) HackerNews (يركّز على الشركات الناشئة والمشاريع والأخبار التقنيّة) Quora (مُتعدّد المجالات) ProductHunt (يركّز على الشركات الناشئة والتقنيّة) وهناك أداة جيّدة أخرى يمكن استخدامها وهي تقييمات موقع أمازون (إذا كنت تصنع منتجًا ماديًا). مجموعات الفيسبوك المتخصّصة في مواضيع معينة هناك الكثير من المجموعات المتنوّعة على موقع فيسبوك، والتي يمكن أن تجد منها ما يناسب مجال مشروعك وسوقك المُستهدَف وتنضمّ إليها. من المفترض أنّك وجدت هذه المجموعات عند بحثك عن "الأماكن" في أثناء بحث الأشياء الثلاثة الذي سبق ذكرها، ولكن إن لم تجد، فإنّ بحثًا بسيطًا منك سيفي بالغرض. ضع في اعتبارك أنّه بالرغم أنّ هذه المجتمعات أداة سهلة للوصول إلى جمهورك الأوائل، إلا أنّك لن تستطيع الاستفادة منها ما لم تُضِف إليها قيمة جيدة، فلا أحد يحب المزعجين الموجودين على الإنترنت الذين يتنقّلون هنا وهناك سعيًا لبيع سلعهم بدون بناء أي علاقة قويّة مع جمهورهم، فإن كنت مثلهم فهذا سيضرّك أكثر ممّا ينفعك. الخطوة الرابعة: ابدأ في محادثة فرديّة لكل فرد في جمهورك الأساسي عند هذه النقطة يجب أن تكون قد وقفت على أرض ثابتة مع مجموعة من العملاء المُحتمَلين، حينها يمكنك أن تبدأ في التحدّث معهم على مستوى شخصي. ابحث عما يحتاجونه. تأكد من وجود المُميّزات التي يرغبونها في منتجك وعدم وجود العيوب التي ينفِرون منها، ومن ثَمّ يمكن تحويل هؤلاء الأشخاص إلى أول عملاء يشترون منك. وقد كانت استراتيجيّة المحادثة الفردية هذه هي ما صنعت أول مشروع للمُصمّم والكاتب ورائد الأعمال "بول جارفيس"، وذلك المشروع هو Creative Class. مع خبرة "بول" في العمل الحرّ لأكثر من 14 سنة، كان يتلقى 100 بريد إلكتروني أسبوعيًا من العاملين في مجال العمل الحرّ يطلبون منه النصيحة. "كنت أدوّن الأسئلة التي يسألني الناس إياها لأبحث عن إجابات لها، وفكّرت في نفسي، إذا سألني 100 شخص مثلًا نفس السؤال وأجبتهم، فلم لا أجيب عن السؤال مرة إضافيّة في دروس ودورات؟" إضافة إلى ذلك، عندما نشر "بول" دورته لأول عملاء اشتروا منه، قام بسؤالهم عما كانوا يفتقدونه في تلك الدورة، وما كانوا يتمنّون أن يقدمه فيها. "بدأْتُ بـ30 إلى 35 فكرة لدروس تعليميّة، ثم انتقَيْت منها في النهاية 7 فقط. بمجرد ما بدأ الناس في شراء الدورة سألتُهم: ماذا كنتم تريدون تعلمه ممّا لم تغطّيه الدورة؟ وتحولت إجابات هذا السؤال إلى 5 دروس إضافيّة. وقد حصلَت هذه الدروس الإضافيّة على مشاهدات أعلى من نظيرتها الأصليّة!" يستخدم "بات" أيضًا نفس الطريقة لمنتجاته، حيث يعرضها في البداية أمام مجموعة صغيرة من العملاء على مستوى MVP (الحد الأدنى لمنتج مقبول)، وبعد ذلك يقوم بتعديل المنتج طبقًا لما جاءه منهم من آراء. "إنهم يخبرونني بما يجب أن يكون في منتجي، بحيث أنّي عندما أضع منتجي على الملأ لا أحتاج للتخمين، فأنا أعلم ما يحتاجونه بالفعل. إن كان يبدو هذا شيئًا مُعقّدًا نوعًا ما، فإنّنا لا زلنا لم نتعمّق في الأمر، فاختبار صلاحية فكرة مشروعك وإيجاد أوّل عملاء تربح منهم أمر ليس بالسهل. ترجمة -وبتصرّف- للمقال How to find your first paying customers لصاحبه Jory MacKay حقوق الصورة البارزة محفوظة لـ Freepik1 نقطة