String في Julia هي سلسلة نصية مرتّبة من المحارف. وكما يشير الدليل: "بالطبع، تكمن المشكلة الحقيقية عندما يسأل المرء عمّا هو المحرف."
سيُغطّى الكثير من هذا التعقيد بمزيد من التفصيل في مفهوم Chars، لكن باختصار:
عمومًا، تُحاط السلاسل النصية بعلامتي تنصيص مزدوجتين " ".
أما علامتا التنصيص المفردتان ' ' فتُستخدمان فقط مع Chars.
ويمكن أيضًا استخدام ثلاث علامات تنصيص مزدوجة """ """، مما يسمح باستخدام " داخل السلسلة النصية دون الحاجة إلى الهروب.
والأهم من ذلك، تُزال المسافات البادئة الزائدة غير المرغوبة من السلاسل النصية متعددة الأسطر.
julia> """
Multiline
String
"""
"Multiline\nString\n"
مثل معظم اللغات بدءًا من C، تستخدم Julia الشرطة المائلة العكسية \ للهروب:
\n في المثال السابق.\$ (انظر أدناه).السلسلة النصية مجموعة قابلة للتكرار، لذا تعمل حلقة 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().@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 وتعمل على مستوى البايت.