یک String در Julia دنبالهای مرتب از نویسهها است. همانطور که راهنما اشاره میکند: «البته، دردسر واقعی وقتی شروع میشود که کسی بپرسد نویسه چیست.»
بیشتر این پیچیدگی در ادامه در مفهوم Chars با جزئیات بیشتری بررسی میشود، اما بهطور خلاصه:
بهطور کلی رشتهها در گیومهی دوگانهی " " قرار میگیرند.
گیومهی تک ' ' فقط برای Chars استفاده میشود.
همچنین میتوان از سه گیومهی دوگانهی """ """ استفاده کرد که به کار بردن " درون رشته را بدون escaping ممکن میکند.
مهمتر اینکه، تورفتگی اضافی ناخواسته از رشتههای چندخطی حذف میشود.
julia> """
Multiline
String
"""
"Multiline\nString\n"
مانند بیشتر زبانها از C به بعد، Julia برای escaping از بکاسلش \ استفاده میکند:
\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" کار میکند، اما این بیشتر زبانهای جهان را نادیده میگیرد.
یکی از نویسههای بیوولف را در نظر بگیرید، که حدود ۱۵ قرن پیش به انگلیسی باستان نوشته شده است.
"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 رابطهی یکبهیک با نویسهها ندارند.
یک نویسهی یونیکد/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"
پرانتزها وقتی حذفشان ابهام ایجاد میکند الزامیاند، اما برای یک شناسهی تنها که پس از آن فاصلهی خالی میآید اختیاریاند.
برای درج یک $ در رشته، آن را بهصورت \$ اسکیپ کنید.
به این محدودیت توجه کنید که درونیابی در حال حاضر قالببندی را ممکن نمیکند، مانند تعداد رقمهای اعشار برای نمایش.
راههای دور زدن این محدودیت عبارتاند از:
round().@sprintf() در ادامه توضیح داده میشود.@sprintf()برای کنترل دقیقتر بر سرهمبندی رشته، Julia تابع sprintf را از C کپی کرده است (و از چند زبان بعدی: ویکیپدیا حدود ۳۰ مورد را فهرست میکند).
در 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 "
تبدیل متقابل اعداد و نمایش رشتهای آنها اغلب مفید است.
تبدیل عدد به رشته ساده است، هرچند مبنایی غیر از مبنای پیشفرض ۱۰ به یک پارامتر اضافی نیاز دارد:
julia> string(42)
"42"
julia> string(42, base=16)
"2a"
julia> string(42, base=2)
"101010"
تجزیهی یک رشته برای گرفتن یک مقدار عددی کمی پیچیدهتر است و احتمال خطای بیشتری دارد. مشخص کردن نوع عددی هدف الزامی است. مشخص کردن مبنا اختیاری است و بهطور پیشفرض ۱۰ است.
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
گاهی مفید است که درونیابی و escaping معمول درون رشتهها را سرکوب کنیم.
برای این کار، گیومهی آغازین را با 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" " برای Regex و s" " برای کار با قطعههای گرفتهشده.
انواع دیگر رشتههای ویژه در برنامهنویسی به سبک Exercism کمتر رایجاند، هرچند در ساخت کتابخانهها بهطور گسترده استفاده میشوند.
v"x.y.z" شمارهگذاری major.minor.patch را برای انتشارهای نرمافزاری مدیریت میکنند.b" " مرزهای نویسهی یونیکد را نادیده میگیرند و در سطح بایت عمل میکنند.