تحتاج برامج كثيرة إلى قيم عشوائية (على ما يبدو) لمحاكاة أحداث من الواقع.
من الأمثلة الشائعة والمألوفة:
('Heads', 'Tails').توليد قيم عشوائية حقًّا باستخدام الحاسوب هو تحدٍّ تقني صعب بشكل مفاجئ، لذا قد ترى هذه النتائج توصف بأنها «شبه عشوائية».
عمليًّا، مكتبة مصممة جيدًا مثل وحدة random في مكتبة Python القياسية سريعة ومرنة، وتعطي نتائج جيدة بما يكفي لغالبية التطبيقات في النمذجة والمحاكاة والألعاب.
ستعرض بقية هذه الصفحة بعضًا من أكثر الدوال شيوعًا في random.
نشجعك على استكشاف توثيق random الكامل، فهناك خيارات أكثر بكثير مما نغطيه هنا.
قبل أن تتمكن من استخدام الأدوات في وحدة random، عليك أولًا استيرادها:
>>> import random
# Choose random integer from a range
>>> random.randrange(1000)
360
>>> random.randrange(-1, 500)
228
>>> random.randrange(-10, 11, 2)
-8
# Choose random integer between two values (inclusive)
>>> random.randint(5, 25)
22
لتجنّب كتابة اسم الوحدة في كل مرة، يمكنك استيراد دوال محددة بالاسم:
>>> from random import choice, choices
# Using choice() to pick Heads or Tails 10 times
>>> tosses = []
... for side in range(10):
... tosses.append(choice(['H', 'T']))
>>> print(tosses)
['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
# Using choices() to pick Heads or Tails 8 times
>>> picks = []
... picks.extend(choices(['H', 'T'], k=8))
... print(picks)
['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
للدالة randrange() ثلاث صيغ لاختيار قيمة عشوائية من range(start, stop, step):
randrange(stop) عددًا صحيحًا n بحيث 0 <= n < stop
randrange(start, stop) عددًا صحيحًا n بحيث start <= n < stop
randrange(start, stop, step) عددًا صحيحًا n بحيث start <= n < stop وn يقع في المتتالية start, start + step, start + 2*step...
في الحالة الشائعة التي يكون فيها step == 1، قد تكون الدالة randint(a, b) أكثر ملاءمة ووضوحًا.
فالنتائج المحتملة من randint() تشمل الحد الأعلى، لذا فإن randint(a, b) تكافئ استخدام randrange(a, b+1):
>>> import random
# Select one number at random from the range 0, 499
>>> random.randrange(500)
219
# Select 10 numbers at random between 0 and 9 two steps apart.
>>> numbers = []
... for integer in range(10):
... numbers.append(random.randrange(0, 10, 2))
>>> print(numbers)
[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]
# roll a die
>>> random.randint(1, 6)
4
تفترض الدوال في هذا القسم أنك تبدأ من متتالية أو حاوية أخرى.
عادةً ما تكون هذه list، أو مع بعض القيود tuple أو set (فالـ tuple غير قابل للتغيير، وset غير مرتب).
choice() وchoices()
تُرجع الدالة choice() عنصرًا واحدًا مختارًا عشوائيًّا من متتالية معطاة.
وفي أبسط صورها، قد تكون رمي قطعة نقدية:
# This will pick one of the two values in the list at random 5 separate times
>>> [random.choice(['H', 'T']) for _ in range(5)]
['T', 'H', 'H', 'T', 'H']
يمكننا تحقيق الشيء نفسه تقريبًا باستخدام الدالة choices()، مع تمرير وسيط بالكلمة المفتاحية يحدد طول المصفوفة:
>>> random.choices(['H', 'T'], k=5)
['T', 'H', 'T', 'H', 'H']
في الأمثلة أعلاه، افترضنا قطعة نقدية عادلة باحتمال متساوٍ لظهور الصورة أو الكتابة، لكن يمكن أيضًا تحديد أوزان. على سبيل المثال، إذا كان في كيس 10 كرات حمراء و15 كرة خضراء، وأردنا سحب واحدة منها عشوائيًّا:
>>> random.choices(['red', 'green'], [10, 15])
['red']
sample()يفترض مثال choices() أعلاه ما يسميه الإحصائيون «السحب مع الإرجاع».
فكل سحبة أو اختيار بلا أي تأثير على احتمال الاختيارات اللاحقة، ويبقى توزيع الخيارات المحتملة نفسه من سحبة إلى أخرى.
في مثال الكرات الحمراء والخضراء: بعد كل اختيار، نُعيد الكرة إلى الكيس ونرجّه جيدًا قبل السحبة التالية. وهذا على النقيض من حالة نسحب فيها كرة حمراء و_تبقى في الخارج_. فعدم إعادة الكرة يعني أن عدد الكرات الحمراء في الكيس صار أقل، وأن الاختيار التالي صار أقل احتمالًا لأن يكون أحمر.
لمحاكاة «السحب دون إرجاع» هذه، توفّر وحدة random الدالة sample().
وصياغة sample() شبيهة بـ choices()، إلا أنها تضيف معاملًا بالكلمة المفتاحية counts:
>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']
تُرجع العينات بالترتيب الذي اختيرت به.
shuffle()تُرجع كل من choices() وsample() مصفوفات جديدة عندما يكون k > 1.
في المقابل، تُبعثر shuffle() ترتيب المصفوفة في مكانها، ويضيع الترتيب الأصلي:
>>> my_list = [1, 2, 3, 4, 5]
>>> random.shuffle(my_list)
>>> my_list
[4, 1, 5, 2, 3]
حتى الآن، ركّزنا على الحالات التي تكون فيها جميع النتائج متساوية الاحتمال.
على سبيل المثال، random.randrange(100) متساوية في احتمالها لإعطاء أي عدد صحيح من 0 إلى 99.
العديد من مواقف الحياة الواقعية أقل بساطة بكثير من ذلك.
ونتيجة لذلك، أنشأ الإحصائيون مجموعة واسعة من distributions لوصف نتائج «العالم الواقعي» رياضيًّا.
بالنسبة إلى الأعداد الصحيحة، تُستخدم randrange() وrandint() عندما تكون جميع الاحتمالات متساوية.
ويُسمى هذا توزيعًا uniform.
توجد نظائر بالفاصلة العائمة لـ randrange() وrandint().
random() تعطي قيمة float اسمها x بحيث 0.0 <= x < 1.0.
uniform(a, b) تعطي x بحيث a <= x <= b.
>>> [round(random.random(), 3) for _ in range(5)]
[0.876, 0.084, 0.483, 0.22, 0.863]
>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
[2.798, 2.539, 3.779, 3.363, 4.33]
يُسمى أيضًا التوزيع «الطبيعي» أو المنحنى «على شكل جرس»، وهو أسلوب شائع جدًّا لوصف عدم الدقة في القيم المقيسة.
على سبيل المثال، افترض أن المصنع الذي تعمل فيه اشترى للتو 10,000 مسمار يُفترض أنها متطابقة.
وتريد ضبط روبوت المصنع للتعامل معها، فتقيس عينة من 100 منها وتجد أن متوسط وزنها (أو mean) هو 4.731 غرام.
من المستبعد جدًّا أن يعني هذا أنها كلها تزن بالضبط 4.731 غرام.
وربما تجد أن القيم تتراوح بين 4.627 و4.794 غرام لكنها تتجمع حول 4.731 غرام.
هذا هو Gaussian distribution، حيث تبلغ الاحتمالات ذروتها عند المتوسط وتتناقص بشكل متماثل على الجانبين (ومن هنا التسمية «على شكل جرس»).
ولمحاكاة هذا في البرمجيات، نحتاج إلى أسلوب ما لتحديد عرض المنحنى (فالمسامير الغالية تتجمع عادةً حول المتوسط بتقارب أكبر من المسامير الرخيصة!).
وبحسب العُرف، يُفعل هذا باستخدام standard deviation: قيم صغيرة لمنحنى حاد وضيّق، وقيم كبيرة لمنحنى منخفض وواسع.
يعشق الرياضيون الحروف اليونانية، لذا نستخدم μ («مو») لتمثيل المتوسط وσ («سيغما») لتمثيل الانحراف المعياري.
وبالتالي، إذا قرأت أن «95% من القيم تقع ضمن 2σ من μ» أو «اكتُشف بوزون هيغز بثقة تبلغ 5 سيغما»، فإن مثل هذه العبارات تتعلق بالانحراف المعياري.
>>> mu = 4.731
>>> sigma = 0.316
>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
[4.72, 4.957, 4.64, 4.556, 4.968]