ال

المحارف في Julia

4 تمارين

نبذة عن المحارف

هذا موضوع كبير! من الممكن أن يُكتب فيه كتاب طويل، وقد فعل ذلك عدة أشخاص (ابحث في Amazon عن "unicode book" لترى بعض الأمثلة).

لمحة تاريخية موجزة

كان التعامل مع المحارف في الحواسيب أبسط بكثير في العقود الماضية، حين كان المبرمجون يفترضون أن الإنجليزية هي اللغة المهمة الوحيدة. إذن: 26 حرفًا بحالتها الكبيرة والصغيرة، و10 أرقام، وعدة علامات ترقيم، إضافة إلى رمز (0x07) لإصدار رنين، وقد اتّسع كل ذلك في 7 بتات: مجموعة محارف ASCII.

بطبيعة الحال، بدأ الناس يسألون عن à وä وŁ، ثم بدأ آخرون يسألون عن ऄ وஹ وญ، وأراد الشباب رموزًا تعبيرية 😱. فماذا نفعل؟

وللتحدث باختصار، اضطر كثير من الأذكياء والصابرين إلى العمل في لجان لسنوات، لوضع تفاصيل مجموعة محارف Unicode، وترميزات مثل UTF-8، واحتاج الكثير من البرمجيات إلى إعادة كتابة بالغة التعقيد. كما ظهرت الكثير من مشكلات Bug الجديدة.

ولمنع انهيار كل شيء، يضمن تصميم Unicode/UTF-8 أن أول 127 رمزًا مطابقة لـ ASCII (حتى جرس الرنين).

المحارف في Julia

تتمتع اللغات المصممة بعد عام 2005 تقريبًا بميزة ضخمة، هي أن معيار Unicode المستقر إلى حد معقول كان موجودًا بالفعل.

كانت Julia (صدرت لأول مرة عام 2012) قادرة على افتراض أن كل شيء سيكون Unicode: المحارف والسلاسل النصية وأسماء المتغيرات والدوال والعوامل الرياضية...

وللاقتباس من الدليل: "تجعل Julia التعامل مع نص ASCII العادي بسيطًا وفعّالًا، والتعامل مع Unicode بسيطًا وفعّالًا قدر الإمكان." لاحظ عبارة "قدر الإمكان"، فهي جزء مهم من العبارة.

تُكتب المحارف الحرفية بين علامتي تنصيص مفردتين، وتختلف عن السلاسل النصية التي تُكتب بين علامتي تنصيص مزدوجتين.

هذا واضح لمن يأتون من عالم C/C++، لكنه قد يربك مبرمجي Python وJavaScript.

julia> a = 'a'  # Roman alphabet
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

julia> typeof(a)
Char

julia> jha = 'झ'  # Devanagari alphabet
'झ': Unicode U+091D (category Lo: Letter, other)

julia> typeof(jha)
Char


julia> '❤'  # heart emoji
'❤': Unicode U+2764 (category So: Symbol, other)

نرى من الأمثلة أن النوع هو Char، ولدى Julia معلومات إضافية عن تصنيف المحرف.

وبنظرة أدق، يبدو أن هذه المحارف يمكن تمثيلها بأربعة أرقام سداسية عشرية. وتحتاج مجموعة المحارف الكاملة إلى ما يصل إلى 6 أرقام سداسية عشرية.

تُسمى هذه الأرقام "نقاط الكود"، وتمتد حاليًا من U+0000 إلى U+10FFFF. تظهر في REPL، لكن داخل الكود استخدم codepoints() للحصول عليها.

التحويل بين Char وInt بسيط:

julia> Int('a')
97

julia> Char(97)
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)

يسمح المترجم ب_بعض_ أشكال الحساب الصحيح على محارف Char:

julia> 'b' - 'a'  # interval, in alphabetic order
1

julia> 'b' + 'a'
ERROR: MethodError: no method matching +(::Char, ::Char)

julia> 'a' + 5
'f': ASCII/Unicode U+0066 (category Ll: Letter, lowercase)

julia> 'f' + ('A' - 'a')  # same as `uppercase('f')`
'F': ASCII/Unicode U+0046 (category Lu: Letter, uppercase)

دوال المحارف

