المسارات
/
Julia
Julia
/
المنهج
/
السلاسل النصية
ال

السلاسل النصية في Julia

7 تمارين

نبذة عن السلاسل النصية

String في Julia هي سلسلة نصية مرتّبة من المحارف. وكما يشير الدليل: "بالطبع، تكمن المشكلة الحقيقية عندما يسأل المرء عمّا هو المحرف."

سيُغطّى الكثير من هذا التعقيد بمزيد من التفصيل في مفهوم Chars، لكن باختصار:

  • كل السلاسل النصية في Julia هي Unicode.
  • السلاسل النصية التي تصادف أنها ASCII أيضًا (الحروف الإنجليزية من A إلى Z بحالتيها الكبيرة والصغيرة، والأرقام، وقليل من علامات الترقيم) تكون سهلة في التعامل.
  • معظم أنظمة الكتابة الأخرى في العالم ممكنة التعامل، لكنها تحتاج إلى مزيد من العناية.

السلاسل النصية الحرفية

عمومًا، تُحاط السلاسل النصية بعلامتي تنصيص مزدوجتين " ". أما علامتا التنصيص المفردتان ' ' فتُستخدمان فقط مع Chars.

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

julia> """
       Multiline
       String
       """
"Multiline\nString\n"

هروب المحارف

مثل معظم اللغات بدءًا من C، تستخدم Julia الشرطة المائلة العكسية \ للهروب:

  1. لإدخال محارف لا تُطبع، مثل محرف السطر الجديد \n في المثال السابق.
  2. لحماية محارف قد يكون لها معنى خاص بخلاف ذلك، مثل \$ (انظر أدناه).

التكرار

السلسلة النصية مجموعة قابلة للتكرار، لذا تعمل حلقة for ... in دون أي حاجة إلى التحويل إلى مصفوفة من المحارف.

julia> s = "Julia"
"Julia"

# admittedly, this is a silly example
julia> for c in s; print(c, "; "); end
J; u; l; i; a; 

للتكرار بهذه الطريقة ميزة أن Julia تتعامل مع حدود المحارف بشكل صحيح، حتى مع السلاسل النصية غير ASCII. ستتضح أهمية ذلك أكثر في القسم التالي.

الفهرسة

من حيث المبدأ، يمكن فهرسة السلاسل النصية تمامًا كما تُفهرس المصفوفات:

julia> s[1], s[5]
('J', 'a')

يعمل هذا بوضوح مع سلسلة نصية من نوع ASCII مثل "Julia"، لكن ذلك يتجاهل معظم لغات العالم.

لنتأمل أحد المحارف في Beowulf، المكتوب بالإنجليزية القديمة قبل نحو 15 قرنًا.

"Hrōðgār" تتكوّن بوضوح من 7 محارف، لكن ليست كلها من نوع ASCII.

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> isascii(king)
false

# collect() converts to a Char vector:
julia> collect(king)
7-element Vector{Char}:
 'H': ASCII/Unicode U+0048 (category Lu: Letter, uppercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'ō': Unicode U+014D (category Ll: Letter, lowercase)
 'ð': Unicode U+00F0 (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)
 'ā': Unicode U+0101 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)

تبدأ الفهرسة في سلسلة نصية كهذه بشكل جيد، لكنها تنهار عند نقطة ما:

julia> king[3]
'ō': Unicode U+014D (category Ll: Letter, lowercase)

julia> king[4]
ERROR: StringIndexError: invalid index [4], valid nearby indices [3]=>'ō', [5]=>'ð'

المشكلة أن الفهرسة بهذه الطريقة تعدّ البايتات، وهي لا تكون لها علاقة واحد إلى واحد مع المحارف في السلاسل النصية غير ASCII. قد يحتاج المحرف الواحد في Unicode/UTF-8 إلى ما يصل إلى 4 بايتات لتمثيله، وسيتناول مفهوم Chars هذا بمزيد من التفصيل.

بناء السلاسل النصية

الدمج

تستخدم لغات كثيرة + كعامل لدمج السلاسل النصية، لكن Julia ليست منها. على الأقل رسالة الخطأ مفيدة.

