یک String در Julia دنبالهای مرتب از نویسهها است. همانطور که در راهنما آمده است: «البته، دردسر واقعی هنگامی آغاز میشود که بپرسید نویسه چیست.»
بخش زیادی از این پیچیدگی در مفهوم Chars با جزئیات بیشتری بررسی میشود، اما بهطور خلاصه:
بهطور کلی، رشتهها داخل کوتیشن دوتایی " " قرار میگیرند.
کوتیشن تک ' ' فقط برای Chars به کار میرود.
همچنین میتوان از کوتیشن دوتایی سهگانه """ """ استفاده کرد که به شما اجازه میدهد " را بدون escape درون رشته به کار ببرید.
مهمتر از آن، تورفتگی اضافی ناخواسته از رشتههای چندخطی حذف میشود.
julia> """
Multiline
"""
"Multiline\nString\n"
مانند بیشتر زبانها از C به بعد، Julia برای escape کردن از بکاسلش \ استفاده میکند:
\n (خط جدید) در مثال قبلی.\$ (به پایین نگاه کنید).در اصل، میتوان رشتهها را دقیقاً مثل بردارها اندیسگذاری کرد:
julia> s[1], s[5]
('J', 'a')
این کار برای یک رشتهی ASCII مثل «Julia» کاملاً جواب میدهد، اما بیشتر زبانهای جهان را نادیده میگیرد.
یکی از نویسههای Beowulf را در نظر بگیرید که حدود ۱۵ قرن پیش به انگلیسی باستان نوشته شده است.
«Hrōðgār» بهوضوح ۷ نویسه است، اما همهشان 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 ممکن است برای نمایش تا ۴ بایت لازم داشته باشد، و مفهوم 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"
پرانتزها هنگامی لازماند که حذفشان موجب ابهام شود، اما برای یک شناسهی تنها که بعد از آن فاصله میآید اختیاریاند.
برای اینکه $ را در رشته بگنجانید، آن را بهصورت \$ escape کنید.
به این محدودیت توجه کنید که درج مقدار فعلاً قالببندی را ممکن نمیکند، مثلاً تعیین تعداد ارقام اعشاری برای نمایش.
@sprintf()برای کنترل دقیقتر بر ساخت رشتهها، Julia تابع sprintf را از C (و چندین زبان بعدی: Wikipedia حدود ۳۰ مورد را فهرست میکند) کپی کرده است.
در 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"
این مثالها یک رشته را به رشتههای کوچکتری تقسیم میکنند. تقسیم یک رشته به نویسهها عملیاتی متفاوت است.
آیا یک رشته شامل زیررشتهی مشخصی است؟
بهطرز عجیبی، دو تابع برای این آزمایش وجود دارد: 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
برای به دست آوردن اندیسهای شروع تا پایانِ یک زیررشته، چندین تابع داریم:
findfirst()findlast()findprev()findnext()julia> findfirst(needle, haystack)
9:13
برای جایگزینی زیررشتهها، تغییرها را با نحوهی نگارش x => y فهرست کنید.
julia> replace("abc", "a"=>"b", "b"=>"c", "c"=>"a")
"bca"
از Julia ۱.۷ به بعد، تابع 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 "
در این تمرین شما خطوط لاگ را پردازش میکنید.
هر خط لاگ رشتهای است با این قالب: "[<LEVEL>]: <MESSAGE>".
سه سطح لاگ متفاوت وجود دارد:
INFOWARNINGERRORسه کار دارید که هرکدام یک خط لاگ میگیرد و از شما میخواهد کاری با آن انجام دهید.
تابع message را طوری پیادهسازی کنید که پیام یک خط لاگ را برگرداند:
julia> message("[ERROR]: Invalid operation")
"Invalid operation"
هر فاصلهی سفید ابتدایی یا انتهایی باید حذف شود:
julia> message("[WARNING]: Disk almost full\r\n")
"Disk almost full"
تابع log_level را طوری پیادهسازی کنید که سطح لاگ یک خط لاگ را برگرداند؛ این مقدار باید با حروف کوچک برگردانده شود:
julia> log_level("[ERROR]: Invalid operation")
"error"
تابع reformat را پیادهسازی کنید که خط لاگ را دوباره قالببندی میکند؛ به این صورت که پیام را اول و سطح لاگ را بعد از آن داخل پرانتز قرار میدهد:
julia> reformat("[INFO]: Operation completed")
"Operation completed (info)"
توجه: همهی رشتهها در این تمرین انگلیسی هستند و به مجموعهی نویسههای ASCII محدود میشوند. مفاهیم بعدی فرصتی فراهم میکنند تا با نویسههای یونیکد کار کنید.