رش

رشته در Julia

7 تمرین

درباره‌ی رشته

یک String در Julia دنباله‌ای مرتب از نویسه‌ها است. همان‌طور که راهنما اشاره می‌کند: «البته، دردسر واقعی وقتی شروع می‌شود که کسی بپرسد نویسه چیست.»

بیشتر این پیچیدگی در ادامه در مفهوم Chars با جزئیات بیشتری بررسی می‌شود، اما به‌طور خلاصه:

  • همه‌ی رشته‌ها در Julia یونیکد هستند.
  • رشته‌هایی که تصادفاً ASCII هم هستند (حروف انگلیسی A تا Z در حالت بزرگ و کوچک، ارقام و چند نویسه‌ی نقطه‌گذاری) کار کردن با آن‌ها آسان است.
  • کار کردن با بیشتر سایر نظام‌های نوشتاری جهان ممکن است، اما دقت بیشتری می‌طلبد.

لیترال‌های رشته‌ای

به‌طور کلی رشته‌ها در گیومه‌ی دوگانه‌ی " " قرار می‌گیرند. گیومه‌ی تک ' ' فقط برای Chars استفاده می‌شود.

همچنین می‌توان از سه گیومه‌ی دوگانه‌ی """ """ استفاده کرد که به کار بردن " درون رشته را بدون escaping ممکن می‌کند. مهم‌تر اینکه، تورفتگی اضافی ناخواسته از رشته‌های چندخطی حذف می‌شود.

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

اسکیپ کردن نویسه‌ها

مانند بیشتر زبان‌ها از C به بعد، Julia برای escaping از بک‌اسلش \ استفاده می‌کند:

  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" کار می‌کند، اما این بیشتر زبان‌های جهان را نادیده می‌گیرد.

یکی از نویسه‌های بیوولف را در نظر بگیرید، که حدود ۱۵ قرن پیش به انگلیسی باستان نوشته شده است.

"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().
  • بیرون از test runner در Exercism، بسته‌ی PyFormattedStrings رشته‌های f پایتون را شبیه‌سازی می‌کند.
  • ماکروی @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" " مرزهای نویسه‌ی یونیکد را نادیده می‌گیرند و در سطح بایت عمل می‌کنند.
ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود

رشته را یاد بگیرید

تمرین کردن قفل شده است

برای تمرین رشته قفل 2 تمرین دیگر را باز کنید