julia> "s1 " + "s2"
ERROR: MethodError: no method matching +(::String, ::String)
The function `+` exists, but no method is defined for this combination of argument types.
String concatenation is performed with * 

julia> "s1 " * "s2"
"s1 s2"

تُدمج دالة string() عددًا أيًّا من السلاسل النصية.

julia> string("lots ", "of ", "bits")
"lots of bits"

تفعل دالة join() الشيء نفسه، كما تأخذ مصفوفة من السلاسل النصية وتدمجها بفاصل معيّن (قيمته الافتراضية "").

julia> join(["lots", "of", "bits"], "_")
"lots_of_bits"

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

الإعادة

ببساطة:

julia> repeat("Abc", 5)
"AbcAbcAbcAbcAbc"

الإدراج

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

في Julia، تُسبق القيم المُدرجة بالرمز $.

julia> myvar = 42
42

julia> "myvar evaluates to $myvar"
"myvar evaluates to 42"

julia> r = 5.3
5.3

julia> "A circle of radius $r has area $(π * r^2)"
"A circle of radius 5.3 has area 88.24733763933729"

الأقواس الهلالية مطلوبة عندما يكون حذفها مُلبِسًا، لكنها اختيارية مع معرّف واحد يتبعه فراغ.

ولتضمين $ في السلسلة النصية، اكتبه بصيغة الهروب \$.

لاحظ القيد القائل إن الإدراج لا يسمح حاليًا بالتنسيق، مثل تحديد عدد المنازل العشرية المعروضة.

من بين الحلول البديلة:

  • لفّ الحساب داخل دالة round().
  • خارج مشغّل الاختبارات في Exercism، تحاكي حزمة PyFormattedStrings سلاسل f في Python.
  • ماكرو @sprintf() موصوف أدناه.

@sprintf()

للحصول على تحكم أدق في تجميع السلاسل النصية، نقلت Julia دالة sprintf من لغة C (وعدة لغات لاحقة: ويكيبيديا تسرد نحو 30 منها).

وفي Julia، يُنفَّذ هذا على هيئة macro داخل وحدة Printf، ومن هنا جاءت البادئة @.

julia> using Printf

julia> r = 5.3
5.3

julia> @sprintf("A circle of radius %.1f has area %.2f", r, π * r^2)
"A circle of radius 5.3 has area 88.25"

دوال السلاسل النصية

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

الطول

ما طول السلسلة النصية؟ أحيانًا يعتمد الأمر على طريقة العدّ.

الحالة البسيطة هي السلاسل النصية من نوع ASCII، حيث يشغل كل محرف بايتًا واحدًا.

julia> string_asc = "Julia"
"Julia"

julia> length(string_asc)
5

julia> sizeof(string_asc)
5

تُرجع دالة length() عدد المحارف، وتُرجع دالة sizeof() عدد البايتات.

يصبح هذا التمييز مهمًا مع مجموعات المحارف الأخرى:

julia> king = "Hrōðgār"
"Hrōðgār"

julia> length(king)
7

julia> sizeof(king)
10

التقسيم

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

وبشكل عام، نستخدم دالة split(). الفاصل الافتراضي هو (أي) مسافة بيضاء، لكن يمكن تحديد فاصل آخر.

julia> split("my little string")
3-element Vector{SubString{String}}:
 "my"
 "little"
 "string"

julia> split("My_snake_case_string", "_")
4-element Vector{SubString{String}}:
 "My"
 "snake"
 "case"
 "string"

تُقسّم هذه الأمثلة سلسلة نصية إلى سلاسل نصية أصغر. أما تقسيم سلسلة نصية إلى محارف فهو عملية مختلفة.

julia> collect("input string")
12-element Vector{Char}:
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
 'p': ASCII/Unicode U+0070 (category Ll: Letter, lowercase)
 'u': ASCII/Unicode U+0075 (category Ll: Letter, lowercase)
 't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
 ' ': ASCII/Unicode U+0020 (category Zs: Separator, space)
 's': ASCII/Unicode U+0073 (category Ll: Letter, lowercase)
 't': ASCII/Unicode U+0074 (category Ll: Letter, lowercase)
 'r': ASCII/Unicode U+0072 (category Ll: Letter, lowercase)
 'i': ASCII/Unicode U+0069 (category Ll: Letter, lowercase)
 'n': ASCII/Unicode U+006E (category Ll: Letter, lowercase)
 'g': ASCII/Unicode U+0067 (category Ll: Letter, lowercase)