يمكن لمجموعة فرعية من دوال معالجة السلاسل النصية العمل أيضًا على مُدخل من النوع Char.

  • في الأبجديات المناسبة، غيّر حالة الأحرف باستخدام uppercase() وlowercase().
  • اختبر حالة الأحرف باستخدام isuppercase() وislowercase().
  • اختبر نوع المحرف باستخدام:
    • isletter()، ويغطي أبجديات كثيرة
    • isdigit()، ويختبر النطاق 0:9 تحديدًا
    • isnumeric()، أوسع من isdigit، فيُرجع true للكسر ¾ ولمختلف الكتابات غير الأوروبية
    • isxdigit()، الأرقام السداسية عشرية
    • isascii()، محرف سابق لـ Unicode
    • ispunct()، علامات الترقيم
    • isspace()، أي محرف مسافة بيضاء
    • isprint()، المحارف القابلة للطباعة (وعكسها iscntrl())
islowercase('A')  # false
uppercase('γ')  # 'Γ': Unicode U+0393 (category Lu: Letter, uppercase)
ispunct('@')  # true
isdigit('A')  # false
isxdigit('A')  # true

للتحقق من وجود محرف في سلسلة نصية، لدينا in. لاحظ أن هذا يختلف عن السلاسل الفرعية:

# char search
julia> 'a' ∈ "xabcahliazlnkiw"
true

# substring search
julia> "a" ∈ "xabcahliazlnkiw"
use occursin(needle, haystack) for string containment

كما تتيح التعبيرات النمطية (موضوع مفهوم آخر) بحثًا ومعالجةً قويين.

التحويل المتبادل بين متجه Char والسلسلة النصية

للتحويل من سلسلة نصية إلى متجه Char، يمكننا استخدام collect().

وللتحويل من متجه Char إلى سلسلة نصية، هناك المنشئ String().

julia> s = "abcde"
"abcde"

julia> cv = collect(s)
5-element Vector{Char}:
 'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
 'b': ASCII/Unicode U+0062 (category Ll: Letter, lowercase)
 'c': ASCII/Unicode U+0063 (category Ll: Letter, lowercase)
 'd': ASCII/Unicode U+0064 (category Ll: Letter, lowercase)
 'e': ASCII/Unicode U+0065 (category Ll: Letter, lowercase)

julia> String(cv)
"abcde"

يعمل هذا مع أي محارف، وليس مع ASCII فقط.

julia> collect("❤,😱")
3-element Vector{Char}:
 '❤': Unicode U+2764 (category So: Symbol, other)
 ',': ASCII/Unicode U+002C (category Po: Punctuation, other)
 '😱': Unicode U+1F631 (category So: Symbol, other)

لاحظ أن المنشئ String() يعمل على متجه. ول_تحويل_ محرف Char واحد إلى سلسلة نصية من محرف واحد، الدالة هي string()، بحرف s صغير.

julia> string('a')
"a"

التخزين

كل ما ورد حتى الآن في هذا المستند يبدو بسيطًا نسبيًا، فهل لا يوجد حقًا ما يدعو للقلق؟

للأسف، هذا تفاؤل مفرط!

تأتي إحدى التعقيدات من الحاجة إلى "ما يصل إلى" 6 أرقام سداسية عشرية لكل نقطة كود. وهذا يعني أن محارف مختلفة تحتاج إلى مقادير مختلفة من المساحة في الذاكرة عند ترميزها بـ UTF-8.

لا يمكن للبايت أن يخزّن سوى أعداد (غير سالبة) حتى 255، أي رقمين سداسيَّي العشرية، لذلك يستخدم UTF-8 عددًا متغيّرًا من البايتات (من 1 إلى 4) لتخزين محرف Char. تُسمى هذه "وحدات الكود"، وستُرجع الدالة ncodeunits() العدد المطلوب لمحرف معيّن.

julia> codepoint(jha)  # jha 'झ' is defined in an earlier example
0x0000091d

julia> ncodeunits(jha)
3

julia> ncodeunits('a')  # ASCII character
1

julia> ncodeunits('😱')  # emoji
4

كما أن ليس كل ما يمكن عرضه على الشاشة له نقطة كود فريدة خاصة به. يُعدّ بعض المحارف المتميزة بصريًا مشتقًّا من محارف أخرى، لذلك يتعامل معها Unicode كمحرف أصل مع معدّل.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم المحارف

التدريب مقفل

افتح 3 تمارين إضافية لممارسة المحارف