مقدمة في علوم الحاسوب النظرية › الاحتساب والتمثيل 4 / 26 السابق التالي

الحوسبة والتمثيل {#chaprepres }

الحوسبة والتمثيل

  • التمييز بين المواصفة (specification) و_التنفيذ_ (implementation)، أو بما يعادل ذلك بين الدوال الرياضية (mathematical functions) و_الخوارزميات/البرامج_ (algorithms/programs).
  • تمثيل كائن كسلسلة (غالبًا من الأصفار والواحدات).
  • أمثلة على تمثيلات لكائنات شائعة مثل الأعداد والمتجهات والقوائم والرسوم البيانية.
  • التمثيلات الخالية من البادئات (prefix-free representations).
  • مبرهنة كانتور: لا يمكن تمثيل الأعداد الحقيقية تمثيلًا دقيقًا بوصفها سلاسل منتهية.

"كان الأبجدية (sic) اختراعًا عظيمًا، مكّن الرجال (sic) من أن يخزّنوا ويتعلّموا بجهدٍ يسير ما تعلّمه غيرهم بالكاد، أي أن يتعلّموا من الكتب بدلًا من الاتصال المباشر، المحتمل أن يكون مؤلمًا، بالعالم الحقيقي."، ب. ف. Skinner

"اسم الأغنية يدعى `HADDOCK'S EYES.'" [قال الفارس]

"آه، هذا إذن هو اسم الأغنية؟" قالت أليس، وهي تحاول أن تُبدي اهتمامًا.

"لا، أنت لا تفهمين شيئًا،" قال الفارس وهو ينظر بعضوية الانزعاج. "هذا هو الاسم الذي تُدعى به. الاسم هو حقًا `THE AGED AGED MAN.' "

"إذن كان ينبغي لي أن أقول `هذا هو ما تُدعى به الأغنية'؟" صحّحت أليس كلامها.

"لا، لا ينبغي لك: فذاك أمر مختلف تمامًا! الأغنية تُدعى `WAYS AND MEANS': لكن ذاك ليس سوى ما تُدعى به فحسب، أنت تعرف!"

"حسنًا، إذن ما هي الأغنية؟" قالت أليس، وقد صارت في هذه اللحظة تائهه تمامًا.

" كنت قادمًا إلى ذلك،" قال الفارس. "الأغنية هي حقًا `A-SITTING ON A GATE': واللحن من اختراعي أنا."

لويس كارول، عبر المرآة (Through the Looking-Glass)

في تقريب أول، الحوسبة هي عملية تنقل مُدخَلًا (input) إلى مُخرَج (output).

/images/introtcs/lec_02_representation-1.webp

عند مناقشة الحوسبة، من الضروري فصل مسألة ما هو المهمة التي نحتاج إلى إنجازها (أي المواصفة) عن مسألة كيف نحقق هذه المهمة (أي التنفيذ). فمثلًا، كما رأينا، هناك أكثر من طريقة لتحقيق المهمة الحاسوبية الخاصة بحساب حاصل ضرب عددين صحيحين.

في هذا الفصل نركّز على جانب ما، أي تعريف المهام الحاسوبية. ولنبدأ، نحتاج إلى تعريف المُدخَلات والمُخرَجات. فالإفادة من كل المُدخَلات والمُخرَجات المحتملة التي قد نرغب يومًا في حسابها تبدو أمرًا صعبًا، إذ إن الحوسبة اليوم تُطبَّق على مجموعة واسعة جدًا من الكائنات. فنحن لا نحسب على الأعداد فحسب، بل أيضًا على النصوص والصور والفيديوهات ورسوم اتصال الشبكات الاجتماعية ومسارات التصوير بالرنين المغناطيسي (MRI) وبيانات الجينات، بل وحتى على برامج أخرى. وسنمثّل كل هذه الكائنات بوصفها سلاسل من الأصفار والواحدات، أي كائنات مثل 00111010011101 أو 10111011 أو أي قائمة منتهية أخرى من 11 و00. (هذا الاختيار للتيسير: فلا شيء «مقدّس» في الأصفار والواحدات، وكان بإمكاننا استعمال أي مجموعة أخرى منتهية من الرموز.)

/images/introtcs/lec_02_representation-2.webp

اليوم، اعتدنا إلى حدٍّ كبير على فكرة التمثيل الرقمي، حتى لا ندهش من وجود مثل هذا الترميز. لكنه في الحقيقة بصيرة عميقة ذات آثار بالغة. يستطيع كثير من الحيوانات أن توصّل خوفًا معيّنًا أو رغبة، لكن ما يميز البشر هو اللغة: فنحن نستخدم مجموعة منتهية من الرموز الأساسية لوصف نطاق تجارب قد يكون غير محدود. وتتيح اللغة نقل المعلومات عبر الزمان والمكان، وتُمكّن مجتمعاتٍ تمتد على أعدادٍ كبيرة من الناس وتُراكم جسمًا من المعرفة المشتركة مع مرور الوقت.

خلال العقود العديدة الماضية، شهدنا ثورةً فيما نستطيع تمثيله ونقله بالشكل الرقمي. فأصبح بإمكاننا الإمساك بالتجارب بدقة تكاد تكون تامة، ونشرها آنيًا إلى حدٍّ كبير لجمهور غير محدود. وفضلا عن ذلك، متى أصبحت المعلومة في صيغة رقمية، أمكننا أن نحسب فوقها، فنستنبط رؤى من بيانات لم تكن متاحة في الأزمنة السابقة. وقلب هذه الثورة ملاحظةٌ بسيطة لكنها عميقة، وهي أنّه يمكننا تمثيل تنوّعٍ غير محدود من الكائنات باستخدام مجموعة منتهية من الرموز (بل باستخدام الرمزين 0 و1 فحسب).

في الفصول اللاحقة، سنُسقط هذه التمثيلات عادةً أمرًا مفترضًا، ومن ثم نستعمل تعبيرات مثل «البرنامج PP يأخذ xx مُدخَلًا» حين يكون xx عددًا أو متجهًا أو رسمًا بيانيًا أو أي كائن آخر، بينما المقصود حقًا هو أن PP يأخذ تمثيلَ xx بوصفه سلسلة ثنائية مُدخَلًا. غير أن هذا الفصل سنمكث فيه قليلًا أكثر على كيفية بناء مثل هذه التمثيلات.

الخلاصات الرئيسة من هذا الفصل هي:

  • يمكننا تمثيل جميع أنواع الكائنات التي نرغب في استخدامها بوصفها مُدخَلات ومُخرَجات باستخدام السلاسل الثنائية (binary strings). فمثلًا يمكننا استخدام الأساس الثنائي (binary basis) لتمثيل الأعداد الصحيحة والأعداد النسبية في سلاسل ثنائية (انظر تمثيل الأعداد الطبيعية والتمثيلات خارج الأعداد الطبيعية).

  • يمكننا تركيبَ تمثيلات الكائنات البسيطة لتمثيل كائنات أعقد. وهكذا يمكننا تمثيل قوائم الأعداد الصحيحة أو النسبية، ونستعمل ذلك لتمثيل كائنات مثل المصفوفات والصور والرسوم البيانية. و_الترميزُ الخالي من البادئات (prefix-free encoding) إحدى الطرق لتحقيق هذا التركيب (انظر الترميزُ الخالي من البادئات).

  • المهمةُ الحاسوبية (computational task) تصف دالة من مُدخَل إلى مُخرَج، أي دالةً (function). ومن المهم جوهريًا التمييز بين «ما» و«كيف»، أو بين المواصفة و_التنفيذ_ (انظر ميّزوا الدوال عن البرامج!). فـ_الدالة_ تحدّد ببساطة أيّ مُخرَج يقابل أيّ مُدخَل، ولا تحدّد _كيفَ يُحسب المُخرَج من المُدخَل، وكما رأينا في سياق الضرب، قد توجد أكثر من طريقة لحساب الدالة نفسها.

  • بينما تكون مجموعة كل السلاسل الثنائية الممكنة لا نهائية، فإنها لا تستطيع تمثيل كل شيء. وعلى وجه التحديد، لا يوجد تمثيل لـ_الأعداد الحقيقية_ (real numbers) (بدقة مطلقة) في سلاسل ثنائية. وتُعرف هذه النتيجة أيضًا بـ«مبرهنة كانتور» (انظر مبرهنة كانتور، والمجموعات القابلة للعدّ، وتمثيلاتُ الأعداد الحقيقية في سلاسل)، ويشار إليها عادةً بالنتيجة القائلة إن «الأعدادَ الحقيقيةَ غير معدودة». كما يُفهم ضمنًا أن هناك مستوياتَ (levels) مختلفة من اللانهائية، وإن كنا لن ندخل في هذا الموضوع في هذا الكتاب (انظر ملاحظة — تعميم يتجاوز السلاسل والأعداد الحقيقية).

«الفكرتان الكبريان» اللتان نناقشهما هما representtuplesidea — يمكننا تركيب تمثيلات للكائنات البسيطة لتمثيل كائنات أعقد — وfunctionprogramidea — من الضروري التمييز بين الدوالِ («ما») و_البرامجِ_ («كيف»). وستكون الثانية موضوعًا سنعود إليه مرارًا في هذا الكتاب.

تعريف التمثيلات

في كل مرة نخزّن فيها أعدادًا أو صورًا أو أصواتًا أو قواعد بيانات أو كائنات أخرى في حاسوب، فإن ما نخزّنه حقًا في ذاكرة الحاسوب هو تمثيل هذه الكائنات. وفضلا عن ذلك، فإن فكرة التمثيل لا تقتصر على الحواسيب الرقمية. فحين نكتب نصًّا أو نرسم صورة، فإننا نُمثّل أفكارًا أو تجارب في صورة سلاسل من الرموز (والتي قد تكون سلاسل من الأصفار والواحدات تمامًا). حتى مخّ أدمغتنا لا يخزّن المُدخَلات الحسية الفعلية التي نخوضها، بل يخزّن تمثيلَها فحسب.

لاستعمال كائنات مثل الأعداد أو الصور أو الرسوم البيانية أو غيرها كمدخلات للحساب، نحتاج إلى تعريف دقيق لكيفية تمثيل هذه الكائنات في سلاسل ثنائية. فـ_مخطّطُ التمثيل_ (representation scheme) هو طريقة تُسقط كائنًا xx على سلسلة ثنائية E(x)∈{0,1}∗E(x) \in \{0,1\}^*. فمثلًا، مخطّط تمثيل للأعداد الطبيعية هو دالة E:N→{0,1}∗E:\N \rightarrow \{0,1\}^*. بالطبع لا يمكننا أن نكتفي بتمثيل جميع الأعداد بالسلسلة "00110011" (مثلًا). والحد الأدنى المطلوب هو أن إذا اختلف عددان xx وx′x' فإنهما يتمثّلان بسلسلتين مختلفتين. وهي صياغة أخرى لقولنا إننا نطلب من دالة الترميز EE أن تكون واحد-لواحد (one to one).

تمثيل الأعداد الطبيعية

نبيّن الآن كيف يمكننا تمثيل الأعداد الطبيعية في سلاسل ثنائية. فقد مثّل الناس الأعداد على مر السنين بطرق متنوعة، منها الأرقام الرومانية وعلامات العدّ ونظامنا العشري الهندي-العربي، وطرائق أخرى كثيرة. ويمكننا استعمال أي واحدة منها، إضافةً إلى طرق أخرى كثيرة، لتمثيل عدد كسلسلة (انظر الشكل التوضيحي). غير أنه، من أجل الموضوعية، نأخذ الأساسَ الثنائيَ (binary basis) تمثيلًا افتراضيًا للأعداد الطبيعية في صورة سلاسل. فمثلًا، نمثّل العدد ستة بالسلسلة 110110 لأن 1⋅22+1⋅21+0⋅20=61\cdot 2^{2} + 1 \cdot 2^1 + 0 \cdot 2^0 = 6، وكذلك نمثّل العدد خمسة وثلاثين بالسلسلة y=100011y = 100011 التي تحقق ∑i=05yi⋅2∣y∣−i−1=35\sum_{i=0}^5 y_i \cdot 2^{|y|-i-1} = 35. وتُعطى أمثلة أخرى في الجدول أدناه.

تمثيل كلّ رقمٍ من الأرقام  بصورةٍ نقطية (bitmap) بقياس ، ويمكن اعتبارها نصًّا في . وبهذه المخطّطة نمثّل عددًا طبيعيًّا  من  خانةٍ عشرية كنصٍّ في . الصورة مأخوذة من تدوينة A. C. Andersen.

العدد (التمثيل العشري) العدد (التمثيل الثنائي)
0 0
1 1
2 10
5 101
16 10000
40 101000
53 110101
389 110000101
3750 111010100110

جدول: تمثيل الأعداد في الأساس الثنائي. يحتوي العمود الأيسر على تمثيلات للأعداد الطبيعية في الأساس العشري، بينما يحتوي العمود الأيمن على تمثيلات الأعداد نفسها في الأساس الثنائي.

إذا كان nn زوجيًا، فإن الرقم الأقل أهمية في تمثيل nn الثنائي هو 00، أما إذا كان nn فرديًا فإن هذا الرقم يساوي 11. ومثلما يقابل العدد ⌊n/10⌋\floor{n/10} «قطع» الرقم العشري الأقل أهمية (مثلًا، ⌊457/10⌋=⌊45.7⌋=45\floor{457/10}=\floor{45.7}=45)، يقابل العدد ⌊n/2⌋\floor{n/2} «قطع» الرقم الثنائي الأقل أهمية. وعليه يمكن تعريف التمثيل الثنائي رسميًا بواسطة الدالة التالية NtS:N→{0,1}∗NtS:\N \rightarrow \{0,1\}^* (NtSNtS اختصار لعبارة «الأعداد الطبيعية في سلاسل»):

NtS(n)={0n=01n=1NtS(⌊n/2⌋)parity(n)n>1NtS(n) = \begin{cases} 0 & n=0 \\ 1 & n=1 \\ NtS(\floor{n/2}) parity(n) & n>1 \end{cases}

حيث parity:N→{0,1}parity:\N \rightarrow \{0,1\} هي الدالة التي تُعرَّف بـparity(n)=0parity(n)=0 إذا كان nn زوجيًا وبـparity(n)=1parity(n)=1 إذا كان nn فرديًا، وكما جرت العادة فإنّ xyxy لمُجموعتَي السلاسل x,y∈{0,1}∗x,y \in \{0,1\}^* تعني ضمَّهما معًا. والدالة NtSNtS معرَّفة تعاُديًّا (recursively): فلكلّ n>1n>1 نعرّف rep(n)rep(n) بدلالة تمثيل العدد الأصغر ⌊n/2⌋\floor{n/2}. ومن الممكن أيضًا تعريف NtSNtS بطريقة غير تعاودية، انظر تمرين — التمثيل الثنائي.

طوال معظم هذا الكتاب، لا يُهمّنا الخياراتُ الخاصة في تمثيل الأعداد كسلاسل ثنائية إلا قليلًا: فكل ما نحتاج إلى معرفته هو أنّ مثل هذا التمثيل موجود. بل إنّنا، في كثيرٍ من أغراضنا، يمكننا استعمال تمثيلٍ أبسط من ذلك، وهو تمثيل العدد الطبيعي nn بالسلسلة 0n0^n المكوّنة من nn صفرًا.

ملاحظة — التمثيل الثنائي في بايثون (اختياري)

ويمكن تنفيذ التمثيل الثنائي في بايثون (Python) على النحو التالي:

def NtS(n):# natural numbers to strings
    if n > 1:
        return NtS(n // 2) + str(n % 2)
    else:
        return str(n % 2)

print(NtS(236))
# 11101100

print(NtS(19))
# 10011

ويمكن أيضًا استعمال بايثون لتنفيذ التحويل العكسي، أي إعادة تحويل السلسلة إلى العدد الطبيعي الذي تمثّله.

def StN(x):# String to number
    k = len(x)-1
    return sum(int(x[i])*(2**(k-i)) for i in range(k+1))

print(StN(NtS(236)))
# 236


ملاحظة — أمثلة برمجية

في هذا الكتاب، نستعمل أحيانًا أمثلةَ شيفرة (code examples) كما في ملاحظة — التمثيل الثنائي في بايثون (اختياري). والغرض منها دائمًا إبرازُ أنّ حساباتٍ بعينها يمكن إنجازها بشكل ملموس، لا توضيح سمات بايثون أو أيّ لغة برمجة أخرى. وبالفعل، فإنّ إحدى رسائل هذا الكتاب هي أنّ جميع لغات البرمجة، بمعنىٍّ دقيق ما، متكافئة (equivalent) فيما بينها، ومن ثمّ كان يمكن تمامًا أن نستعمل JavaScript أو C أو COBOL أو Visual Basic أو حتى BrainF*ck. وهذا الكتاب ليس عن البرمجة، ولا بأس البتّة إن لم تكن ملمًّا ببايثون أو لم تتابع أمثلةَ الشيفرة مثل تلك في ملاحظة — التمثيل الثنائي في بايثون (اختياري).

معنى التمثيلات (نقاش)

من الطبيعيّ لنا أن نفكّر في 236236 على أنه العدد «الفعليّ»، وفي 1110110011101100 على أنه «مجرد» تمثيلٍ له. غير أنّ معظم الأوروبيين في القرون الوسطى كانوا يرون في CCXXXVI العدد «الفعليّ»، ويرون في 236236 (لو سمعوا به أصلًا) تمثيلًا موضعيًّا هنديًّا-عربيًّا غريبًا.[1] وحين تتحقّق «سيادتُنا» الروبوتات من الذكاء الاصطناعي، فستظنّ على الأرجح أنّ 1110110011101100 هو العدد «الفعليّ»، وأنّ 236236 ليس سوى تمثيلٍ تحتاج إليه عند إصدار أوامرها للبشر.

فما هو العدد «الفعليّ»؟ لقد أتار هذا السؤالَ فلاسفةُ الرياضيات على مدى التاريخ. فقد ذهب أفلاط إلى أنّ الكائنات الرياضية توجد في نوعٍ ما من «المجال المثاليّ» للوجود (وهو مجالٌ «أكثر واقعيةً» إلى حدٍّ ما من العالم الذي ندركه بحواسّنا، إذ ذلك العالم الأخير ليس سوى ظلّ لذلك المجال المثاليّ). وفي رؤية أفلاط، فإنّ الرموز 236236 ليست سوى تدوينٍ لكائنٍ مثاليّ، يمكننا — إجلالًا للموسيقيّ المتوفّى (late musician) — أن نسمّيه «العدد الذي يُمثَّل عادةً بـ236236».

أما الفيلسوف النمساوي لودفيغ فيتغنشتاين، فقد ذهب من جهةٍ أخرى إلى أنّ الكائنات الرياضية غير موجودة البتّة، وأنّ الموجود حقًا ليس سوى العلامات الفعلية على الورق التي تشكّل 236236 أو 1110110011101100 أو CCXXXVI. وفي رأي فيتغنشتاين، فإنّ الرياضيات لا تتعلّق إلا بالتعامل الصوريّ مع رموزٍ لا تحمل أيّ معنى بطبيعتها. ويمكنك أن تفكّر في العدد «الفعليّ» على نحوٍ (نوعٍ من التهافت) «ذلك الشيء المشترك بين 236236 و1110110011101100 وCCXXXVI وكلّ التمثيلات السابقة واللاحقة التي يقصد بها التقاط الكائن نفسه».

وأنت أثناء قراءتك لهذا الكتاب حرٌّ في أن تختار فلسفتَك في الرياضيات، ما دمت تُبقي التمييز قائمًا بين الكائنات الرياضية ذاتها وبين الخيارات المختلفة لتمثيلها، سواء كانت بقعًا من الحبر أو بكسلات على شاشة أو أصفارًا وأحاد، أو أيّ صورةٍ أخرى.

التمثيلات خارج الأعداد الطبيعية

لقد رأينا أنّ الأعداد الطبيعية يمكن تمثيلها سلاسلَ ثنائية. والآن نبيّن أنّ الأمر نفسه صحيحٌ للأنواع الأخرى من الكائنات، بما في ذلك الأعداد الصحيحة (المحتملة السالب) والأعداد النسبية والمتجهات والقوائم والرسوم البيانية وكثيرٌ غيرها. وفي حالاتٍ كثيرة، فإنّ اختيار تمثيل السلسلة «المناسب» لقطعة بياناتٍ ما يتطلّب قدرًا كبيرًا من العناية، والعثور على التمثيل «الأفضل» (مثلًا: الأكثر إحكامًا، أو الأدقّ، أو الأسهل في المعالجة، أو الأكثر متانةً أمام الأخطاء، أو الأكثر غنىً بالسمات المفيدة، إلخ) هو موضوعُ بحثٍ شديد الكثافة. لكنّنا سنركّز الآن على عرض بعض التمثيلات البسيطة لمختلف الكائنات التي نودّ استعمالها مُدخَلات ومُخرَجات للحوسبة.

تمثيل الأعداد الصحيحة (المحتملة السالب)

بما أنّنا نستطيع تمثيل الأعداد الطبيعية سلاسل، فبإمكاننا تمثيل المجموعة الكاملة من الأعداد الصحيحة (أي عناصر المجموعة Z={…,−3,−2,−1,0,+1,+2,+3,…}\Z=\{ \ldots, -3 , -2 , -1 , 0 , +1, +2, +3,\ldots \} ) بإضافة بتٍّ واحد يمثّل الإشارة. ولتمثيل عددٍ mm (محتمل السالب)، نُضيف إلى مقدّمة تمثيل العدد الطبيعي ∣m∣|m| بتًّا σ\sigma يساوي 00 إذا كان m≥0m \geq 0 ويساوي 11 إذا كان m<0m<0. رسميًّا، نعرّف الدالة ZtS:Z→{0,1}∗ZtS:\Z \rightarrow \{0,1\}^* على النحو التالي ZtS(m)={0  NtS(m)m≥01  NtS(−m)m<0ZtS(m) = \begin{cases} 0\;NtS(m) & m \geq 0 \\ 1\;NtS(-m) & m < 0 \end{cases} حيث NtSNtS معرَّفة كما في المعادلة.

في حين يجب أن تكون دالةُ الترميز في التمثيل واحد-لواحد (one to one)، فإنّها لا يلزم أن تكون شاملةً (onto). فمثلًا، لا يوجد في التمثيل أعلاه عددٌ يمثَّل بالسلسلة الفارغة، ومع ذلك فهو تمثيلٌ جيّد، لأنّ كلّ عدد صحيح يمثَّل بشكلٍ فريد بواسطة سلسلةٍ ما.

ملاحظة — التفسير والسياق

بالسلسلة y∈{0,1}∗y\in \{0,1\}^*، كيف نعرف ما إذا كانت «مفترضًا» أن تمثّل عددًا طبيعيًّا (غير سالب) أم عددًا صحيحًا (محتمل السالب)؟ وكذلك، حتى إن كنّا نعلم أنّ yy «مفترض» أن يكون عددًا صحيحًا، فكيف نعلم ما مخطّطُ التمثيل الذي يستعمله؟ الجواب القصير هو أننا لا نعلم هذه المعلومة بالضرورة، ما لم تأتِنا من السياق. (وفي لغات البرمجة، يقرّر المُصرِّف أو المفسّر تمثيل تسلسل البتات المقابلَ لمتغيّرٍ ما بحسب نوعه (type) من المتغيّرات.) ويمكننا أن نتعامل مع السلسلة نفسها yy بوصفها تمثّل عددًا طبيعيًّا أو عددًا صحيحًا أو قطعةَ نصّ أو صورةً أو كائنًا شبحيًّا أخضر (green gremlin). ولكلّما قلنا جملةً مثل «ليكن nn هو العدد الذي تمثّله السلسلة yy»، فسنفترض أننا نثبّت مخطّطَ تمثيلٍ قانونيًّا (canonical) من نوع التي سبق ذكرها. نادرًا ما يكون اختيار مخطّط التمثيل بعينه مهمًّا، إلا فيجب ألّا نغيّره حفاظًا على الاتّساق.

تمثيل المكمّل الثاني (اختياري)

الطريقة التي يتّبعها تمثيل الأعداد الصحيحة (المحتملة السالب) في تمثيل عددٍ صحيح ببتّ إشارةٍ محدَّد تُعرف باسم تمثيل القيمة ذات الإشارة (Signed Magnitude Representation)، وقد استُعمِلت في بعض الحواسيب المبكّرة. غير أنّ تمثيل المكمّل الثاني هو أكثر شيوعًا بكثيرٍ في الممارسة العملية. ويُعرَّف تمثيل المكمّل الثاني (two's complement) للعدد الصحيح kk في المجموعة {−2n,−2n+1,…,2n−1}\{ -2^n , -2^n+1, \ldots, 2^n-1 \} بأنه السلسلة ZtSn(k)ZtS_n(k) ذات الطول n+1n+1 المعرَّفة على النحو التالي: ZtSn(k)={NtSn+1(k)0≤k≤2n−1NtSn+1(2n+1+k)−2n≤k≤−1  , ZtS_n(k) = \begin{cases} NtS_{n+1}(k) & 0 \leq k \leq 2^n-1 \\ NtS_{n+1}(2^{n+1}+k) & -2^n \leq k \leq -1 \end{cases} \;, حيث NtSℓ(m)NtS_\ell(m) تدلّ على التمثيل الثنائي القياسيّ للعدد m∈{0,…,2ℓ}m \in \{0,\ldots, 2^{\ell}\} كسلسلةٍ طولها ℓ\ell، متبوعةً بأصفارٍ بادئة بقدر الحاجة. فمثلًا، إذا كان n=3n=3 فإن ZtS3(1)=NtS4(1)=0001ZtS_3(1)=NtS_4(1)=0001، وZtS3(2)=NtS4(2)=0010ZtS_3(2)=NtS_4(2)=0010، وZtS3(−1)=NtS4(16−1)=1111ZtS_3(-1)=NtS_4(16-1)=1111، وZtS3(−8)=NtS4(16−8)=1000ZtS_3(-8)=NtS_4(16-8)=1000. وإذا كان kk عددًا سالبًا أكبرَ من −2n-2^n أو مساويًا له، فإنّ 2n+1+k2^{n+1}+k يكون عددًا بين 2n2^n و2n+1−12^{n+1}-1. وعليه، فإنّ تمثيل المكمّل الثاني لمثل هذا العدد kk هو سلسلةٌ طولها n+1n+1 يكون الرقم فيها الأول مساويًا لـ11.

وهذه الخلاصة يمكن صياغتها على نحوٍ آخر، وهو أنّنا نمثّل العدد k∈{−2n,…,2n−1}k \in \{ -2^n,\ldots, 2^n-1 \} المحتمل السالب بوصفه العدد غير السالب kmod  2n+1k \mod 2^{n+1} (انظر أيضًا الشكل التوضيحي). وهذا يعني أنّه إذا كان العددان kk وk′k' (المحتملان السالب) ليسا كبيرين إلى هذا الحدّ (أي إنّ k+k′∈{−2n,…,2n−1} k + k' \in \{ -2^n,\ldots, 2^n-1 \})، فبإمكاننا حساب تمثيل k+k′k+k' بجمع تمثيلي kk وk′k' بمقياس 2n+12^{n+1} كما لو كانا عددين صحيحين غير سالبين. وخاصيةُ تمثيل المكمّل الثاني هذه هي جاذبيّتُه الرئيسة، إذ إنّ المعالجات الدقيقة (microprocessors) تستطيع، تبعًا لبنيتها، تنفيذ العمليات الحسابية بمقياس 2w2^w بكفاءةٍ عالية جدًّا (ولقيم معيّنة من ww مثل 3232 و6464). وتترك كثيرٌ من الأنظمة للمبرمج مهمّةَ التحقّق من ألّا تكون القيم كبيرةً، وتُنفّذ هذه الحسبة بمقياسها بصرف النظر عن حجم الأعداد المتدخّلة. ولهذا السبب، يمكن في بعض الأنظمة أن ينتج جمعُ عددين موجبين كبيرين عددًا سالبًا (مثلًا، قد ينتج جمعُ 2n−1002^n-100 مع 2n−2002^n-200 عددًا −300-300 لأنّ (2n+1−300)mod  2n+1=−300(2^{n+1}-300) \mod 2^{n+1} = -300، انظر أيضًا الشكل التوضيحي).

/images/introtcs/lec_02_representation-4.webp

الأعداد النسبية وتمثيل أزواج السلاسل

يمكننا تمثيل عددٍ نسبي من الشكل a/ba/b بأن نمثّل العددين aa وbb. غير أنّ مجردَ ضمّ تمثيلي aa وbb لن يجدي نفعًا. فمثلًا، التمثيل الثنائي للعدد 44 هو 100100، والتمثيل الثنائي للعدد 4343 هو 101011101011، لكنّ ضمَّ هاتين السلسلتين 100101011100101011 هو أيضًا ضمٌّ للتمثيل 1001010010 للعدد 1818 وللتمثيل 10111011 للعدد 1111. وعليه، إن استعملنا هذا الضمّ البسيط لما استطعنا أن نُميّز ما إذا كانت السلسلة 100101011100101011 تمثّل 4/434/43 أم 18/1118/11.

نعالج هذه المشكلة بإعطاء تمثيلٍ عامٍّ لـ_أزواج السلاسل_ (pairs of strings). ولو كنّا نستعمل قلمًا وورقًا لاكتفينا برمزٍ فاصلٍ مثل ∥\| لتمثيل، مثلًا، الزوج المكوّن من العددين اللذين تمثّلهما 1010 و110001110001 بوصفه السلسلةَ ذات الطول 99 «10∥11000110\|110001». بعبارةٍ أخرى، هناك خريطةٌ واحد-لواحد FF من أزواج السلاسل x,y∈{0,1}∗x,y \in \{0,1\}^* إلى سلسلةٍ واحدة zz فوق الأبجدية Σ={0,1,∥}\Sigma = \{0,1,\| \} (بعبارةٍ أخرى، z∈Σ∗z\in \Sigma^*). استعمالُ هذه الفواصل يشبه الطريقة التي نفصل بها الكلمات في الإنجليزية بالمسافات وعلامات الترقيم. وبإضافة قدرٍ يسيرٍ من التكرار، نحصل على الأثر نفسه في المجال الرقميّ. ويمكننا ربط المجموعة الثلاثية Σ\Sigma بالمجموعة الثلاثية {00,11,01}⊂{0,1}2\{00,11,01 \} \subset \{0,1\}^2 ربطًا واحد-لواحد، ومن ثمّ ترميز سلسلة z∈Σ∗z\in \Sigma^* ذات الطول nn بوصفها سلسلة w∈{0,1}∗w\in \{0,1\}^* ذات الطول 2n2n.

ويُستمدّ تمثيلنا النهائيّ للأعداد النسبية بدمج الخطوات التالية:

  1. تمثيل عددٍ نسبي (محتمل السالب) كزوجٍ من الأعداد الصحيحة a,ba,b بحيث r=a/br=a/b.

  2. تمثيل العدد الصحيح بسلسلةٍ عبر التمثيل الثنائي.

  3. دمج 1 و2 للحصول على تمثيلٍ للعدد النسبي كزوجٍ من السلاسل.

  4. تمثيل زوجِ سلاسلٍ فوق {0,1}\{0,1\} بسلسلةٍ واحدة فوق Σ={0,1,∥}\Sigma = \{0,1,\|\}.

  5. تمثيل سلسلةٍ فوق Σ\Sigma بسلسلةٍ أطول فوق {0,1}\{0,1\}.

مثال — تمثيل عددٍ نسبي في سلسلة

لنفكّر في العدد النسبي r=−5/8r=-5/8. نمثّل −5-5 بـ11011101 و+8+8 بـ0100001000، ومن ثمّ يمكننا تمثيل rr بوصفه زوجًا من السلاسل (1101,01000)(1101,01000)، وتمثيل هذا الزوج بوصفه السلسلةَ 1101∥010001101\|01000 ذات الطول 1010 فوق الأبجدية {0,1,∥}\{0,1,\|\}. والآن، بتطبيق الخريطة 0↦000 \mapsto 00، 1↦111\mapsto 11، ∥↦01\| \mapsto 01، يمكننا تمثيل السلسلة السابقة بوصفها السلسلةَ s=11110011010011000000s=11110011010011000000 ذات الطول 2020 فوق الأبجدية {0,1}\{0,1\}.

ويمكن استعمال الفكرة نفسها لتمثيل ثلاثياتِ السلاسل، والأربعات، وهكذا، في سلسلةٍ واحدة. وبالفعل، إنّ هذه إحدى حالات مبدأٍ عامٍّ جدًّا نطبّقه مرارًا وتكرارًا في نظريّة علوم الحاسوب وممارسته معًا (مثلًا، في البرمجة الكائنية (Object Oriented)):

إن استطعنا تمثيل كائناتٍ من نوع $T) في سلاسل، فبإمكاننا تمثيل الصفوف المرتّبة (tuples) من كائناتٍ من هذا النوع في سلاسل أيضًا.

وتكرارُ الفكرة نفسها يعني أنّنا، متى استطعنا تمثيل كائناتٍ من نوع TT، استطعنا أيضًا تمثيل قوائمَ قوائمَ (lists of lists) من هذه الكائنات، بل وقوائمَ قوائمَ قوائمَ وهكذا وهكذا. وسنعود إلى هذه النقطة حين نناقش الترميزَ الخاليَ من البادئات (prefix free encoding) في الترميزُ الخالي من البادئات.

تمثيل الأعداد الحقيقية

مجموعةُ الأعداد الحقيقية R\R تضمّ جميع الأعداد بما فيها الموجبة والسالبة والكسرية، وكذلك الأعداد غير النسبية (irrational) مثل π\pi أو ee. وكلّ عددٍ حقيقي يمكن تقريبه بعددٍ نسبي، ومن ثمّ يمكننا تمثيل كلّ عددٍ حقيقي xx بعددٍ نسبي a/ba/b قريبٍ جدًّا من xx. فمثلًا، يمكننا تمثيل π\pi بالعدد 22/722/7 ضمن خطأٍ قدره نحو 10−310^{-3}، وإن أردنا خطأً أصغر (مثلًا نحو 10−410^{-4}) فاستخدم 311/99311/99، وهكذا وهكذا.

/images/introtcs/lec_02_representation-5.webp

إنّ التمثيل أعلاه للأعداد الحقيقية عبر أعداد نسبية تقاربها اختيارٌ جيّد لمخطّط تمثيل. غير أنّه، في تطبيقات الحوسبة المعتادة، يكون استعمال مخطّط تمثيل الفاصلة العائمة (floating-point representation scheme) لتمثيل الأعداد الحقيقية أكثر شيوعًا (انظر الشكل التوضيحي). في مخطّط تمثيل الفاصلة العائمة، نمثّل x∈Rx\in \R بالزوج (b,e)(b,e) من عددين صحيحين (موجب أو سالب) ذات أحجامٍ محدَّدة مسبقًا (يحدّدها مستوى الدقّة المرغوب) بحيث يكون b×2eb \times 2^{e} أقرب ما يكون إلى xx. وتمثيلُ الفاصلة العائمة هو النسخةُ بالأساس الثاني من الترميز العلمي (scientific notation)، إذ نمثّل عددًا y∈Ry\in R بتقريبه من الشكل b×10eb \times 10^e لكلٍّ من bb وee. ويسمّى «الفاصلة العائمة» لأنّنا يمكننا التفكير في العدد bb على أنّه يحدّد متتابعةَ من الأرقام الثنائية، وفي ee على أنّه يصف موضعَ «الفاصلة الثنائية» داخل هذه المتتابعة. وإنّ استعمال تمثيل الفاصلة العائمة هو سببُ ظهور النتيجة 0.30000000000000004 بدلًا من 0.3 عند طباعة التعبير 0.1+0.2 في كثيرٍ من أنظمة البرمجة، للمزيد انظر هنا، وهنا، وهنا.

/images/introtcs/lec_02_representation-6.webp

ولعلّ القارئ أن يكون (محقًّا في) قلقه من أنّ تمثيل الفاصلة العائمة (أو تمثيل الأعداد النسبية) لا يستطيع تمثيلَ الأعداد الحقيقية إلا تقريبًا. في كثيرٍ من تطبيقات الحوسبة (لا في جميعها)، يمكن جعلُ الدقّة محكمةً بالقدر الكافي بحيث لا يؤثّر ذلك في النتيجة النهائية، وإن كان علينا أحيانًا أن نكون حذرين. وبالفعل، فإنّ أعطالَ الفاصلة العائمة لا تكون أحيانًا أمرًا يُضحَك عليه. فمثلًا، تتّهم أخطاءُ التقريب في الفاصلة العائمة بحدوث العطل الذي مني به صاروخُ باتريوت الأمريكي وهو يعترض صاروخَ سكود عراقيًّا، وقد كلّف ذلك 28 حياة، فضلًا عن خطأٍ في مقداره مئة مليون جنيه في حساب المدفوعات لمتقاعدين بريطانيين.

مبرهنة كانتور، والمجموعات القابلة للعدّ، وتمثيلاتُ الأعداد الحقيقية في سلاسل

اقتباس

"لأيّ مجموعة من الثمرات، يمكننا تحضير سلطات فواكه أكثر من عدد الثمرات. وإن لم يكن كذلك، لأمكننا أن نُسمّي كلّ سلطةٍ بثمرٍ مختلف، وأن نأخذ بعين الاعتبار سلطةَ كلّ الثمرات التي ليست في سلطتها. واسمُ هذه السلطة داخلها إن وُجد وإلّا لم يوجد."، مارتا ستوري.

بالنظر إلى المشكلات في تقريب الأعداد الحقيقية بالفاصلة العائمة، فإنّ سؤالًا طبيعيًّا هو: هل يمكن تمثيلُ الأعداد الحقيقية بدقّة في سلاسل؟ ولأسف، فإنّ المبرهنة التالية تُبيّن أنّ ذلك غير ممكن:

مبرهنة — مبرهنة كانتور

لا توجد دالةٌ واحد-لواحد RtS:R→{0,1}∗RtS:\R \rightarrow \{0,1\}^*.[2]

المجموعات القابلة للعدّ. نقول إنّ مجموعةً SS قابلة للعدّ (countable) إذا وُجدت خريطةٌ شاملة C:N→SC:\N \rightarrow S، أو بعبارةٍ أخرى، إذا استطعنا كتابة SS على هيئة المتتابعة C(0),C(1),C(2),…C(0),C(1),C(2),\ldots. ولأنّ التمثيل الثنائي يعطي خريطةً شاملة من {0,1}∗\{0,1\}^* إلى N\N، ولأنّ تركيب خريطتين شاملتين يعطي خريطةً شاملة، فإنّ المجموعة SS تكون قابلةً للعدّ إذا وإذا فقط وُجدت خريطةٌ شاملة من {0,1}∗\{0,1\}^* إلى SS. وباستخدام الخصائص الأساسية للدوال (انظر الدوال)، تكون المجموعة قابلةً للعدّ إذا وإذا فقط وُجدت دالةٌ واحد-لواحد من SS إلى {0,1}∗\{0,1\}^*. وعليه، يمكننا إعادة صياغة مبرهنة — مبرهنة كانتور على النحو التالي:

مبرهنة — مبرهنة كانتور (صياغة مكافئة)

الأعداد الحقيقية غير قابلة للعدّ. أي إنّ دالةً شاملة NtR:N→RNtR:\N \rightarrow \R غير موجودة.

أثبت مبرهنة — مبرهنة كانتور (صياغة مكافئة) عالمُ الرياضيات جورج كانتور عام 1874. وكان هذه النتيجة (والنظرية التي تحيط بها) صادمةً للرياضيين إلى حدٍّ كبير في ذلك الوقت. فبإبرازه أنّ لا وجود لخريطة واحد-لواحد من R\R إلى {0,1}∗\{0,1\}^* (أو إلى N\N)، برهن كانتور على أنّ هاتين المجموعتين اللانهائيتين لهما «أشكالٌ مختلفة من اللانهائية»، وأنّ مجموعة الأعداد الحقيقية R\R «أكبر» في معنى ما من المجموعة اللانهائية {0,1}∗\{0,1\}^*. وكان فكرةُ وجود «درجاتٍ من اللانهائية» مربكةً إلى حدٍّ عميقٍ للرياضيين والفلاسفة في ذلك الوقت. فقد سمّى الفيلسوف لودفيغ فيتغنشتاين (الذي ذكرناه من قبل) نتائجِ كانتور «عبثًا محضًا» و«أمرًا مضحكًا». ورأى آخرون أنّها أسوأ من ذلك. فقد سمّى ليبولد كرونكر كانتور «مُفسدَ الشباب»، بينما قال هنري بوانكاري إنّ أفكارَ كانتور «يجب التخلّص منها في الرياضيات إلى الأبد». ثمّ إنّ التيار انقلب في النهاية، وأصبح عملُ كانتور مقبولًا في العالم أجمع بوصفه حجرَ الأساس في نظرية المجموعات وفي أسس الرياضيات. وكما قال ديفيد هيلبرت عام 1925: «لن يطردنا أحدٌ من الفردوس الذي خلقه كانْتور لنا.» وكما سنرى لاحقًا في هذا الكتاب، تلعب أفكارُ كانتور دورًا كبيرًا جدًّا أيضًا في نظرية الحوسبة.

والآن، بعد أن ناقشنا أهميةَ مبرهنة — مبرهنة كانتور، فلنرَ بُرهانَه. ينجَز ذلك على خطوتين:

  1. نُعرّف مجموعةً لامتناهيية X\mathcal{X} يكون إثباتُ أنّ X\mathcal{X} غير قابلة للعدّ أسهلَ لنا (أي إنّ الأسهل لنا هو إثباتُ عدم وجود دالة واحد-لواحد من X\mathcal{X} إلى {0,1}∗\{0,1\}^*).

  2. نُثبت أنّ هناك دالةً واحد-لواحد GG تُسقط X\mathcal{X} على R\mathbb{R}.

ويمكننا استعمالُ البرهان بالتناقض لإظهار أنّ هاتين الحقيقتين معًا تستلزمان مبرهنة — مبرهنة كانتور. وتحديدًا، إن افترضنا (قصدًا للتناقض) وجودَ خريطةٍ واحد-لواحد FF تُسقط R\mathbb{R} على {0,1}∗\{0,1\}^*، فإنّ الدالة x↦F(G(x))x \mapsto F(G(x)) الناتجة عن تركيب FF مع الدالة GG من الخطوة 2 أعلاه ستكون دالةً واحد-لواحد من X\mathcal{X} إلى {0,1}∗\{0,1\}^*، وهذا يناقض ما أثبتناه في الخطوة 1!

ولتحويل هذه الفكرة إلى برهانٍ كامل لـمبرهنة — مبرهنة كانتور، نحتاج إلى:

  • نُعرّف المجموعة X\mathcal{X}.

  • نُثبت أنّه لا توجد دالة واحد-لواحد من X\mathcal{X} إلى {0,1}∗\{0,1\}^*

  • نُثبت أنّ هناك دالةً واحد-لواحد من X\mathcal{X} إلى R\R.

والآن ننتقل إلى فعل ذلك بالضبط. أي إنّنا سنُعرّف المجموعة {0,1}∞\{0,1\}^\infty التي ستلعب دور X\mathcal{X}، ثمّ نصوغ ونُثبت مُبرهَنين يُظهران أنّ هذه المجموعة تفي بالخاصيتين اللتين نريدهما.

تعريف

نرمز بـ{0,1}∞\{0,1\}^\infty إلى المجموعة {f  ∣  f:N→{0,1}}\{ f \;|\; f:\N \rightarrow \{0,1\} \}.

أي إنّ {0,1}∞\{0,1\}^\infty مجموعةُ دوال، والدالة ff تنتمي إلى {0,1}∞\{0,1\}^\infty إذا وإذا فقط كان مجالُها N\N ومجالُها المشترك  {0,1}\ \{0,1\}. ويمكننا أيضًا التفكير في {0,1}∞\{0,1\}^\infty بوصفها مجموعةَ كلّ المتتابعات (sequences) اللانهائية من البتات، إذ يمكن التمييز بين الدالة f:N→{0,1}f:\N \rightarrow \{0,1\} وبين المتتابعة (f(0),f(1),f(2),…)(f(0),f(1),f(2),\ldots ). ويُظهر المُبرهانان التاليان أنّ {0,1}∞\{0,1\}^\infty تستطيع أن تلعب دور X\mathcal{X} لإثبات مبرهنة — مبرهنة كانتور.

لمّة

لا توجد خريطةٌ واحد-لواحد FtS:{0,1}∞→{0,1}∗FtS:\{0,1\}^\infty \rightarrow \{0,1\}^*.[3]

لمّة

هناك خريطةٌ واحد-لواحد FtR:{0,1}∞→RFtR:\{0,1\}^\infty \rightarrow \R.[4]

وكما رأينا أعلاه، فإنّ لمّة ولمّة معًا تستلزمان مبرهنة — مبرهنة كانتور. ولنُكرّر الحجة بصورةٍ أكثر رسمية، فلنفترض، قصدًا للتناقض، أنّه كانت هناك فعلًا دالةٌ واحد-لواحد RtS:R→{0,1}∗RtS:\R \rightarrow \{0,1\}^*. وبموجب لمّة، هناك دالةٌ واحد-لواحد FtR:{0,1}∞→RFtR:\{0,1\}^\infty \rightarrow \R. وعليه، في ظلّ هذا الافتراض، ولأنّ تركيب دالتين واحد-لواحد يعطي دالةً واحد-لواحد (انظر تمرين — تركيب الدوال الواحد-لواحد)، فإنّ الدالة FtS:{0,1}∞→{0,1}∗FtS:\{0,1\}^\infty \rightarrow \{0,1\}^* المعرَّفة بـFtS(f)=RtS(FtR(f))FtS(f)=RtS(FtR(f)) ستكون واحد-لواحدة، وهذا يناقض لمّة. انظر الشكل التوضيحي لرسمٍ توضيحيٍّ لهذه الحجة.

نُثبت cantorthm بجمع sequencestostrings وsequencestoreals. وتُظهر sequencestoreals، التي تستعمل أدوات التفاضل والتكامل القياسية، وجود تطبيقٍ واحد-لواحد  من المجموعة  إلى الأعداد الحقيقية. ولو وُجد تطبيقٌ واحد-لواحد افتراضي ، لجمَعنا بينهما للحصول على تطبيقٍ واحد-لواحد . لكنّ هذا يتعارض مع sequencestostrings — أيّ قلب البرهان — التي تُستبعد وجود مثل هذا التطبيق.

ولا يبقى سوى إثبات المُبرهَنين. ونبدأ بإثبات لمّة، وهو في الحقيقة جوهرُ مبرهنة — مبرهنة كانتور.

/images/introtcs/lec_02_representation-7.webp

تمهيد: «كانْتور الصغير». برهانُ لمّة دقيقٌّ إلى حدٍّ ما. ومن طرائق اكتساب الحدس له أن ندرِس العبارةَ المنتهية التالية: «لا توجد دالة شاملة f:{0,…,99}→{0,1}100f:\{0,\ldots,99\} \rightarrow \{0,1\}^{100}». ولنا نعلم بالطبع أنّها صادقة، إذ إنّ المجموعة {0,1}100\{0,1\}^{100} أكبر من المجموعة [100][100]، لكنّنا لنرَ برهانًا مباشرًا. فلكلّ f:{0,…,99}→{0,1}100f:\{0,\ldots,99\} \rightarrow \{0,1\}^{100} يمكننا تعريفُ السلسلة d‾∈{0,1}100\overline{d} \in \{0,1\}^{100} على النحو التالي: d‾=(1−f(0)0,1−f(1)1,…,1−f(99)99)\overline{d} = (1-f(0)_0, 1-f(1)_1 , \ldots, 1-f(99)_{99}). وإن كانت ff شاملة، لَوُجدَ عددٌ n∈[100]n\in [100] بحيث f(n)=d‾f(n) =\overline{d}، لكنّنا ندّعي أنّ مثل هذا nn غير موجود. بالفعل، إن وُجد مثل هذا nn لاتّسقَ الإحداثي رقم nn في d‾\overline{d} مع f(n)nf(n)_n، بينما هو بالتعريف يساوي 1−f(n)n1-f(n)_n. وانظر أيضًا «برهانًا بالشيفرة» لهذه العبارة.

برهان

سنُثبت أنّه لا توجد دالة شاملة (onto) StF:{0,1}∗→{0,1}∞StF:\{0,1\}^* \rightarrow \{0,1\}^\infty. وهذا يثبت المُبرهَن، لأنّه لكلّ مجموعتين AA وBB توجد دالة شاملة من AA إلى BB إذا وإذا فقط وُجدت دالة واحد-لواحد من BB إلى AA (انظر لمّة).

وتُعرف تقنيةُ هذا البرهان باسم «حجّة القُطر» (diagonal argument)، وهي مصوَّرة في الشكل التوضيحي. ولنفترض، قصدًا للتناقض، وجودَ دالةٍ من هذا النوع StF:{0,1}∗→{0,1}∞StF:\{0,1\}^* \rightarrow \{0,1\}^\infty. وسنُبيّن أنّ StFStF ليست شاملةً بإظهار دالةٍ d‾∈{0,1}∞\overline{d}\in \{0,1\}^\infty بحيث d‾≠StF(x)\overline{d} \neq StF(x) لكلّ x∈{0,1}∗x\in \{0,1\}^*. لنأخذ بعين الاعتبار الترتيبَ المعجميّ (lexicographic) للسلاسل الثنائية (أي """",00,11,0000,0101,…\ldots). فلكلّ n∈Nn\in \N، لنُسمِّ xnx_n السلسلةَ رقم nn في هذا الترتيب. أي إنّ x0=""x_0 =""، وx1=0x_1 = 0، وx2=1x_2= 1 وهكذا وهكذا. نعرّف الدالة d‾∈{0,1}∞\overline{d} \in \{0,1\}^\infty على النحو التالي: d‾(n)=1−StF(xn)(n)\overline{d}(n) = 1 - StF(x_n)(n) لكلّ n∈Nn\in \N. أي إنّنا، لحساب d‾\overline{d} على مُدخَل n∈Nn\in\N، نحسب أوّلًا g=StF(xn)g= StF(x_n)، حيث xn∈{0,1}∗x_n \in \{0,1\}^* هي السلسلة رقم nn في الترتيب المعجميّ. ولأنّ g∈{0,1}∞g \in \{0,1\}^\infty، فإنّها دالةٌ تُسقط N\N على {0,1}\{0,1\}. وتُعرَّف القيمة d‾(n)\overline{d}(n) على أنّها نفيُ g(n)g(n).

تعريفُ الدالة d‾\overline{d} دقيقٌّ بعض الشيء. ومن طرائق التفكير في ذلك أن تتخيّل الدالة StFStF محدَّدةً بجدولٍ لا نهائيّ الطول، يكون فيه كلُّ سطرٍ مقابلًا لسلسلةٍ x∈{0,1}∗x\in \{0,1\}^* (مع ترتيب السلاسل ترتيبًا معجميًّا)، ويحتوي المتتابعةَ StF(x)(0),StF(x)(1),StF(x)(2),…StF(x)(0), StF(x)(1), StF(x)(2),\ldots. وعناصرُ هذا الجدول القُطرية (diagonal) هي القيم

StF("")(0),StF(0)(1),StF(1)(2),StF(00)(3),StF(01)(4),…StF("")(0),StF(0)(1),StF(1)(2),StF(00)(3), StF(01)(4),\ldots

التي تقابل العناصر StF(xn)(n)StF(x_n)(n) في السطر رقم nn والعمود رقم nn من هذا الجدول، لكلٍّ من n=0,1,2,…n=0,1,2,\ldots. والدالة d‾\overline{d} التي عرّفناها أعلاه تُسقط كلّ n∈Nn\in \N على نفي القيمة القُطرية رقم nn.

ولإتمام البرهان على أنّ StFStF ليست شاملة، نحتاج إلى إظهار أنّ d‾≠StF(x)\overline{d} \neq StF(x) لكلّ x∈{0,1}∗x\in \{0,1\}^*. بالفعل، لنأخذ سلسلةً ما x∈{0,1}∗x\in \{0,1\}^* ولنُسمِّ g=StF(x)g = StF(x). وإذا كان nn هو موضع xx في الترتيب المعجميّ، فإنّ ذلك بالبناء يعني أنّ d‾(n)=1−g(n)≠g(n)\overline{d}(n) = 1-g(n) \neq g(n)، أي إنّ g≠d‾g \neq \overline{d}، وهو ما أردنا إثباته.

ملاحظة — تعميم يتجاوز السلاسل والأعداد الحقيقية

ليس لـلمّة علاقةٌ كبيرة بالأعداد الطبيعية أو بالسلاسل. وتُظهر دراسةُ البرهان أنّه يُثبت في الحقيقة أنّه لكلّ مجموعة SS لا توجد خريطةٌ واحد-لواحد F:{0,1}S→SF:\{0,1\}^S \rightarrow S، حيث {0,1}S\{0,1\}^S تدلّ على المجموعة {f  ∣  f:S→{0,1}}\{ f \;|\; f:S \rightarrow \{0,1\} \} لكلّ الدوال المنطقية (Boolean functions) ذات المجال SS. وبما أنّنا نستطيع التمييز بين المجموعة الجزئية V⊆SV \subseteq S ودالتها المميِّزة f=1Vf=1_V (أي إنّ 1V(x)=11_V(x)=1 إذا وإذا فقط كان x∈Vx\in V)، يمكننا أن نعتبر {0,1}S\{0,1\}^S أيضًا على أنّها مجموعةُ كلّ المجموعات الجزئية لـSS. وتُسمّى هذه المجموعة أحيانًا مجموعةَ القوى (power set) لـSS، وتُرمز إليها بـP(S)\mathcal{P}(S) أو بـ2S2^S.

ويمكن تعميمُ برهان لمّة لإظهار أنّه لا توجد خريطةٌ واحد-لواحد بين مجموعةٍ ومجموعة القوى الخاصّة بها. وبشكلٍ خاص، هذا يعني أنّ المجموعة {0,1}R\{0,1\}^\R «أكبر» حتى من R\R. واستعمل كانْتور هذه الأفكار لبناء تسلسلٍ هرميٍّ لا نهائيٍّ من درجات اللانهائية. ويُتبيّن أنّ عدد هذه الدرجات أكبر بكثير من ∣N∣|\N| بل ومن ∣R∣|\R| أيضًا. وقد رمز إلى عدد عناصر N\N بـℵ0\aleph_0، ورمز إلى أصغر الأعداد اللانهائية التي تليه بـℵ1\aleph_1. (ℵ\aleph هو الحرف الأول في الأبجدية العبرية.) كما طرح كانْتور فرضيةَ المتصل التي تنصّ على ∣R∣=ℵ1|\R|=\aleph_1. وسنعود لاحقًا في هذا الكتاب إلى قصّة هذه الفرضية الشائقة. وتذكر محاضرة أرونسون هذه بعض هذه القضايا (انظر أيضًا محاضرة بيركلي CS 70 هذه).

ولإتمام برهان مبرهنة — مبرهنة كانتور، نحتاج إلى إثبات لمّة. وهذا يتطلّب بعض الخلفية في التفاضل والتكامل، وإلّا فهو مباشر. وإن لم تكن قد حصّلت على خبرةٍ كبيرة في نهايات المتسلّلات الحقيقية من قبل، فقد يكون البرهانُ الرسمي أدناه صعبًا بعض الشيء في متابعته. وهذا الجزء ليس جوهرَ حجّة كانْتور، ولا تلعب هذه النهايات دورًا مهمًّا في بقية هذا الكتاب، من حقّك إذن أن تصدّق لمّة وأن تتخطّى برهانَها.

نعرّف FtR(f)FtR(f) على أنّه العددُ الواقع بين 00 و22 الذي مفكّكُه العشري (decimal expansion) هو f(0).f(1)f(2)…f(0).f(1) f(2) \ldots، أو بعبارةٍ أخرى FtR(f)=∑i=0∞f(i)⋅10−iFtR(f) = \sum_{i=0}^{\infty} f(i) \cdot 10^{-i}. وإذا كانت ff وgg دالتين مختلفتين في {0,1}∞\{0,1\}^\infty، فلا بدّ أن تختلفا في مُدخَلٍ ما kk. وإذا أخذنا أصغرَ مثل هذا kk، فإنّ الأعداد f(0).f(1)f(2)…f(k−1)f(k)…f(0).f(1) f(2) \ldots f(k-1) f(k) \ldots وg(0).g(1)g(2)…g(k)…g(0) . g(1) g(2) \ldots g(k) \ldots تتّفقان فيما بينهما حتى الرقم k−1k-1 بعد الفاصلة العشرية، وتختلفان في الرقم kk. لكنّ هذا يعني أنّ هذين العددين يجب أن يكونا مختلفَين. وتحديدًا، إذا كان f(k)=1f(k)=1 وg(k)=0g(k)=0، فإنّ العدد الأول أكبر من الثاني، وإلّا (أي إذا كان f(k)=0f(k)=0 وg(k)=1g(k)=1) فإنّ الأول أصغر من الثاني. وفي البرهان علينا أن نكون حذرينٍ بعض الشيء، إذ إنّ هذه أعدادٌ ذات توسيعاتَ لا نهائية (infinite expansions). فمثلًا، للعدد النصف مفكّكان عشريان هما 0.50.5 و0.49999⋯0.49999\cdots. لكنّ هذه المسألة لا تظهر هنا، إذ نقيّد انتباهنا بالأعداد التي لا يتضمّن مفكّكُها العشريّ الرقمَ 99.

برهان

ولكلّ f∈{0,1}∞f \in \{0,1\}^\infty، نعرّف FtR(f)FtR(f) على أنّه العددُ الذي مفكّكُه العشري هو f(0).f(1)f(2)f(3)…f(0).f(1)f(2)f(3)\ldots. رسميًّا، FtR(f)=∑i=0∞f(i)⋅10−i FtR(f) = \sum_{i=0}^\infty f(i) \cdot 10^{-i} ومن النتائج المعروفة في التفاضل والتكامل (ولن نكرّر برهانَها هنا) أنّ المتسلّلة في الطرف الأيمن من المعادلة تتقارب إلى حدٍّ معيَّنٍ في R\mathbb{R}.

والآن نُثبت أنّ FtRFtR واحد-لواحدة. لتكن f,gf,g دالتين مختلفتين في {0,1}∞\{0,1\}^\infty. ولأنّ ff وgg مختلفتان، فلا بدّ أن تختلفا في مُدخَلٍ ما، ونعرّف kk أصغرَ مُدخَلٍ يختلفان فيه، ونفترض دون فَقد شيءٍ من العموم أنّ f(k)=0f(k)=0 وg(k)=1g(k)=1. (وإلّا، إذا كان f(k)=1f(k)=1 وg(k)=0g(k)=0، فيكفينا أن نبدّل الأدوارَ بين ff وgg.) يتّفق العددان FtR(f)FtR(f) وFtR(g)FtR(g) فيما بينهما حتى الرقم k−1k-1 بعد الفاصلة العشرية. ولأنّ هذا الرقم يساوي 00 عند FtR(f)FtR(f) ويساوي 11 عند FtR(g)FtR(g)، فإنّنا ندّعي أنّ FtR(g)FtR(g) أكبر من FtR(f)FtR(f) بمقدار 0.5⋅10−k0.5 \cdot 10^{-k} على الأقل. ولنرَ ذلك، لاحظ أنّ الفرق FtR(g)−FtR(f)FtR(g)-FtR(f) يكون أصغرَ ما يمكن إذا كان g(ℓ)=0g(\ell)=0 لكلّ ℓ>k\ell>k وكان f(ℓ)=1f(\ell)=1 لكلّ ℓ>k\ell>k، وعندئذٍ (لأنّ ff وgg يتّفقان حتى الرقم k−1k-1 بعد الفاصلة)

FtR(g)−FtR(f)=10−k−10−k−1−10−k−2−10−k−3−⋯ FtR(g)-FtR(f) = 10^{-k} - 10^{-k-1} - 10^{-k-2} - 10^{-k-3} - \cdots

ولأنّ المتسلّلة اللانهائية ∑i=0∞10−i\sum_{i=0}^{\infty} 10^{-i} تتقارب إلى 10/910/9، يتبع أنّه لكلّ ff وgg من هذا النوع، FtR(g)−FtR(f)≥10−k−10−k−1⋅(10/9)>0FtR(g) - FtR(f) \geq 10^{-k} - 10^{-k-1}\cdot (10/9) > 0. وبشكلٍ خاص، نرى أنّه لكلّ f,g∈{0,1}∞f,g \in \{0,1\}^\infty مختلفَين، FtR(f)≠FtR(g)FtR(f) \neq FtR(g)، ممّا يعني أنّ الدالة FtRFtR واحد-لواحدة.

ملاحظة — استعمال المفكّك العشري (اختياري)

في البرهان أعلاه استعملنا الحقيقةَ إنّ 1+1/10+1/100+⋯1 + 1/10 + 1/100 + \cdots تتقارب إلى 10/910/9، وبإدراجها في المعادلة نحصل على أنّ الفرق بين FtR(g)FtR(g) وFtR(h)FtR(h) هو على الأقل 10−k−10−k−1⋅(10/9)>010^{-k} - 10^{-k-1}\cdot (10/9) > 0. ولأنّ اختيارَ التمثيل العشري لـFtRFtR كان اعتباطيًّا، إلا أنّه لم يكن في وسعتنا استعمالُ التمثيل الثنائي بدلًا منه. فلو استعملنا التوسيعَ الثنائي بدلًا من العشري، لكانت المتسلّلةُ المقابلة 1+1/2+1/4+⋯1 + 1/2 + 1/4 + \cdots تتقارب إلى 2/1=22/1=2، ولأنّ 2−k=2−k−1⋅22^{-k} = 2^{-k-1} \cdot 2، لما استطعنا أن نستنتج أنّ FtRFtR واحد-لواحدة. وبالفعل، هناك فعلًا زوجان من المتتابعات f,g∈{0,1}∞f,g\in \{0,1\}^\infty مختلفَتين بحيث ∑i=0∞f(i)2−i=∑i=0∞g(i)2−i\sum_{i=0}^\infty f(i)2^{-i} = \sum_{i=0}^\infty g(i)2^{-i}. (فمثلًا، المتتابعة 1,0,0,0,…1,0,0,0,\ldots والمتتابعة 0,1,1,1,…0,1,1,1,\ldots تملكان هذه الخاصية.)

نتيجة: الدوال المنطقية غير قابلة للعدّ

تنبثق من مبرهنة كانْتور النتيجةُ التالية التي سنستعملها عدّة مرّات في هذا الكتاب: إنّ مجموعةَ كلّ الدوال المنطقية (Boolean functions) (التي تُسقط {0,1}∗\{0,1\}^* على {0,1}\{0,1\}) غير قابلة للعدّ:

مبرهنة — الدوال المنطقية غير قابلة للعدّ

لنكن ALLALL مجموعةَ كلّ الدوال F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\}. عندئذٍ ALLALL غير قابلة للعدّ. وبعبارةٍ مكافئة، لا توجد خريطةٌ شاملة StALL:{0,1}∗→ALLStALL:\{0,1\}^* \rightarrow ALL.

وهذا نتيجةٌ مباشرة لـلمّة، إذ يمكننا استعمالَ التمثيل الثنائي لإظهار خريطةٍ واحد-لواحد من {0,1}∞\{0,1\}^\infty إلى ALLALL. وعليه، فإنّ عدم قابلية {0,1}∞\{0,1\}^\infty للعدّ يستلزم عدمَ قابلية ALLALL للعدّ.

برهان

ولأنّ {0,1}∞\{0,1\}^\infty غير قابلة للعدّ، فإنّ النتيجة ستنبع بإظهار خريطةٍ واحد-لواحد من {0,1}∞\{0,1\}^\infty إلى ALLALL. والسبب في ذلك أنّ وجودَ مثل هذه الخريطة يعني أنّه لو كانت ALLALL قابلة للعدّ، وبالتالي لو وُجدت خريطةٌ واحد-لواحد من ALLALL إلى N\N، لَوُجدت خريطةٌ واحد-لواحد من {0,1}∞\{0,1\}^\infty إلى N\N، وهذا يناقض لمّة.

والآن نُبيّن هذه الخريطة الواحد-لواحد. فنكتفي بإسقاط دالةٍ f∈{0,1}∞f \in \{0,1\}^\infty على الدالة F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} على النحو التالي. لنُعيّن F(0)=f(0)F(0)=f(0)، وF(1)=f(1)F(1)=f(1)، وF(10)=f(2)F(10)=f(2)، وF(11)=f(3)F(11)=f(3) وهكذا وهكذا. أي إنّنا لكلّ x∈{0,1}∗x\in \{0,1\}^* يمثّل عددًا طبيعيًّا nn بالأساس الثنائي، نعرّف F(x)=f(n)F(x)=f(n). وإذا لم يمثّل xx عددًا من هذا النوع (مثلًا، إذا كان له صفرٌ بادئ)، فإنّنا نُعيّن F(x)=0F(x)=0.

وهذه الخريطة واحد-لواحدة، لأنّه إذا كان f≠gf \neq g عنصرين مختلفَين في {0,1}∞\{0,1\}^\infty، فلا بدّ أن يختلفا في مُدخَلٍ ما n∈Nn\in \N. لكن عندئذٍ، إذا كانت x∈{0,1}∗x\in \{0,1\}^* هي السلسلة التي تمثّل nn، فإنّنا نرى أنّ F(x)≠G(x)F(x) \neq G(x) حيث FF هي الدالة في ALLALL التي أُسقطت إليها ff، وGG هي الدالة التي أُسقطت إليها gg.

الشروط المكافئة لقابلية العدّ

النتائجُ أعلاه تتيّن صياغاتٍ متعدّدة ومتكافئة لحقيقة أنّ مجموعةً ما قابلة للعدّ. وتحديدًا، فالعباراتُ التالية كلّها متكافئة:

  1. المجموعة SS قابلة للعدّ

  2. هناك خريطةٌ شاملة من N\N إلى SS

  3. هناك خريطةٌ شاملة من {0,1}∗\{0,1\}^* إلى SS.

  4. هناك خريطةٌ واحد-لواحد من SS إلى N\N

  5. هناك خريطةٌ واحد-لواحد من SS إلى {0,1}∗\{0,1\}^*.

  6. هناك خريطةٌ شاملة من مجموعةٍ قابلة للعدّ TT ما إلى SS.

  7. هناك خريطةٌ واحد-لواحد من SS إلى مجموعةٍ قابلة للعدّ TT ما.

توقّف وتأمّل

تأكّد من أنّك تعرف كيف تُثبِت تكافؤَ كلّ النتائج أعلاه.

تمثيل كائناتٍ غير الأعداد

الأعدادُ ليست بالطبع الكائنات الوحيدة التي يمكننا تمثيلَها في سلاسل ثنائية. ويتكوّن مخطّطُ التمثيل (representation scheme) المستخدم لتمثيل كائناتٍ من مجموعة O\mathcal{O} من دالةِ ترميز (encoding) تُسقط كائنًا من O\mathcal{O} على سلسلة، ودالةِ فكّ ترميز (decoding) تُعيد السلسلةَ إلى كائنٍ في O\mathcal{O}. ورسميًّا، نُعرِّف ما يلي:

تعريف — التمثيل في سلاسل

لنكن O\mathcal{O} مجموعةً ما. مخطّطُ تمثيلٍ (representation scheme) لـO\mathcal{O} هو زوجٌ من الدوال E,DE,D حيث E:O→{0,1}∗E:\mathcal{O} \rightarrow \{0,1\}^* دالةٌ كلّيةٌ واحد-لواحدة، وD:{0,1}∗→pOD:\{0,1\}^* \rightarrow_p \mathcal{O} دالةٌ (قد تكون جزئية)، بحيث يحقّق DD وEE الشرطَ D(E(o))=oD(E(o))=o لكلّ o∈Oo\in \mathcal{O}. وتُعرَّف EE بأنها دالةُ الترميز (encoding)، وتُعرَّف DD بأنها دالةُ فكّ الترميز (decoding).

لاحظ أنّ الشرط D(E(o))=oD(E(o))=o لكلّ o∈Oo\in\mathcal{O} يستلزم أنّ DD شاملة (onto) (هل ترى لماذا؟). ويتبين أنّه لبناء مخطّط تمثيلٍ لا يلزم سوى العثور على دالةِ ترميز. أي إنّ لكلّ دالة ترميزٍ واحد-لواحد هناك دالةُ فكّ ترميزٍ مقابلة، كما يبيّنه المُبرهَن التالي:

لمّة

لنفترض أنّ E:O→{0,1}∗E: \mathcal{O} \rightarrow \{0,1\}^* واحد-لواحدة. عندئذٍ هناك دالة D:{0,1}∗→OD:\{0,1\}^* \rightarrow \mathcal{O} بحيث D(E(o))=oD(E(o))=o لكلّ o∈Oo\in \mathcal{O}.

برهان

لنأخذ عنصرًا اعتباطيًّا ما o0o_0 من O\mathcal{O}. فلكلّ x∈{0,1}∗x \in \{0,1\}^*، إمّا ألّا يوجد o∈Oo\in\mathcal{O} بحيث E(o)=xE(o)=x، أو يوجد عنصرٌ واحدٌ فقط من هذا النوع (وإلّا ما كانت EE واحد-لواحدة). وسنُعرِّف D(x)D(x) لتكون o0o_0 في الحالة الأولى، ولتساوي هذا العنصر الواحد oo في الحالة الثانية. وبالتعريف يكون D(E(o))=oD(E(o))=o لكلّ o∈Oo\in \mathcal{O}.

ملاحظة — دوال فكّ الترميز الكلّية

ولأنّ دالةَ فكّ الترميز في مخطّط التمثيل يمكن أن تكون جزئية (partial) في الأصل، فإنّ برهان لمّة يستلزم أنّ لكلّ مخطّط تمثيلٍ دالةَ فكّ ترميزٍ كلّية (total). وقد تكون هذه الملاحظة مفيدة أحيانًا.

التمثيلات المنتهية

إذا كانت O\mathcal{O} منتهية (finite)، فبإمكاننا تمثيلُ كلّ كائنٍ في O\mathcal{O} بسلسلةٍ طولها لا يتجاوز عددًا ما nn. وما قيمة nn؟ لنرمز بـ{0,1}≤n\{0,1\}^{\leq n} إلى المجموعة {x∈{0,1}∗:∣x∣≤n}\{ x\in \{0,1\}^* : |x| \leq n \} من السلاسل التي لا يتجاوز طولها nn. وحجمُ {0,1}≤n\{0,1\}^{\leq n} يساوي ∣{0,1}0∣+∣{0,1}1∣+∣{0,1}2∣+⋯+∣{0,1}n∣=∑i=0n2i=2n+1−1 |\{0,1\}^0| + |\{0,1\}^1| + |\{0,1\}^2| + \cdots + |\{0,1\}^n| = \sum_{i=0}^n 2^i = 2^{n+1}-1 باستعمال الصيغة القياسية لمجموع متتاليةٍ هندسية.

وللحصول على تمثيلٍ لكائنات O\mathcal{O} في سلاسلٍ طولها لا يتجاوز nn، نحتاج إلى ايجاد دالةٍ واحد-لواحد من O\mathcal{O} إلى {0,1}≤n\{0,1\}^{\leq n}. ويمكننا فعلُ ذلك إذا وإذا فقط كان ∣O∣≤2n+1−1|\mathcal{O}| \leq 2^{n+1}-1، كما يستلزمه المُبرهَن التالي:

لمّة

لكلّ مجموعتين منتهيتين غير خاليتين S,TS,T، هناك دالةٌ واحد-لواحد E:S→TE:S \rightarrow T إذا وإذا فقط كان ∣S∣≤∣T∣|S| \leq |T|.

برهان

لنكتب k=∣S∣k=|S| وm=∣T∣m=|T|، وبالتالي نكتب عناصرَ SS وTT على هيئة S={s0,s1,…,sk−1}S = \{ s_0 , s_1 , \ldots, s_{k-1} \} وT={t0,t1,…,tm−1}T= \{ t_0 , t_1 , \ldots, t_{m-1} \}. ونحتاج إلى إظهار أنّ هناك دالةً واحد-لواحد E:S→TE: S \rightarrow T إذا وإذا فقط كان k≤mk \leq m. في اتجاه «إذا»، إذا كان k≤mk \leq m يمكننا ببساطة نعرِّف E(si)=tiE(s_i)=t_i لكلّ i∈[k]i\in [k]. ومن الواضح أنّه لكلّ i≠ji \neq j، ti=E(si)≠E(sj)=tjt_i = E(s_i) \neq E(s_j) = t_j، ومن ثمّ فإنّ هذه الدالة واحد-لواحدة. وفي الاتجاه الآخر، نفترض أنّ k>mk>m وأنّ E:S→TE: S \rightarrow T دالةٌ ما. عندئذٍ لا يمكن أن تكون EE واحد-لواحدة. بالفعل، لكلّ i=0,1,…,m−1i=0,1,\ldots,m-1 لنُعلِّم العنصرَ tj=E(si)t_j=E(s_i) في TT. وإذا كان tjt_j معلَّمًا من قبل، فقد وجدنا كائنَين في SS يُسقطان على العنصر نفسه tjt_j. وإلّا، ولأنّ في TT عددًا من العناصر يساوي mm، فعندما نصل إلى i=m−1i=m-1 نكون قد علّمنا كلَّ عناصر TT. وعليه، في هذه الحالة، لا بدّ أن يُسقط E(sm)E(s_m) على عنصرٍ كان معلَّمًا من قبل. وتُعرف هذه الملاحظة أحيانًا باسم «مبدأ الحمّام» (Pigeonhole Principle): وهو المبدأ القائل إنّك إن كان لديك قنٌ للدجاج فيه mm ثقبًا وk>mk>m حمامة، فلا بدّ من وجود حمامتَين في الثقب نفسه.

الترميزُ الخالي من البادئات

ولإظهار مخطّطِ تمثيلٍ للأعداد النسبية، استعملنا «الحيلة» (hack) التي تقضي بترميز الأبجدية {0,1,∥}\{ 0,1, \|\} لتمثيل الصفوف المرتّبة من السلاسل في سلسلةٍ واحدة. وهذه حالةٌ خاصة من النمط العام للترميز الخالي من البادئات (prefix-free). والفكرةُ كالتالي: إن كان تمثيلُنا بحيث لا تكون السلسلةُ xx التي تمثّل كائنًا oo بادئةً (prefix) (أي سلسلةٍ ابتدائية) لسلسلة yy تمثّل كائنًا مختلفًا o′o'، فبإمكاننا تمثيلَ قائمةٍ من الكائنات بمجرّد ضمّ تمثيلات كلّ أعضاء القائمة. فمثلًا، لأنّ كلّ جملةٍ في الإنجليزية تنتهي بعلامة ترقيم مثل النقطة أو علامة التعجب أو علامة الاستفهام، فلا يمكن لأيّ جملةٍ أن تكون بادئةً لجملةٍ أخرى، ومن ثمّ يمكننا تمثيلَ قائمةٍ من الجمل بمجرّد ضمّ الجمل واحدةً تلو الأخرى. (وللإنجليزية بعضُ التعقيدات، مثل النقاط المستعملة في الاختصارات (مثل «e.g.») أو أقواس الجمل التي تحتوي علامات ترقيم، لكنّ الفكرة الجوهرية عن تمثيلٍ خالٍ من البادئات للجمل تبقى صحيحة.)

ويتبين أنّنا يمكننا تحويلَ كلّ تمثيلٍ إلى صورةٍ خاليةٍ من البادئات. وهذا يبرّر representtuplesidea، ويسمح لنا بتحويل مخطّطِ تمثيلٍ لكائناتٍ من نوع TT إلى مخطّطِ تمثيلٍ للقوائم من كائنات النوع TT. وبتكرار التقنية نفسها، يمكننا أيضًا تمثيلَ قوائمَ قوائمَ من كائناتِ النوع TT، وهكذا وهكذا. لكن لنُعرِّف أوّلًا «خلوّ التمثيل من البادئات» تعريفًا رسميًّا:

تعريف — الترميز الخالي من البادئات

لسلسلتين y,y′y,y'، نقول إنّ yy بادئةٌ لـy′y' إذا كان ∣y∣≤∣y′∣|y| \leq |y'| ولكلّ i<∣y∣i<|y| كان yi′=yiy'_i = y_i.

لنكن O\mathcal{O} مجموعةً غير خالية، وE:O→{0,1}∗E:\mathcal{O} \rightarrow \{0,1\}^* دالة. نقول إنّ EE خاليةٌ من البادئات (prefix-free) إذا كان E(o)E(o) غير فارغٍ لكلّ o∈Oo\in\mathcal{O}، ولم يوجد زوجٌ مختلفٌ من الكائنات o,o′∈Oo, o' \in\mathcal{O} بحيث يكون E(o)E(o) بادئةً لـE(o′)E(o').

تذكّر أنّه لكلّ مجموعة O\mathcal{O}، تتكوّن المجموعة O∗\mathcal{O}^* من كلّ الصفوف المرتّبة (tuples) (أي القوائم) ذات الطول المنتهي من عناصر O\mathcal{O}. ويُظهر المبرهنةُ التالية أنّه إذا كانت EE ترميزًا خاليًا من البادئات لـO\mathcal{O}، فبإمكاننا، بضمّ الترميزات، الحصولَ على تمثيلٍ صحيحٍ (أي واحد-لواحد) لـO∗\mathcal{O}^*:

مبرهنة — خلوّ الترميز من البادئات يستلزم ترميزَ الصفوف المرتّبة

لنفترض أنّ E:O→{0,1}∗E:\mathcal{O} \rightarrow \{0,1\}^* خاليةٌ من البادئات. عندئذٍ تكون الخريطةُ التالية E‾:O∗→{0,1}∗\overline{E}:\mathcal{O}^* \rightarrow \{0,1\}^* واحد-لواحدة، إذ إنّنا لكلّ (o0,…,ok−1)∈O∗(o_0,\ldots,o_{k-1}) \in \mathcal{O}^* نعرّف E‾(o0,…,ok−1)=E(o0)E(o1)⋯E(ok−1)  . \overline{E}(o_0,\ldots,o_{k-1}) = E(o_0)E(o_1) \cdots E(o_{k-1}) \;.

توقّف وتأمّل

المبرهنةُ مبرهنة — خلوّ الترميز من البادئات يستلزم ترميزَ الصفوف المرتّبة مثالٌ على مبرهنةٍ قد يصعب فهمُها قليلًا، لكنّها في الحقيقة سهلةُ البرهان نسبيًّا متى فهمتَ ما تعنيه. ولذلك فإنّي أنصحك بشدّة بأن تتوقّف هنا وتتأكّد من فهم نصّ هذه المبرهنة. وينبغي لك أيضًا محاولةُ إثباتها بنفسك قبل المضيّ قدمًا.

/images/introtcs/lec_02_representation-8.webp

الفكرةُ وراء البرهان بسيطة. لنفترض مثلًا أننا نريد فكَّ ترميز ثلاثيّةٍ (o0,o1,o2)(o_0,o_1,o_2) من تمثيلها x=E‾(o0,o1,o2)=E(o0)E(o1)E(o2)x= \overline{E}(o_0,o_1,o_2)=E(o_0)E(o_1)E(o_2). وسنقوم بذلك بإيجاد أوّل بادئةٍ x0x_0 من xx تكون تمثيلًا لكائنٍ ما. ثمّ نفكّ ترميز هذا الكائن، ونحذف x0x_0 من xx للحصول على سلسلةٍ جديدة x′x'، ثمّ نتابع إيجاد أوّل بادئةٍ x1x_1 من x′x' وهكذا وهكذا (انظر تمرين — الترميز الخالي من البادئات للصفوف المرتّبة). وسيضمن خاصيةُ خلوّ EE من البادئات أنّ x0x_0 ستكون في الواقع E(o0)E(o_0)، وأنّ x1x_1 ستكون E(o1)E(o_1)، إلخ.

برهان

والآن نُبيّن البرهانَ الرسمي. لنفترض، قصدًا للتناقض، وجودَ صفَّين مختلفَين (o0,…,ok−1)(o_0,\ldots,o_{k-1}) و(o0′,…,ok′−1′)(o'_0,\ldots,o'_{k'-1}) بحيث

E‾(o0,…,ok−1)=E‾(o0′,…,ok′−1′)  . \overline{E}(o_0,\ldots,o_{k-1})= \overline{E}(o'_0,\ldots,o'_{k'-1}) \;.

سنسمّي السلسلة E‾(o0,…,ok−1)\overline{E}(o_0,\ldots,o_{k-1}) بـx‾\overline{x}.

لنكن ii أوّلَ دليلٍ بحيث oi≠oi′o_i \neq o'_i. (وإذا كان oi=oi′o_i=o'_i لكلّ ii، فإنّه بما أنّنا نفترض أنّ الصفَّين مختلفَين، فلا بدّ أن يكون أحدهما أكبرَ من الآخر. وفي هذه الحالة نفترض دون فَقد شيءٍ من العموم أنّ k′>kk'>k ولنأخذ i=ki=k.) وفي الحالة i<ki<k، نرى أنّ السلسلة x‾\overline{x} يمكن كتابتُها بطريقتين:

x‾=E‾(o0,…,ok−1)=x0⋯xi−1E(oi)E(oi+1)⋯E(ok−1) \overline{x} = \overline{E}(o_0,\ldots,o_{k-1}) = x_0\cdots x_{i-1} E(o_i) E(o_{i+1}) \cdots E(o_{k-1})

و

x‾=E‾(o0′,…,ok′−1′)=x0⋯xi−1E(oi′)E(oi+1′)⋯E(ok′−1′) \overline{x} = \overline{E}(o'_0,\ldots,o'_{k'-1}) = x_0\cdots x_{i-1} E(o'_i) E(o'_{i+1}) \cdots E(o'_{k'-1})

حيث xj=E(oj)=E(oj′)x_j = E(o_j) = E(o'_j) لكلّ j<ij<i. لنكن y‾\overline{y} السلسلةَ الناتجة عن حذف البادئة x0⋯xi−1x_0 \cdots x_{i-1} من x‾\overline{x}. ونرى أنّ y‾\overline{y} يمكن كتابتُها على أنّها معًا y‾=E(oi)s\overline{y}= E(o_i)s لسلسلةٍ ما s∈{0,1}∗s\in \{0,1\}^* وأنّها y‾=E(oi′)s′\overline{y} = E(o'_i)s' لسلسلةٍ ما s′∈{0,1}∗s'\in \{0,1\}^*. لكنّ هذا يعني أنّ أحدَ E(oi)E(o_i) وE(oi′)E(o'_i) لا بدّ أن يكون بادئةً للآخر، وهذا يناقض خلوّ EE من البادئات.

أمّا في الحالة i=ki=k وk′>kk'>k، فنصل إلى تناقضٍ بالطريقة التالية. في هذه الحالة

x‾=E(o0)⋯E(ok−1)=E(o0)⋯E(ok−1)E(ok′)⋯E(ok′−1′)\overline{x} = E(o_0)\cdots E(o_{k-1}) = E(o_0) \cdots E(o_{k-1}) E(o'_k) \cdots E(o'_{k'-1})

أي ما يعني أنّ E(ok′)⋯E(ok′−1′)E(o'_k) \cdots E(o'_{k'-1}) لا بدّ أن يقابل السلسلةَ الفارغة ""\text{""}. لكنّ في هذه الحالة لا بدّ أن تكون E(ok′)E(o'_k) هي السلسلة الفارغة، وهي على الأخص بادئةٌ لأيّ سلسلةٍ أخرى، وهذا يناقض خلوّ EE من البادئات.

ملاحظة — خلوّ تمثيل القوائم من البادئات

حتى لو كان التمثيلُ EE لكائنات O\mathcal{O} خاليًا من البادئات، فإنّ هذا لا يعني أنّ تمثيلَنا E‾\overline{E} لـ_القوائم_ من هذه الكائنات سيكون خاليًا من البادئات أيضًا. وبالفعل، لن يكون كذلك: فلكلّ ثلاثة كائنات o,o′,o′′o,o',o''، يكون تمثيلُ القائمة (o,o′)(o,o') بادئةً لتمثيل القائمة (o,o′,o′′)(o,o',o''). لكنّنا، كما سنرى في لمّة أدناه، نستطيع تحويلَ كلّ تمثيلٍ إلى صورةٍ خاليةٍ من البادئات، وسنكون عندئذٍ قادرين على استعمال ذلك التحويل عند الحاجة لتمثيل قوائمَ القوائم، وقوائمَ قوائمَ القوائم، وهكذا وهكذا.

جعلُ التمثيلات خاليةً من البادئات

بعضُ التمثيلات الطبيعية خاليةٌ من البادئات. فمثلًا، كلُّ تمثيلٍ ذي طولِ مُخرَجٍ ثابتٍ (fixed output length) (أي دالةٍ واحد-لواحد E:O→{0,1}nE:\mathcal{O} \rightarrow \{0,1\}^n) يكون خاليًا من البادئات تلقائيًّا، إذ لا يمكن لسلسلةٍ xx أن تكون بادئةً لسلسلة x′x' مساويةٍ لها في الطول إلّا إذا كانت xx وx′x' متطابقتَين. وعلاوةً على ذلك، يمكن استعمال الطريقة التي استعملناها في تمثيل الأعداد النسبية لإظهار ما يلي:

لمّة

لنكن E:O→{0,1}∗E:\mathcal{O} \rightarrow \{0,1\}^* دالةً واحد-لواحد. عندئذٍ هناك ترميزٌ E‾\overline{E} واحد-لواحد وخالٍ من البادئات بحيث ∣E‾(o)∣≤2∣E(o)∣+2|\overline{E}(o)| \leq 2|E(o)|+2 لكلّ o∈Oo\in\mathcal{O}.

توقّف وتأمّل

ولأجل الاكتمال سنُدرج البرهانَ أدناه، لكنّه فكرةٌ جيّدة أن تتوقّف هنا وتحاول إثباتَه بنفسك، مستعملًا التقنيةَ نفسها التي استعملناها في تمثيل الأعداد النسبية.

برهان

وتتمثّل فكرةُ البرهان في استعمال الخريطة 0↦000 \mapsto 00، 1↦111 \mapsto 11 لـ«مضاعفة» كلّ بتٍّ في السلسلة xx، ثمّتعليمُ نهاية السلسلة بإلحاق الزوج 0101 بها. فإذا رمّزنا سلسلةً xx بهذه الطريقة، فإنّ ذلك يضمن ألا يكون ترميزُ xx بادئةً أبدًا لترميزِ سلسلةٍ مختلفة x′x'. ورسميًّا، نعرّف الدالة PF:{0,1}∗→{0,1}∗PF:\{0,1\}^* \rightarrow \{0,1\}^* على النحو التالي: PF(x)=x0x0x1x1…xn−1xn−101PF(x)=x_0 x_0 x_1 x_1 \ldots x_{n-1}x_{n-1}01 لكلّ x∈{0,1}∗x\in \{0,1\}^*. وإذا كان E:O→{0,1}∗E:\mathcal{O} \rightarrow \{0,1\}^* هو التمثيلَ (المحتمل ألّا يكون خاليًا من البادئات) لـO\mathcal{O}، فإنّنا نحوّله إلى تمثيلٍ E‾:O→{0,1}∗\overline{E}:\mathcal{O} \rightarrow \{0,1\}^* خالٍ من البادئات بعرّفين E‾(o)=PF(E(o))\overline{E}(o)=PF(E(o)).

ولإثبات المُبرهَن نحتاج إلى إظهار أنّ (1) E‾\overline{E} واحد-لواحدة و__(2)__ E‾\overline{E} خاليةٌ من البادئات. وبالفعل، خلوُّ التمثيل من البادئات شرطٌ أقوى من الواحد-لواحد (فإذا تساوت سلسلتان، فإنّ إحداهما بادئةٌ للأخرى بالضرورة)، ومن ثمّ يكفي إثباتُ (2)، وهو ما سنفعله الآن.

لنكن o≠o′o \neq o' كائنَين مختلفَين في O\mathcal{O}. سنُثبت أنّ E‾(o)\overline{E}(o) ليست بادئةً لـE‾(o′)\overline{E}(o')، أو بعبارةٍ أخرى إنّ PF(x)PF(x) ليست بادئةً لـPF(x′)PF(x') حيث x=E(o)x = E(o) وx′=E(o′)x'=E(o'). ولأنّ EE واحد-لواحدة، فإنّ x≠x′x \neq x'. وسنقسم إلى ثلاث حالات، تبعًا لكون ∣x∣<∣x′∣|x|<|x'| أو ∣x∣=∣x′∣|x|=|x'| أو ∣x∣>∣x′∣|x|>|x'|. فإذا كان ∣x∣<∣x′∣|x|<|x'|، فإنّ البتَّين في الموضعين 2∣x∣,2∣x∣+12|x|,2|x|+1 من PF(x)PF(x) لهما القيمة 0101، أمّا البتّان المقابلتان في PF(x′)PF(x') فتساويان إمّا 0000 أو 1111 (حسب البتّ رقم ∣x∣|x| في x′x')، ومن ثمّ لا يمكن أن تكون PF(x)PF(x) بادئةً لـPF(x′)PF(x'). وإذا كان ∣x∣=∣x′∣|x|=|x'|، فإنّه بما أنّ x≠x′x \neq x' فلا بدّ من إحداثيٍ ii تختلفان فيه، أي إنّ السلسلتين PF(x)PF(x) وPF(x′)PF(x') تختلفان في الإحداثيّين 2i,2i+12i,2i+1، وهذا أيضًا يعني أنّ PF(x)PF(x) لا يمكن أن تكون بادئةً لـPF(x′)PF(x'). وإذا كان ∣x∣>∣x′∣|x|>|x'|، فإنّ ∣PF(x)∣=2∣x∣+2>∣PF(x′)∣=2∣x′∣+2|PF(x)|=2|x|+2>|PF(x')|=2|x'|+2، ومن ثمّ تكون PF(x)PF(x) أطولَ من PF(x′)PF(x') (ولا يمكن أن تكون بادئةً لها). وفي كلّ الحالات نرى أنّ PF(x)=E‾(o)PF(x)=\overline{E}(o) ليست بادئةً لـPF(x′)=E‾(o′)PF(x')=\overline{E}(o')، وبهذا يكتمل البرهان.

وليس برهانُ لمّة الطريقةَ الوحيدة، ولا حتى أفضلَ طريقةٍ، لتحويل تمثيلٍ اعتباطيٍّ إلى صورةٍ خاليةٍ من البادئات. ويطلب منك تمرين — تحويلٍ خالٍ من البادئات أكثرَ كفاءة أن تبني تحويلًا خاليًا من البادئات أكثرَ كفاءةً بحيث يحقّق ∣E‾(o)∣≤∣E(o)∣+O(log⁡∣E(o)∣)|\overline{E}(o)| \leq |E(o)| + O(\log |E(o)|).

«البرهانُ ببايثون» (اختياري)

برهانُ مبرهنة — خلوّ الترميز من البادئات يستلزم ترميزَ الصفوف المرتّبة ولمّة إنشائيّان (constructive) بالمعنى أنّهما يوفّران لنا:

  • طريقةً لتحويل دالتي الترميز وفكّ الترميز الخاصّتَين بأيّ تمثيلٍ لكائن OO إلى دالتي ترميزٍ وفكّ ترميزٍ خاليتَين من البادئات، و

  • طريقةً لتوسيع الترميزِ الخالي من البادئات وفكِّه من كائنٍ مفرد إلى ترميزِ قوائمَ الكائنات وفكِّها بالضمّ.

وتحديدًا، يمكننا تحويلَ أيّ زوجٍ من دوال بايثون encode وdecode إلى دوالَ pfencode وpfdecode تقابل ترميزًا وفكّ ترميزٍ خاليَين من البادئات. وبالمثل، إذا أعطينا pfencode وpfdecode لكائنٍ مفرد، فيمكننا توسيعَهما إلى ترميز القوائم. ولنُبيّن كيف يعمل هذا في حالة الدالتين NtS وStN اللتين عرّفناهما أعلاه.

ونبدأ بـ«برهان بايثون» لـلمّة: أي طريقةٍ لتحويل تمثيلٍ اعتباطيٍّ إلى تمثيلٍ خالٍ من البادئات. والدالةُ prefixfree أدناه تأخذ كمدخلَين زوجَ دالتي ترميزٍ وفكّ ترميز، وتُرجِع ثلاثيةَ دوال تحتوي دالتي ترميزٍ وفكّ ترميزٍ خالتيَين من البادئات، فضلًا عن دالةٍ تتحقّق ما إذا كانت سلسلةٌ ما ترميزًا صحيحًا لكائن.

# takes functions encode and decode mapping
# objects to lists of bits and vice versa,
# and returns functions pfencode and pfdecode that
# maps objects to lists of bits and vice versa
# in a prefix-free way.
# Also returns a function pfvalid that says
# whether a list is a valid encoding
def prefixfree(encode, decode):
    def pfencode(o):
        L = encode(o)
        return [L[i//2] for i in range(2*len(L))]+[0,1]
    def pfdecode(L):
        return decode([L[j] for j in range(0,len(L)-2,2)])
    def pfvalid(L):
        return (len(L) % 2 == 0 ) and all(L[2*i]==L[2*i+1] for i in range((len(L)-2)//2)) and L[-2:]==[0,1]

    return pfencode, pfdecode, pfvalid

pfNtS, pfStN , pfvalidN = prefixfree(NtS,StN)

NtS(234)
# 11101010
pfNtS(234)
# 111111001100110001
pfStN(pfNtS(234))
# 234
pfvalidM(pfNtS(234))
# true

توقّف وتأمّل

لاحظ أنّ الدالة prefixfree في بايثون أعلاه تأخذ كمدخلَين دالتي بايثون (Python functions) وتُخرج ثلاثَ دوال بايثون. (وعندمّا لا يكون ذلك مُربكًا، نستعمل عبارة «دالة بايثون» أو «روتين فرعي» (subroutine) للتمييز بين مثل هذه المقتطفات من برامج بايثون وبين الدوال الرياضية.) ولستَ بحاجة إلى معرفة بايثون في هذه المادة، لكنّ عليك أن تُتقن فكرةَ الدوال بوصفها كائناتٍ رياضية قائمةً بذاتها، يمكن استعمالُها كمدخلاتٍ ومُخرجاتٍ لدوالٍ أخرى.

والآن نُبيّن «برهانًا ببايثون» لـمبرهنة — خلوّ الترميز من البادئات يستلزم ترميزَ الصفوف المرتّبة. أي إنّنا نُبيّن دالةً represlists تأخذ كمدخلٍ مخطّطَ تمثيلٍ خاليًا من البادئات (منفَّذًا بدوال الترميز وفكّ الترميز واختبار الصلاحية) وتُخرج مخطّطَ تمثيلٍ للقوائم من هذه الكائنات. وإن أردنا جعلُ هذا التمثيل خاليًا من البادئات، لأمكننا إدخالُه في الدالة prefixfree أعلاه.

def represlists(pfencode,pfdecode,pfvalid):
    """
    Takes functions pfencode, pfdecode and pfvalid,
    and returns functions encodelists, decodelists
    that can encode and decode lists of the objects
    respectively.
    """

    def encodelist(L):
        """Gets list of objects, encodes it as list of bits"""
        return "".join([pfencode(obj) for obj in L])

    def decodelist(S):
        """Gets lists of bits, returns lists of objects"""
        i=0; j=1 ; res = []
        while j<=len(S):
            if pfvalid(S[i:j]):
                res += [pfdecode(S[i:j])]
                i=j
            j+= 1
        return res

    return encodelist,decodelist


LtS , StL = represlists(pfNtS,pfStN,pfvalidN)

LtS([234,12,5])
# 111111001100110001111100000111001101
StL(LtS([234,12,5]))
# [234, 12, 5]

تمثيل الحروف والنصوص

يمكننا تمثيلُ حرفٍ أو رمزٍ بسلسلة، ثمّ إن كان هذا التمثيل خاليًا من البادئات، يمكننا تمثيلُ متتابعةٍ من الرموز بمجرّد ضمّ تمثيل كلّ رمز. ومن هذه التمثيلات ASCII الذي يمثّل 128128 حرفًا ورمزًا في سلاسلٍ من 77 بتات. ولأنّ تمثيل ASCII ثابتُ الطول، فهو خالٍ من البادئات تلقائيًّا (هل ترى لماذا؟). أمّا Unicode فهو تمثيلُ (عند زمن كتابة هذا الكتاب) نحو 128,000 رمزٍ في صورة أعداد (تُسمّى نقاطَ ترميز (code points)) بين 00 و 1,114,1111,114,111. وهناك أنواعٌ متعدّدة من التمثيلات الخالية من البادئات لنقاط الترميز، ومن أشهرها UTF-8 الذي يرمّز كلَّ نقطة ترميزٍ في سلسلةٍ طولها بين 88 و3232.

/images/introtcs/lec_02_representation-9.webp

مثال — تمثيل برايل

نظامُ برايل (Braille system) طريقةٌ أخرى لترميز الحروف والرموز الأخرى في سلاسلَ ثنائية. وتحديدًا، في برايل، يُرمَّز كلُّ حرفٍ كسلسلةٍ في {0,1}6\{0,1\}^6، تُكتب بنقاطٍ مُزاحةٍ مرتّبةً في عمودَين وثلاثة أسطر، انظر الشكل التوضيحي. (وتحتاج بعض الرموز إلى أكثر من سلسلةٍ من ستّ بتاتٍ لِترميزها، ولذلك يستعمل برايل ترميزًا خاليًا من البادئات أعمّ.)

اخترع نظامَ برايل عام 1821 لويس برايل حين كان عمره 12 سنةً فقط (مع أنّه ظلّ يعمل عليه ويحسّنه طوال حياته). وكان برايل طفلًا فرنسيًا فقد بصره في سنّ الخامسة بسبب حادثٍ ما.

مثال — تمثيل الكائنات في C (اختياري)

يمكننا استعمالُ لغات البرمجة لاستكشاف كيفية تمثيل بيئة حوسبتنا لقيمَ مختلفة. وهذا الأمر أسهلُ ما يكون في لغات البرمجة «غير الآمنة» مثل C التي تسمح بالوصول المباشر إلى الذاكرة.

فباستعمال برنامجٍ بسيطٍ بلغة C حصلنا على التمثيلات التالية لقيمٍ مختلفة. ومن الممكن أن نرى أنّه في حالة الأعداد الصحيحة، الضربُ في 2 يوافق «إزاحةً يسارية» (left shift) داخل كل بايت. وبالمقابل، في حالة أعداد الفاصلة العائمة، الضربُ في اثنين يوافق زيادةَواحدٍ في جزء الأس (الأسّ) في التمثيل. وفي البنية التي استعملناها، يُمثَّل العدد السالب باستعمال منهج المكمّل الثاني. ويمثّل C السلاسلَ بصورةٍ خاليةٍ من البادئات بضمان وجود بايتٍ صفريٍّ في نهايتها.

int      2    : 00000010 00000000 00000000 00000000
int      4    : 00000100 00000000 00000000 00000000
int      513  : 00000001 00000010 00000000 00000000
long     513  : 00000001 00000010 00000000 00000000 00000000 00000000 00000000 00000000
int      -1   : 11111111 11111111 11111111 11111111
int      -2   : 11111110 11111111 11111111 11111111
string   Hello: 01001000 01100101 01101100 01101100 01101111 00000000
string   abcd : 01100001 01100010 01100011 01100100 00000000
float    33.0 : 00000000 00000000 00000100 01000010
float    66.0 : 00000000 00000000 10000100 01000010
float    132.0: 00000000 00000000 00000100 01000011
double   132.0: 00000000 00000000 00000000 00000000 00000000 10000000 01100000 01000000

تمثيل المتجهات والمصفوفات والصور

متى استطعنا تمثيلَ الأعداد وقوائمَ الأعداد، استطعنا كذلك تمثيلَ المتجهات (وهي في الحقيقة قوائمُ أعداد). وبالمثل، يمكننا تمثيلَ قوائمَ القوائم، ومن ثمّ يمكننا بالتحديد تمثيلَ المصفوفات. ولتمثيل صورة، يمكننا تمثيلَ لون كلّ بكسلٍ بقائمةٍ من ثلاثة أعداد تمثّل شدّات الأحمر والأخضر والأزرق. (ويمكننا التقيّد بثلاثة ألوان أساسية، إذ إنّ أغلب البشر لا يملكون في شبكيتهم سوى ثلاثة أنواعٍ من المخاريط؛ أمّا لتمثيل الألوان التي يراها روبيان البحر [Mantis Shrimp] فكان علينا ستّة عشر لونًا أساسيًّا.) وعليه، تُمثَّل صورةٌ من nn بكسلًا بقائمةٍ من nn قائمةً من هذه القوائم ذات الطول ثلاثة. ويمكن تمثيلُ فيديوٍ كقائمةٍ من الصور. وبالطبع، فإنّ هذه التمثيلات مسرِّفةٌ نوعًا ما، وغالبًا ما تُستعمل تمثيلاتٌ أكثر إحكامًا للصور والفيديوهات، وإن كان ذلك ليس من شأننا في هذا الكتاب.

تمثيل الرسوم البيانية

يمكن تمثيلُ رسمٍ بيانيّ (graph) على nn رأسٍ بوصفه مصفوفةَ التجاور (adjacency) بحجم n×nn\times n يكون مدخلُها (i,j)th(i,j)^{th} مساويًا لـ11 إذا كان الضلع (i,j)(i,j) حاضرًا، ومساويًا لـ00 وإلّا. بعبارةٍ أخرى، يمكننا تمثيلُ رسمٍ بيانيّ موجّه G=(V,E)G=(V,E) على nn رأسًا بسلسلة A∈{0,1}n2A\in \{0,1\}^{n^2} بحيث Ai,j=1A_{i,j}=1 إذا وإذا فقط كان الضلع i  j→∈E\overrightarrow{i\;j}\in E. ويمكننا تحويلُ رسمٍ بيانيّ غير موجّه إلى رسمٍ موجّه باستبدال كلّ ضلع {i,j}\{i,j\} بالضلعين i  j→\overrightarrow{i\; j} وi  j←\overleftarrow{i\;j}

وثمة تمثيلٌ آخر للرسوم البيانية، هو تمثيلُ قائمة التجاور (adjacency list). أي إنّنا نُطابق مجموعةَ رؤوس VV للرسم البياني مع المجموعة [n][n] حيث n=∣V∣n=|V|، ونمثّل الرسمَ البياني G=(V,E)G=(V,E) كقائمةٍ من nn قائمة، تكون القائمةُ رقم ii تتألّف من الجيران الخارجيين للرأس ii. ويكون الفرقُ بين هذين التمثيلين جوهريًّا في بعض التطبيقات، وإن كان لا يُذكر عادةً بالنسبة إلينا.

/images/introtcs/lec_02_representation-10.webp

تمثيل القوائم والقوائم المتداخلة

إن كانت لدينا طريقةٌ لتمثيل كائناتٍ من مجموعة O\mathcal{O} في سلاسلَ ثنائية، فبإمكاننا تمثيلُ قوائمَ هذه الكائنات بتطبيق تحويلٍ خالٍ من البادئات. وعلاوةً على ذلك، يمكننا استعمالُ حيلةٍ شبيهةٍ بما سبق للتعامل مع القوائم المتداخلة (nested). والفكرةُ أنّه إن كان لدينا بعض التمثيل E:O→{0,1}∗E:\mathcal{O} \rightarrow \{0,1\}^*، بإمكاننا تمثيلُ قوائمَ متداخلةٍ من عناصر O\mathcal{O} باستعمال سلاسلَ فوق الأبجدية الخماسية Σ={\Sigma = \{ 0,1,[ , ] , , }\}. فمثلًا، إن كان o1o_1 مُمثَّلًا بـ0011، وo2o_2 مُمثَّلًا بـ10011، وo3o_3 مُمثَّلًا بـ00111، فبإمكاننا تمثيلُ القائمة المتداخلة (o1,(o2,o3))(o_1,(o_2,o_3)) بالسلسلة "[0011,[10011,00111]]" فوق الأبجدية Σ\Sigma. وبترميز كلّ عنصرٍ من عناصر Σ\Sigma بدوره كسلسلةٍ من ثلاثة بتّات، فبإمكاننا تحويلُ أيّ تمثيلٍ لكائنات O\mathcal{O} إلى تمثيلٍ يمكّن من تمثيل القوائم (المتداخلة المحتملة) لهذه الكائنات.

الترميز

سنُطابق عادةً بين كائنٍ وتمثيله في سلسلة. فمثلًا، إذا كانت F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^* دالةً ما تُسقط السلاسلَ على السلاسل، وnn عددًا صحيحًا، فقد نقول عباراتٍ مثل «F(n)+1F(n)+1 أوّليّ» بمعنى أنّنا إن يمثّلنا nn بالسلسلة xx، فإنّ العددَ الصحيح mm الذي تمثّله السلسلة F(x)F(x) يحقّق أنّ m+1m+1 أوّليّ. (ويمكنك أن ترى كيف يوفّر لنا هذا الاصطلاح في مطابقة الكائنات مع تمثيلاتِها كثيرًا من الصياغات المرهقة.) وبالمثل، إن كان x,yx,y كائنَين ما وFF دالةً تأخذ السلاسلَ كمدخلات، فإنّ F(x,y)F(x,y) تعني عندنا نتيجةَ تطبيق FF على تمثيل الزوج المرتَّب (x,y)(x,y). ونستعمل الترميزَ نفسه لاستدعاء الدوالّ على الصفوف المرتّبة من kk عنصرًا من الكائنات، لكلّ kk.

وهذا الاصطلاح في مطابقة الكائن مع تمثيله في سلسلة هو اصطلاحٌ نتبعه نحن البشر في كلّ وقت. فمثلًا، حين يقول الناس عبارةً مثل «1717 عددٌ أوّليّ»، فإنّ المقصود حقًا هو أنّ العددَ الصحيح الذي تمثيلُه العشري هو السلسلة «17» هو عددٌ أوّليّ.

اقتباس

حين نقول

إنّ AA خوارزميةٌ تحسب دالةَ الضرب على الأعداد الطبيعية.

فالمقصود حقًا هو أنّ

إنّ AA خوارزميةٌ تحسب الدالةَ F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^* بحيث إنّ لكلّ زوج a,b∈Na,b \in \N، إذا كانت x∈{0,1}∗x\in \{0,1\}^* سلسلةً تمثّل الزوج (a,b)(a,b) فإنّ F(x)F(x) ستكون سلسلةً تمثّل حاصلَ ضربهما a⋅ba\cdot b.

تعريف المهام الحاسوبية بوصفها دوالَّ رياضية

تجريدًا، العمليةُ الحاسوبية (computational process) هي عمليةٌ تأخذ مُدخَلًا هو سلسلةُ بتاتٍ وتُنتج مُخرَجًا هو سلسلةُ بتاتٍ. ويمكن إنجازُ هذا التحويل من المُدخَل إلى المُخرَج باستعمال حاسوبٍ حديث، أو شخصٍ يتبع تعليمات، أو تطوّرِ نظامٍ طبيعيٍّ ما، أو بأيّ وسيلةٍ أخرى.

في الفصول اللاحقة، سننتقل إلى تعريف العمليات الحاسوبية تعريفًا رياضيًّا، لكنّنا، كما ناقشنا أعلاه، نركّز في الوقت الحالي على المهام الحاسوبية. أي إنّنا نركّز على المواصفة لا على التنفيذ. ومرّةً أخرى، على المستوى التجريدي، تستطيع المهمة الحاسوبية أن تحدّد أيّ علاقةٍ يجب أن تكون بين المُخرَج والمُدخَل. غير أنّه، في معظم هذا الكتاب، سنركّز على أبسط المهمّات وأكثرها شيوعًا، وهي حسابُ دالة. وهذه بعضُ الأمثلة:

  • معطى (تمثيلَ) عددين صحيحين x,yx,y، احسب حاصلَ ضربهما x×yx\times y. وباستعمال تمثيلنا أعلاه، يقابل ذلك حسابَ دالةٍ من {0,1}∗\{0,1\}^* إلى {0,1}∗\{0,1\}^*. وقد رأينا أنّ هناك أكثر من طريقةٍ لحلّ هذه المهمة الحاسوبية، وإنّنا في الواقع لا نعرف حتى الآن أفضلَ خوارزميةٍ لهذه المشكلة.

  • معطى (تمثيلَ) عددٍ صحيح z>1z>1، احسب تحليلَه إلى عواملَ أولية (factorization)، أي قائمةَ الأعداد الأولية p1≤⋯≤pkp_1 \leq \cdots \leq p_k بحيث z=p1⋯pkz = p_1\cdots p_k. ويقابل ذلك أيضًا حسابَ دالةٍ من {0,1}∗\{0,1\}^* إلى {0,1}∗\{0,1\}^*. وثغراتُ معرفتنا بتعقيد هذه المشكلة أكبرُ من ذلك.

  • معطى (تمثيلَ) رسمٍ بيانيّ GG ورأسَين ss وtt، احسب طولَ أقصر طريقٍ في GG بين ss وtt، أو افعل الشيءَ نفسه مع أطول طريق (من غير رؤوسٍ متكرّرة) بين ss وtt. وتقابل المهمّتان معًا حسابَ دالةٍ من {0,1}∗\{0,1\}^* إلى {0,1}∗\{0,1\}^*، وإنّهما يتباينان تباينًا هائلًا في صعوبة الحساب.

  • معطى شيفرةُ برنامجٍ في بايثون، حدّد ما إذا كان هناك مُدخَلٌ يُجبره على حلقةٍ لا نهائية. وتقابل هذه المهمةُ حسابَ دالةٍ جزئية من {0,1}∗\{0,1\}^* إلى {0,1}\{0,1\}، إذ ليست كلُّ سلسلةٍ برنامجًا صحيحًا نحويًّا في بايثون. وسنرى أنّنا نفهم الوضعَ الحسابي لهذه المشكلة، لكنّ الجواب مدهشٌ إلى حدٍّ كبير.

  • معطى (تمثيلَ) صورةٍ II، قرّر ما إذا كانت II صورةَ قطةٍ أم صورةَ كلب. ويقابل ذلك حسابُ دالةٍ ما (جزئية) من {0,1}∗\{0,1\}^* إلى {0,1}\{0,1\}.

ملاحظة — الدوال المنطقية واللغات

الحالةُ الخاصة المهمّة من المهام الحاسوبية تقابل حسابَ دوالَ منطقية (Boolean)، يكون مُخرَجُها بتًّا واحدًا {0,1}\{0,1\}. وحسابُ هذه الدوال يقابل الإجابةَ عن سؤال بنعم/لا، ولذلك تُسمّى هذه المهمة أيضًا مسألةَ قرار (decision problem). وبالنسبة إلى أيّ دالة F:{0,1}∗→{0,1}F:\{0,1\}^* \rightarrow \{0,1\} وx∈{0,1}∗x\in \{0,1\}^* فإنّ مهمةَ حساب F(x)F(x) تقابل مهمةَ تحديد ما إذا كان x∈Lx\in L من عدمه، حيث L={x:F(x)=1}L = \{ x : F(x)=1 \} هي اللغةُ (language) المقابلةُ للدالة FF. (ويُعزى مصطلحُ «اللغة» إلى الروابط التاريخية بين نظرية الحوسبة واللغويات الصورية كما طوّرها نوام تشومسكي.) ولذلك تشير كثيرٌ من الكتب إلى مثل هذه المهمة الحاسوبية بأنّها قرارُ لغةٍ ما.

/images/introtcs/lec_02_representation-11.webp

ولكلّ دالةٍ بعينها FF، قد توجد عدّةُ خوارزمياتٍ ممكنة لحساب FF. وستهمّنا أسئلةٌ مثل:

  • بالنسبة إلى دالةٍ ما FF، هل يمكن أن يكون الحالُ أنّ لا توجد خوارزمية لحساب FF؟

  • وإن كانت هناك خوارزمية، فما أفضلُها؟ وهل يمكن أن تكون FF «غيرَ قابلة للحساب بفعالية» بمعنى أنّ كلّ خوارزميةٍ لحساب FF تتطلّب مقدارًا هائلًا من الموارد على نحوٍ محرِم؟

  • وإذا لم نتمكن من الإجابة عن هذا السؤال، فهل يمكننا إثباتُ تكافؤٍ بين دالتَين مختلفتَين FF وF′F' بمعنى أنّهما إمّا سهلتَان معًا (أي لهما خوارزميّتان سريعَتان) أو صعبتَان معًا؟

  • وهل يمكن لِصعوبة حساب دالةٍ ما أن تكون شيئًا حسنًا في آن؟ وهل يمكننا استعمالُها في تطبيقاتٍ في مجالاتٍ مثل التعمية (cryptography)؟

ولكي نفعل ذلك، سنحتاج إلى تعريفٍ رياضيّ لمفهوم الخوارزمية، وهو ما سنفعله في تعريف الحوسبة.

ميّزوا الدوال عن البرامج!

يجب أن تحذر دائمًا من الالتباس المحتمل بين المواصفات والتنفيذ، أو بما يعادل ذلك بين الدوال الرياضية والخوارزميات/البرامج. ولا يساعدنا في ذلك أنّ لغات البرمجة (بما فيها بايثون) تستعمل مصطلحَ «الدوال» للدلالة على (أجزاءٍ من) البرامج. ويستمدّ هذا الالتباس أيضًا من آلاف السنين من التاريخ الرياضي، حيث كان الناس يعرّفون الدوال عادةً بمعنى طريقةٍ لحسابها.

فمثالًا، تأمّل دالةَ الضرب على الأعداد الطبيعية. إنّها الدالة MULT:N×N→NMULT:\N\times \N \rightarrow \N التي تُسقط زوجًا (x,y)(x,y) من الأعداد الطبيعية على العدد x⋅yx \cdot y. وكما ذكرنا، يمكن تنفيذُها بأكثر من طريقة:

def mult1(x,y):
    res = 0
    while y>0:
        res += x
        y   -= 1
    return res

def mult2(x,y):
    a = str(x) # represent x as string in decimal notation
    b = str(y) # represent y as string in decimal notation
    res = 0
    for i in range(len(a)):
        for j in range(len(b)):
            res += int(a[len(a)-i])*int(b[len(b)-j])*(10**(i+j))
    return res

print(mult1(12,7))
# 84
print(mult2(12,7))
# 84

وكلا الدالتين mult1 وmult2 ينتج المُخرَجَ نفسه معطىً الزوج نفسه من الأعداد الطبيعية كمدخلَات. (مع أنّ mult1 ستستغرق وقتًا أطول بكثير في ذلك حين تصير الأعداد كبيرة.) وعليه، وإن كان هذان برنامجان مختلفان، فإنّهما يحسبان الدالةَ الرياضيةَ نفسها. وهذا التمييزُ بين برنامجٍ أو خوارزميةٍ AA وبين الدالةِ FF التي تحسبها AA سيكون حاسمًا بالنسبة إلينا إلى حدٍّ كبير في هذه المادة (انظر أيضًا الشكل التوضيحي).

/images/introtcs/lec_02_representation-12.webp

الدالةُ ليست هي البرنامج. فالبرنامج يحسب الدالة.

تمييزُ الدوال عن البرامج (أو سائر طرائق الحساب، بما فيها الدوائر و_الآلات_) موضوعٌ محوريّ في هذه المادة. ولهذا السبب، فإنّه موضوعٌ غالبًا ما يتكرّر في الأسئلة التي أُسنِّدها (وكثيرٌ من المحاضرين الآخرين) في الواجبات المنزلية والامتحانات (تلميح، تلميح).

ملاحظة — الحوسبة أبعد من الدوال (متقدّم، اختياري)

تلتقط الدوالّ قدرًا كبيرًا من المهام الحاسوبية، لكنّ من الممكن النظرُ في سياقاتٍ أعمّ أيضًا. وفي البداية، سنتحدّث عن دوالَّ جزئية (partial functions) التي لا تكون معرَّفةً على كلّ المُدخَلات. وحسابُ دالةٍ جزئية لا يقتضي سوى القلق على المُدخَلات التي تكون الدالةُ معرَّفةً عليها. ومن أوضح صياغٍ أخرى لذلك أنّه يمكننا تصميمُ خوارزميةٍ لدالةٍ جزئية FF على افتراض أنّ أحدًا ما «وعَدَنا» بأنّ كلّ مُدخَلاتٍ xx ستكون من النوع الذي تكون عنده F(x)F(x) معرَّفة (وإلّا فلا يهمّنا الناتج). ولذلك تُسمّى هذه المهمّات أيضًا مسائلَ وعدٍ (promise problems).

والتعميمُ الآخر هو النظر في علاقاتٍ (relations) قد يكون لها أكثرُ من مُخرَجٍ ممكنٍ مقبول. فمثلًا، تأمّل مهمّةَ إيجاد أيّ حلٍّ لجملةِ معادلاتٍ معطاة. فالعلاقةُ RR تُسقط سلسلةً x∈{0,1}∗x\in \{0,1\}^* إلى مجموعةٍ من السلاسل R(x)R(x) (مثلًا، قد تصف xx جملةَ معادلات، وعندئذٍ تقابل R(x)R(x) مجموعةَ كلّ الحلول لـxx). ويمكننا أيضًا مطابقةُ العلاقة RR بمجموعةِ أزواج السلاسل (x,y)(x,y) حيث y∈R(x)y\in R(x). وتحلّ عمليةٌ حاسوبيةٌ علاقةً إذا كانت لكلّ x∈{0,1}∗x\in \{0,1\}^* تُخرج سلسلةً ما y∈R(x)y\in R(x).

وفي وقتٍ لاحقٍ من هذا الكتاب، سننظر في مهمّاتٍ أعمّ من ذلك، بما فيها المهمّاتُ التفاعلية (interactive)، مثل إيجاد استراتيجيّةٍ جيّدة في لعبة، والمهمّات المعرَّفة بمفاهيمَ احتمالية، وغيرها. لكنّ في معظم هذا الكتاب، سنركّز على مهمة حساب دالة، بل أحيانًا دالةٍ منطقية، مُخرَجُها بتٌ واحدٌ لا غير. ويُبيّن أنّ قدرًا كبيرًا من نظرية الحوسبة يمكن دراستُه في سياق هذه المهمة، وأنّ الرؤى المكتسَبة فيها تنطبق على السياقات الأعمّ.

خلاصة

  • يمكننا تمثيلُ الكائنات التي نريد الحسابَ عليها باستعمال السلاسل الثنائية.
  • مخطّطُ التمثيل لمجموعةِ كائنات O\mathcal{O} هو خريطةٌ واحد-لواحد من O\mathcal{O} إلى {0,1}∗\{0,1\}^*.
  • يمكننا استعمالُ الترميز الخالي من البادئات لـ«تعزيز» تمثيلٍ لمجموعة O\mathcal{O} إلى تمثيلاتٍ لقوائم عناصر O\mathcal{O}.
  • المهمةُ الحاسوبية الأساسية هي مهمةَ حساب دالةٍ F:{0,1}∗→{0,1}∗F:\{0,1\}^* \rightarrow \{0,1\}^*. وهذه المهمة لا تشمل فحسب حساباتٍ حسابية مثل الضرب والتحليل إلى عواملَ أولية إلخ، بل مهمّاتٍ كثيرة جدًّا أخرى تنشأ في مجالاتٍ متنوّعة كالحساب العلمي والذكاء الاصطناعي ومعالجة الصور وتنقيب البيانات وغيرها كثير.
  • سندرس مسألةَ إيجاد (أو على الأقلّ إعطاء حدودٍ لـ) أفضل خوارزميةٍ لحساب FF لمختلف الدوالّ FF ذات الاهتمام.

التمارين

تمرين

أيُّ هذه الكائنات يمكن تمثيلُه بسلسلة ثنائية؟

a. عددٌ صحيح xx

b. رسمٌ بيانيّ غير موجّه GG.

c. رسمٌ بيانيّ موجّه HH

d. كلُّ ما سبق.

تمرين — التمثيل الثنائي

a. أثبت أنّ الدالة NtS:N→{0,1}∗NtS:\N \rightarrow \{0,1\}^* للتمثيل الثنائي المعرَّفة في المعادلة تفي بأنّه لكلّ n∈Nn\in \N، إذا كان x=NtS(n)x = NtS(n) فإنّ ∣x∣=1+max⁡(0,⌊log⁡2n⌋)|x| =1+\max(0,\floor{\log_2 n}) وxi=⌊x/2⌊log⁡2n⌋−i⌋mod  2x_i = \floor{x/2^{\floor{\log_2 n}-i}} \mod 2.

b. أثبت أنّ NtSNtS دالةٌ واحد-لواحدة، وذلك بايجاد دالةٍ StN:{0,1}∗→NStN:\{0,1\}^* \rightarrow \N بحيث StN(NtS(n))=nStN(NtS(n))=n لكلّ n∈Nn\in \N.

تمرين — أكثر إحكامًا من تمثيل ASCII

يمكن استعمالُ ترميز ASCII لترميز سلسلةٍ من nn حرفًا إنجليزيًّا في سلسلةٍ ثنائيةٍ من 7n7n بت، لكنّنا في هذا التمرين نسأل عن إيجاد تمثيلٍ أكثر إحكامًا لسلاسل الحروف الإنجليزية الصغيرة.

  1. أثبت أنّه يوجد مخطّطُ تمثيلٍ (E,D)(E,D) للسلاسل فوق الأبجدية المكوّنة من 26 حرفًا {a,b,c,…,z}\{ a, b,c,\ldots,z \} في سلاسلَ ثنائية، بحيث إنّ لكلّ n>0n>0 وسلسلةٍ x∈{a,b,…,z}nx \in \{ a,b,\ldots,z \}^n ذات الطول nn، يكون التمثيلُ E(x)E(x) سلسلةً ثنائيةً طولها لا يتجاوز 4.8n+10004.8n+1000. بعبارةٍ أخرى، أثبت أنّه لكلّ nn، هناك دالةٌ واحد-لواحد E:{a,b,…,z}n→{0,1}⌊4.8n+1000⌋E:\{a,b,\ldots, z\}^n \rightarrow \{0,1\}^{\lfloor 4.8n +1000 \rfloor}.

  2. أثبت أنّه لا يوجد مخطّطُ تمثيلٍ للسلاسل فوق الأبجدية {a,b,…,z}\{ a, b,\ldots,z \} في سلاسلَ ثنائية، بحيث إنّ لكلّ سلسلةٍ x∈{a,b,…,z}nx \in \{ a,b,\ldots, z\}^n ذات الطول nn، يكون التمثيلُ E(x)E(x) سلسلةً ثنائيةً طولها ⌊4.6n+1000⌋\lfloor 4.6n+1000 \rfloor. بعبارةٍ أخرى، أثبت أنّه يوجد n>0n>0 بحيث لا توجد دالةٌ واحد-لواحد E:{a,b,…,z}n→{0,1}⌊4.6n+1000⌋E:\{a,b,\ldots,z\}^n \rightarrow \{0,1\}^{\lfloor 4.6n + 1000 \rfloor}.

  3. دالةُ bz2.compress في بايثون هي خريطةٌ من السلاسل إلى السلاسل، تستعمل خوارزميةَ bzip2 للضغط بلا فقدان (lossless) (أي واحد-لواحد). وبعد تحويل النصّ إلى أحرفٍ صغيرة وقصّ المسافات والأرقام، يحتوي نصُّ رواية «الحرب والسلم» لتولستوي على n=2,517,262n=2,517,262 حرفًا. لكنّنا إذا شغّلنا bz2.compress على سلسلة نصّ «الحرب والسلم»، نحصل على سلسلةٍ طولها k=6,274,768k=6,274,768 بت، أي 2.49n2.49n فقط (وبالتحديد أصغرُ بكثير من 4.6n4.6n). اشرح لماذا لا يتعارض هذا مع إجابتك عن السؤال السابق.

  4. ومن اللافت أنّه إذا حاولنا تطبيق bz2.compress على سلسلةٍ عشوائية، نحصل على أداءٍ أسوأ بكثير. ففي تجاربي حصلت على نسبةٍ قدرها نحو 4.784.78 بين عدد البتات في المُخرَج وعدد المحارف في المُدخَل. لكنّه من الممكن تخيّلُ أنّ الأمر يمكن أن يكون أفضل، وأنّ هناك شركةً اسمها «Pied Piper» تملك خوارزميةً تستطيع ضغطَ سلسلةٍ من nn حرفًا إنجليزيًّا صغيرًا عشوائيًّا بلا فقدان إلى أقلّ من 4.6n4.6n بت.[5] برهن أنّ هذا ليس الحال، بأن تُثبت أنّه لكلّ n>100n>100 ولكلّ دالةٍ واحد-لواحد Encode:{a,…,z}n→{0,1}∗Encode:\{a,\ldots,z\}^{n} \rightarrow \{0,1\}^*، إذا تركنا ZZ هو المتغيّر العشوائيّ ∣Encode(x)∣|Encode(x)| (أي طولِ Encode(x)Encode(x)) مع xx المختار عشوائيًّا بالتساوي من المجموعة {a,…,z}n\{a,\ldots,z\}^n، فإنّ القيمةَ المتوقّعة لـZZ لا تقلّ عن 4.6n4.6n.

تمرين — تمثيل الرسوم البيانية: حدٌّ أعلى

أثبت أنّه يوجد تمثيلٌ في سلاسل للرسوم البيانية الموجّهة ذات مجموعة رؤوس [n][n] ودرجةٍ لا تتجاوز 1010، يستعمل 1000nlog⁡n1000 n\log n بت على الأكثر. وبشكلٍ أدقّ، أثبت ما يلي: لنفترض أنّنا نعرّف لكلّ n∈Nn\in\mathbb{N} المجموعةَ GnG_n على أنها المجموعةُ التي تضمّ كلّ الرسوم البيانية الموجّهة (من غير حلقاتٍ ذاتية) فوق مجموعة الرؤوس [n][n]، حيث لا تتجاوز درجةُ كلّ رأسٍ 1010. عندئذٍ، أثبت أنّه لكلّ nn كبيرٍ بما يكفي، هناك دالةٌ واحد-لواحد E:Gn→{0,1}⌊1000nlog⁡n⌋E:G_n \rightarrow \{0,1\}^{\lfloor 1000 n \log n \rfloor}.

تمرين — تمثيل الرسوم البيانية: حدٌّ أدنى

  1. عرّف SnS_n على أنها مجموعةُ الدوالّ الواحد-لواحد والشاملة التي تُسقط [n][n] على [n][n]. وأثبت أنّه يوجد خريطةٌ واحد-لواحد من SnS_n إلى G2nG_{2n}، حيث G2nG_{2n} هي المجموعة المعرَّفة في تمرين — تمثيل الرسوم البيانية: حدٌّ أعلى أعلاه.

  2. في هذا السؤال ستُبيّن أنّه لا يمكن تحسينُ تمثيل تمرين — تمثيل الرسوم البيانية: حدٌّ أعلى ليصبح بطول o(nlog⁡n)o(n \log n). وتحدّدًا، أثبت أنّه لكلّ n∈Nn\in \mathbb{N} كبيرٍ بما يكفي، لا توجد دالةٌ واحد-لواحد E:Gn→{0,1}⌊0.001nlog⁡n⌋+1000E:G_n \rightarrow \{0,1\}^{\lfloor 0.001 n \log n \rfloor +1000}.

تمرين — الضرب في تمثيلاتٍ مختلفة

تذكّر أنّ خوارزمية المدرسة الابتدائية في ضرب عددين تتطلّب O(n2)O(n^2) عملية. ولنفترض بدلًا من استعمال التمثيل العشري، أننا نستعمل أحدَ التمثيلات التالية R(x)R(x) لتمثيل عددٍ xx بين 00 و10n−110^n-1. فأيُّ هذه التمثيلات لا يزال بوسعك أن تضرب بهما العددَين في O(n2)O(n^2) عملية؟

a. التمثيلُ الثنائي القياسي: B(x)=(x0,…,xk)B(x)=(x_0,\ldots,x_{k}) حيث x=∑i=0kxi2ix = \sum_{i=0}^{k} x_i 2^i وkk أكبرُ عددٍ بحيث x≥2kx \geq 2^k.

b. التمثيلُ الثنائي المعكوس: B(x)=(xk,…,x0)B(x) = (x_{k},\ldots,x_0) حيث xix_i معرَّفٌ كما سبق لكلٍّ i=0,…,k−1i=0,\ldots,k-1. \

c. التمثيلُ العشري المُرمَّز ثنائيًّا: B(x)=(y0,…,yn−1)B(x)=(y_0,\ldots,y_{n-1}) حيث yi∈{0,1}4y_i \in \{0,1\}^4 يمثّل الرقمَ العشري رقم ii في xx، مع تحويل 00 إلى 00000000، و11 إلى 00010001، و22 إلى 00100010، وهكذا (أي إنّ 99 تُحوَّل إلى 10011001)

d. كلُّ ما سبق.

تمرين

لنفترض أنّ R:N→{0,1}∗R:\N \rightarrow \{0,1\}^* يقابل تمثيلَ عددٍ xx كسلسلةٍ من 11 عددَها xx (مثلًا، R(4)=1111R(4)=1111، وR(7)=1111111R(7)=1111111، إلخ). وإذا كان x,yx,y عددَين بين 00 و10n−110^n -1، فهل يمكننا بعدُ ضربُ xx وyy باستعمال O(n2)O(n^2) عملية إذا أعطيناهما بالتمثيل R(⋅)R(\cdot)؟

تمرين

تذكّر أنّه إذا كانت FF دالةً واحد-لواحد وشاملة تُسقط عناصرَ مجموعةٍ منتهية UU في مجموعةٍ منتهية VV، فإنّ لحجمي UU وVV قيمةً واحدة. ولتكن B:N→{0,1}∗B:\N\rightarrow\{0,1\}^* الدالةَ بحيث إنّ لكلّ x∈Nx\in\N، يكون B(x)B(x) هو التمثيلَ الثنائي لـxx.

  1. أثبت أنّ x<2kx < 2^k إذا وإذا فقط كان ∣B(x)∣≤k|B(x)| \leq k.

  2. استعمل 1. لحساب حجم المجموعة {y∈{0,1}∗:∣y∣≤k}\{ y \in \{0,1\}^* : |y| \leq k \} حيث ∣y∣|y| تدلّ على طول السلسلة yy.

  3. استعمل 1. و2. لإثبات أنّ 2k−1=1+2+4+⋯+2k−12^k-1 = 1 + 2 + 4+ \cdots + 2^{k-1}.

تمرين — الترميز الخالي من البادئات للصفوف المرتّبة

لنفترض أنّ F:N→{0,1}∗F:\N\rightarrow\{0,1\}^* دالةً واحد-لواحد خاليةً من البادئات، بمعنى أنّه لا يوجد a≠ba\neq b بحيث يكون F(a)F(a) بادئةً لـF(b)F(b).

a. أثبت أنّ F2:N×N→{0,1}∗F_2:\N\times \N \rightarrow \{0,1\}^* المعرَّفة بـF2(a,b)=F(a)F(b)F_2(a,b) = F(a)F(b) (أي ضمّ F(a)F(a) وF(b)F(b)) دالةٌ واحد-لواحدة.

b. أثبت أنّ F∗:N∗→{0,1}∗F_*:\N^*\rightarrow\{0,1\}^* المعرَّفة بـF∗(a1,…,ak)=F(a1)⋯F(ak)F_*(a_1,\ldots,a_k) = F(a_1)\cdots F(a_k) دالةٌ واحد-لواحدة، حيث N∗\N^* تدلّ على مجموعة كلّ القوائم ذات الطول المنتهي من الأعداد الطبيعية.

تمرين — تحويلٍ خالٍ من البادئات أكثرَ كفاءة

لنفترض أنّ F:O→{0,1}∗F:O\rightarrow\{0,1\}^* تمثيلٌ ما (لا يلزم أن يكون خاليًا من البادئات) للكائنات في المجموعة OO، وأنّ G:N→{0,1}∗G:\N\rightarrow\{0,1\}^* تمثيلٌ خالٍ من البادئات للأعداد الطبيعية. عرّف F′(o)=G(∣F(o)∣)F(o)F'(o)=G(|F(o)|)F(o) (أي ضمّ تمثيل الطول وتمثيلِ F(o)F(o)).

a. أثبت أنّ F′F' تمثيلٌ خالٍ من البادئات لـOO.

b. بيّن أنّه يمكننا تحويلُ أيّ تمثيلٍ إلى تمثيلٍ خالٍ من البادئات بتعديلٍ يحوّل سلسلةً من kk بت إلى سلسلةٍ طولها على الأكثر k+O(log⁡k)k+O(\log k).

c. بيّن أنّه يمكننا تحويلُ أيّ تمثيلٍ إلى تمثيلٍ خالٍ من البادئات بتعديلٍ يحوّل سلسلةً من kk بت إلى سلسلةٍ طولها على الأكثر k+log⁡k+O(log⁡log⁡k)k+ \log k + O(\log\log k).[6]

تمرين — متباينة كرافت

لنفترض أنّ S⊆{0,1}∗S \subseteq \{0,1\}^* مجموعةٌ منتهيةٌ خاليةٌ من البادئات، ولنفترض أيضًا أنّ nn عددٌ أكبر من max⁡{∣x∣:x∈X}\max \{ |x| : x\in X \}.

a. لكلّ x∈Sx\in S، لتدلّ L(x)⊆{0,1}nL(x) \subseteq \{0,1\}^n على كلّ السلاسل ذات الطول nn التي بتّاتها الأولى kk هي x0,…,xk−1x_0,\ldots,x_{k-1}. وأثبت أنّ (1) ∣L(x)∣=2n−∣x∣|L(x)|=2^{n-|x|} و (2) لكلّ x,x′∈Sx,x' \in S مختلفَين، تكون L(x)L(x) منفصلةً عن L(x′)L(x').

b. أثبت أنّ ∑x∈S2−∣x∣≤1\sum_{x\in S}2^{-|x|} \leq 1. (تلميح: أوّلًا بيّن أنّ ∑x∈S∣L(x)∣≤2n\sum_{x \in S} |L(x)| \leq 2^n.)

c. أثبت أنّه لا يوجد ترميزٌ خالٍ من البادئات للسلاسل بحِملٍ إضافيٍّ (overhead) أصغرَ من لوغاريتميّ. بعبارةٍ أخرى، أثبت أنّه لا توجد دالة PF:{0,1}∗→{0,1}∗PF:\{0,1\}^* \rightarrow \{0,1\}^* بحيث ∣PF(x)∣≤∣x∣+0.9log⁡∣x∣|PF(x)| \leq |x|+0.9\log |x| لكلّ x∈{0,1}∗x\in \{0,1\}^* كبيرٍ بما يكفي، بحيث تكون المجموعة {PF(x):x∈{0,1}∗}\{ PF(x) : x\in \{0,1\}^* \} خاليةً من البادئات. والمعاملُ 0.90.9 اعتباطيّ؛ فالمهمّ كلّه أنّه أصغرُ من 11.

تمرين — تركيب الدوال الواحد-لواحد

أثبت أنّه لكلّ دالتين واحد-لواحد F:S→TF:S \rightarrow T وG:T→UG:T \rightarrow U، تكون الدالةُ H:S→UH:S \rightarrow U المعرَّفة بـH(x)=G(F(x))H(x)=G(F(x)) واحد-لواحدة.

تمرين — الأعداد الطبيعية والسلاسل

  1. لقد بيّنا أنّ الأعداد الطبيعية يمكن تمثيلُها في سلاسل. وأثبت أنّ الاتجاه الآخر صحيحٌ أيضًا: أنّه هناك خريطةٌ واحد-لواحد StN:{0,1}∗→NStN:\{0,1\}^* \rightarrow \N. (StNStN اختصارٌ لعبارة «السلاسل إلى الأعداد».)

  2. تذكّر أنّ كانْتور أثبت أنّه لا توجد خريطةٌ واحد-لواحد RtN:R→NRtN:\R \rightarrow \N. بيّن أنّ نتيجةَ كانْتور تستلزم مبرهنة — مبرهنة كانتور.

تمرين — إسقاطُ قوائم الأعداد الصحيحة إلى عدد

تذكّر أنّه لكلّ مجموعة SS، تُعرَّف المجموعةُ S∗S^* على أنها مجموعةُ كلّ المتتابعات المنتهية من عناصر SS (أي S∗={(x0,…,xn−1)  ∣  n∈N  ,  ∀i∈[n]xi∈S}S^* = \{ (x_0,\ldots,x_{n-1}) \;|\; n\in\mathbb{N} \;,\; \forall_{i\in [n]} x_i \in S \} ). وأثبت أنّه هناك خريطةٌ واحد-لواحد من Z∗\mathbb{Z}^* إلى N\mathbb{N} حيث Z\mathbb{Z} هي المجموعة {…,−3,−2,−1,0,+1,+2,+3,…}\{ \ldots, -3 , -2 , -1,0,+1,+2,+3,\ldots \} لكلّ الأعداد الصحيحة.

ملاحظاتٌ مرجعية

دراسةُ تمثيل البيانات في سلاسل، بما في ذلك قضايا مثل الضغط (compression) و_تصحيح الأخطاء_ (error corrections)، تقع ضمن اختصاص نظرية المعلومات (information theory)، كما يتناولها الكتابُ الكلاسيكي لكوفر وتوماس [@CoverThomas06]. كما تُدرس التمثيلات أيضًا في مجال تصميم بُنى البيانات (data structures design)، كما يتناوله كتبٌ مثل [@CLRS].

السؤالُ عمّا إذا كانت الأعداد الصحيحة تُمثَّل بأكثر الأرقام دلالةٍ أولًا أم آخرًا يُعرف بـالتر الكبير مقابل الصغير. ويأتي هذا المصطلح من ورقةِ كوهن الممتعة والمفيدة [@cohen1981holy] عن الصراع بين أنصار المدرستين، وهو ما قارنه بالقبائل المتقاتلة في «رحلات غليفر» لجوناثان سويفت. وقد اقترح تمثيلُ المكمّل الثاني للأعداد الصحيحة الموقَّعة في تقريرِ فون نيومان الكلاسيكي [@vonNeumann45] الذي فصّل مناهج تصميم الحاسوب المخزَّن-البرنامجي، وإنّ تمثيلاتٍ مشابهة استُعملت حتى قبل ذلك في العدّاد وفي أجهزة الحساب الميكانيكية الأخرى.

قد تبدو فكرةُ فصلِ تعريفِ الدالة أو مواصفتِها عن تنفيذِها أو حسابِها «بديهيةً»، لكنّ الرياضيين استغرقوا وقتًا طويلًا قبل أن يصلوا إلى هذه الرؤية. تاريخيًّا، كانت الدالةُ FF تُحدَّد بقواعدَ أو صيغَ تُظهر كيفيةَ اشتقاق المُخرَج من المُدخَل. وكما نناقش بعمقٍ أكبر في الشمولية وغياب قابلية الحساب، فإنّ هذه الفكرةَ غيرَ الرسمية نسبيًّا للدالة بدأت في القرن التاسع عشر «تتنهّك عند دروزها»، إلى أن انتهى الرياضيّون في النهاية إلى تعريفٍ أكثر صرامة للدالة بوصفه إسنادًا اعتباطيًّا لكلّ مُدخَلٍ مُخرَجًا. ولأنّ كثيرًا من الدوال قد توصَف (أو تُحسب) بصيغةٍ واحدة أو أكثر، فإنّنا لا نعدّ اليوم هذه خاصيةً جوهريّة للدوال، ونسمح أيضًا بدوالٍ لا تقابل أيّ «صيغةٍ حسنة».

لقد ذكرنا أنّ جميع تمثيلات الأعداد الحقيقية هي بطبيعتها تقريبية (approximate). ومن ثمّ فإنّ من المهمّ أن نفهم ما هي الضماناتُ التي يمكننا تقديمُها على جودة تقريب مُخرَج الخوارزمية، بدلالة جودة تقريب مُدخَلاتها. ويُعرف هذا السؤال بسؤال تحديد الاستقرار العددي لمعادلاتٍ معطاة. يتضمّن موقع Floating-Point Guide وصفًا وافيًا لتمثيل الفاصلة العائمة، وكذلك الطرقَ الكثيرة التي قد يفشل فيها بخفّة، وانظر أيضًا الموقع 0.30000000000000004.com.

يعطي داوبن [@Dauben90cantor] سيرةً ذاتيةً لكانْتور مع تركيزٍ على تطوّر أفكاره الرياضية. و[@halmos1960naive] كتابٌ كلاسيكيٌّ في نظرية المجموعات يتضمّن أيضًا مبرهنةَ كانْتور. وتُشرح مبرهنةُ كانْتور أيضًا في كثيرٍ من كتب الرياضيات المتقطّعة، ومنها [@LehmanLeightonMeyer, @LewisZax19].

تمثيلُ الرسوم البيانية بمصفوفة التجاور ليس مجرّد طريقةٍ مريحةٍ لإسقاط رسمٍ بيانيٍّ في سلسلة ثنائية، بل يتبيّن أنّ كثيرًا من المفاهيم والعمليات الطبيعية على المصفوفات مفيدةٌ للرسوم البيانية أيضًا. (فمثلًا، تعتمد خوارزميةُ PageRank من Google على هذه الرؤية.) وتُعدّ ملاحظاتُ مدرّس سبيلمان مصدرًا ممتازًا لهذا المجال، المعروف بـ_نظرية الرسوم البيانية الطيفية_ (spectral graph theory). وسنعود إلى هذه الرؤية بعد وقتٍ طويلٍ في هذا الكتاب حين نتحدّث عن المسارات العشوائية (random walks).


  1. كان البابليون قد اخترعوا نظامًا موضعيًّا قبل ذلك بزمنٍ بعيد، أمّا النظام العشري الموضعيّ الذي نستعمله اليوم فقد اخترعه علماء الرياضيات الهنود نحو القرن الثالث، وأخذ به علماء الرياضيات العرب في القرن الثامن، ولم يحظَ باهتمامٍ في أوروبا إلا مع نشر كتاب "Liber Abaci" عام 1202 تأليفًا لليوناردو من بيزا، المعروف أيضًا بفيبوناتشي، ولم يزيح الأرقام الرومانية من الاستعمال الشائع إلا في القرن الخامس عشر. ↩︎

  2. RtSRtS اختصارٌ لعبارة «الأعداد الحقيقية في سلاسل». ↩︎

  3. FtSFtS اختصارٌ لعبارة «الدوال في سلاسل». ↩︎

  4. FtRFtR اختصارٌ لعبارة «الدوال في أعداد حقيقية». ↩︎

  5. في الحقيقة، فإنّ تلك الشركة الخيالية بعينها تستعمل مقياسًا يركّز على سرعةِ الضغط أكثر من نسبتِه، انظر هنا وهنا. ↩︎

  6. تلميح: فكّر تعاُديًّا في كيفية تمثيل طول السلسلة. ↩︎