البحث والاستبدال

هل تحتوي سلسلة نصية على سلسلة نصية فرعية معيّنة؟ ومن الغريب أن هناك دالتين لاختبار ذلك: occursin() وcontains()، ولا تختلفان إلا في ترتيب وسائطهما.

julia> needle = "Julia"
"Julia"

julia> haystack = "Python, Julia and R"
"Python, Julia and R"

julia> occursin(needle, haystack)
true

julia> contains(haystack, needle)
true

ولتحديد الموضع بدقة أكبر:

julia> startswith(haystack, "Python")
true

julia> endswith(haystack, "Python")
false

للحصول على فهرسي البداية والنهاية لسلسلة نصية فرعية، لدينا عدة دوال:

julia> findfirst(needle, haystack)
9:13

لاستبدال السلاسل النصية الفرعية، اسرد التغييرات باستخدام الصيغة x => y.

julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"

منذ Julia 1.7، يمكن لدالة replace أن تأخذ أي عدد من التغييرات، مع ضمان ألّا يتغيّر أي محرف أكثر من مرة واحدة أثناء الاستبدال.

تُسمّى الصيغة x => y بـpair، وهو نوع مهم في Julia يُغطّى بمزيد من التفصيل في مفهوم Dicts and Pairs.

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

المسافات البيضاء

كثيرًا ما نحتاج إلى إزالة المسافات البيضاء من بداية السلسلة النصية و/أو نهايتها.

الدوال المخصصة لذلك هي lstrip() (من اليسار)، وrstrip() (من اليمين)، وstrip() (من الجهتين).

julia> strip("\n\t  useful_bit\n\n")
"useful_bit"

تغيير حالة الأحرف

أسماء هذه الدوال واضحة إلى حد كبير:

julia> s = repeat("aBcD ", 5)
"aBcD aBcD aBcD aBcD aBcD "

julia> uppercase(s)
"ABCD ABCD ABCD ABCD ABCD "

julia> lowercase(s)
"abcd abcd abcd abcd abcd "

julia> titlecase(s)  # Initial letter of each word uppercase
"Abcd Abcd Abcd Abcd Abcd "

التحليل

غالبًا ما يكون التحويل المتبادل بين الأعداد وتمثيلها النصي مفيدًا.

تحويل العدد إلى سلسلة نصية بسيط، لكن الأساس غير الافتراضي 10 يحتاج إلى معامل إضافي:

julia> string(42)
"42"

julia> string(42, base=16)
"2a"

julia> string(42, base=2)
"101010"

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

julia> parse(Int, "42")
42

julia> parse(Float64, "42")
42.0

julia> parse(Int, "42?")
ERROR: ArgumentError: invalid base 10 digit '?' in "42?"

julia> parse(Int, "42 ")  # whitespace may be tolerated
42

سلاسل نصية خاصة

أحيانًا يكون من المفيد تعطيل الإدراج والهروب المعتادين داخل السلاسل النصية. لذلك، اسبق علامة التنصيص الافتتاحية بـraw.

julia> x = 10
10

julia> "x = $x if we use \$ for interpolation"
"x = 10 if we use \$ for interpolation"

julia> raw"x = $x if we use \$ for interpolation"
"x = \$x if we use \\\$ for interpolation"

للتعبيرات النمطية صيغتها المعقدة الخاصة، وتوفّر Julia نوعين مفيدين من السلاسل النصية: r" " للتعبير النمطي وs" " للعمل مع الأجزاء الملتقطة.

هناك سلاسل نصية خاصة أخرى متنوعة أقل شيوعًا في البرمجة على طريقة Exercism، لكنها تُستخدم على نطاق واسع في إنشاء المكتبات.

  • تتعامل سلاسل الإصدارات الدلالية v"x.y.z" مع ترقيم major.minor.patch لإصدارات البرمجيات.
  • تتجاوز القيم الحرفية لمصفوفات البايتات b" " حدود محارف Unicode وتعمل على مستوى البايت.
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم السلاسل النصية

التدريب مقفل

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