درس ۵ از ۴۹ · آغاز راه

رشته‌ها و متن

رشته‌ها متن‌اند و سلام از پایه با یونیکد کار می‌کند؛ پس متن فارسی مثل هر متن دیگری رفتار می‌کند. تنها نکته‌ای که باید همیشه در ذهن داشت این است که رشته به شکل بایت‌های UTF-8 نگه داشته می‌شود و بعضی کارها بایت می‌شمارند و بعضی نویسه.

شکل‌های نوشتن رشته

  • "..." رشته‌ی عادی است. نویسه‌های گریز در آن کار می‌کنند: \n (خط تازه)، \t (جهش)، \"، \\ و \uHHHH برای نوشتن هر نویسه‌ی یونیکد با کدش.
  • `...` (بک‌تیک) رشته‌ی خام است: هر چه درونش باشد، از بک‌اسلش تا نقل‌قول، همان‌طور که نوشته شده می‌ماند و می‌تواند چندخطی باشد. برای مسیر پرونده، عبارت باقاعده و جیسون ساده‌ترین راه است.
  • """...""" رشته‌ی چندخطی است و نویسه‌های گریز در آن هم کار می‌کنند.
  • «...» همان رشته‌ی عادی است و تنها در پرونده‌های فارسی پذیرفته می‌شود. نویسه‌های گریز در آن هم می‌آیند و برای نوشتن خود گیومه‌ی بسته باید آن را با \» گریز داد.
str-literals.salam
تابع ریشه:
    سرچاپ "خط اول\nخط دوم"
    سرچاپ "ستون\tستون"
    سرچاپ "او گفت: \"سلام\""
    سرچاپ "گریز یونیکد: \u0633\u0644\u0627\u0645"
    سرچاپ `مسیر C:\temp و "نقل‌قول" بدون گریز`
    شعر := """بنی آدم اعضای یکدیگرند
که در آفرینش ز یک گوهرند"""
    سرچاپ شعر
    سرچاپ «گیومه‌ی فارسی، با گریز \» درونش»
پایان
خروجیخط اول خط دوم ستون ستون او گفت: "سلام" گریز یونیکد: سلام مسیر C:\temp و "نقل‌قول" بدون گریز بنی آدم اعضای یکدیگرند که در آفرینش ز یک گوهرند گیومه‌ی فارسی، با گریز » درونش

رشته‌ی عادی "..." باید در همان خطی که شروع شده تمام شود؛ برای متن چندخطی از """ یا بک‌تیک استفاده کنید.

چسباندن رشته‌ها

رشته‌ها را با + به هم بچسبانید. اگر یک طرف عدد یا منطقی باشد، خودکار به متن تبدیل می‌شود و به تبدیل جداگانه نیازی نیست. += متنی را به انتهای یک رشته‌ی ناپایا می‌افزاید. سلام جاگذاری از نوع "{نام}" ندارد و متن را با همین + می‌سازیم.

str-concat.salam
تابع ریشه:
    نام := "آرش"
    سن := ۲۵
    پیام := "سلام " + نام + "، شما " + سن + " سال دارید."
    سرچاپ پیام
    سرچاپ "جمع: " + ۲ + ۳
    سرچاپ "جمع: " + (۲ + ۳)
    سرچاپ "بها " + ۲.۵ + "، پرداخت شد: " + درست
    ناپایا گزارش := "آغاز شد"
    گزارش += "، وصل شد"
    گزارش += "، " + ۳ + " پرونده فرستاده شد"
    سرچاپ گزارش
پایان
خروجیسلام آرش، شما 25 سال دارید. جمع: 23 جمع: 5 بها 2.5، پرداخت شد: true آغاز شد، وصل شد، 3 پرونده فرستاده شد

به خط دوم خروجی دقت کنید. + از چپ به راست حساب می‌شود؛ پس "جمع: " + ۲ پیش از رسیدن به ۳ متن شده است و نتیجه جمع: 23 می‌شود. هر جا حاصل جمع را می‌خواهید، حساب را در پرانتز بگذارید.

بایت و نویسه

رشته به شکل UTF-8 نگه داشته می‌شود. هر حرف انگلیسی یک بایت جا می‌گیرد، اما هر حرف فارسی دو بایت. برای همین دو جور طول و دو جور جایگاه داریم:

  • طول()، زیررشته و بیاب بایت می‌شمارند. سریع‌اند و برای متن انگلیسی دقیق.
  • شمارنویسه()، زیررشته نویسه، نویسه شماره و بیاب نویسه نویسه می‌شمارند. برای متن فارسی همیشه این‌ها را به کار ببرید.
str-chars.salam
تابع پیش‌نمایش(عنوان: رشته، سقف: صحیح): رشته:
    اگر عنوان.شمارنویسه() <= سقف:
        برگشت عنوان
    پایان
    برگشت عنوان.زیررشته نویسه(۰، سقف) + "..."
پایان

تابع ریشه:
    سلام := "سلام"
    سرچاپ سلام.طول()، سلام.شمارنویسه()
    سرچاپ سلام.نویسه شماره(۰)، سلام.بیاب نویسه("م")، سلام.بیاب("م")
    واژه := "cafe"
    سرچاپ واژه[۰]، واژه[۰] برگردان صحیح
    سرچاپ پیش‌نمایش("عنوان کوتاه"، ۲۰)
    سرچاپ پیش‌نمایش("پیش‌بینی هوای تبریز برای آخر هفته"، ۲۰)
پایان
خروجی8 4 س 3 6 c 99 عنوان کوتاه پیش‌بینی هوای تبریز ...

"سلام" هشت بایت است اما چهار نویسه، و حرف م نویسه‌ی شماره‌ی ۳ است ولی از بایت ۶ شروع می‌شود. تابع پیش‌نمایش عنوان را به اندازه‌ی چند نویسه کوتاه می‌کند، یعنی همان چیزی که خواننده انتظار دارد. اگر همین کار را با زیررشته بکنید، ممکن است حرفی از وسط نصف شود و نشانه‌ای شکسته چاپ شود. دسترسی با واژه[۰] هم تنها یک بایت را به شکل نویسه می‌خواند و فقط برای متن انگلیسی درست است.

برای دیدن تک‌تک نویسه‌ها، روی خود رشته هر بزنید. هر ح در متن هر نویسه را به شکل یک رشتهی کوتاه می‌دهد، پس می‌شود ح را با "ی" مقایسه کرد یا یک‌راست چاپش کرد، و هر ش، ح در متن شماره‌ی نویسه را هم می‌دهد. رشته فقط یک بار از اول تا آخر خوانده می‌شود، که روی متن بلند بسیار سریع‌تر از صدا زدن نویسه شماره(ش) در یک حلقه‌ی تکرار است. اگر خود بایت‌ها را می‌خواهید، بنویسید هر ب در متن.بایت ها(): ب یک نویسه است و عدد کنارش جایگاه بایت.

str-each.salam
تابع ریشه:
    واژه := "سلام"
    هر ح در واژه:
        چاپ ح + " "
    پایان
    سرچاپ ""
    هر ش، ح در "café":
        سرچاپ ش، ح
    پایان
    ناپایا مصوت := 0
    هر ح در "Tabriz تبریز":
        اگر ح == "a" یا ح == "i" یا ح == "ی": مصوت++ پایان
    پایان
    سرچاپ "مصوت:"، مصوت
    هر ش، ب در "Hi".بایت ها():
        سرچاپ ش، ب برگردان صحیح
    پایان
پایان
خروجیس ل ا م 0 c 1 a 2 f 3 é مصوت: 3 0 72 1 105

هر نویسه در این‌جا یک نقطه‌ی کد یونیکد است. بیشتر حرف‌ها یک نقطه‌ی کدند، اما برخی نشانه‌ها که به چشم یکی می‌آیند، مثل دست تکان‌دادن با رنگ پوست، دو تا هستند؛ رشته.خوشه ها(متن) متن را به همین واحدهای دیدنی می‌شکند.

روش‌های رشته

این روش‌ها روی هر رشته‌ای در دسترس‌اند. هیچ‌کدام خود رشته را تغییر نمی‌دهند و مقدار تازه‌ای برمی‌گردانند.

str-methods.salam
تابع ریشه:
    متن := "  زبان برنامه‌نویسی سلام  "
    تمیز := متن.پیراست()
    سرچاپ تمیز
    سرچاپ "salam".طول(), "salam lang".زیررشته(۰، ۵)
    سرچاپ "salam lang".بیاب("lang")
    بخش‌ها := "سیب,انار,انگور".بشکاف(",")
    سرچاپ بخش‌ها.طول(), بخش‌ها.بگیر(۱)
    سرچاپ "سلام".پیوست("!")
    سرچاپ "گزارش.pdf".ختم با(".pdf")، "سلام دنیا".شروع با("سلام")، "سلام دنیا".دربردارد("م د")
    سرچاپ "۴۲".به صحیح() + ۱، "2.5".به اعشار() * ۲.۰
پایان
خروجیزبان برنامه‌نویسی سلام 5 salam 6 3 انار سلام! true true true 43 5
روشکار
طول()طول رشته به بایت
شمارنویسه()تعداد نویسه‌ها؛ برای متن فارسی با طول بایتی فرق دارد
زیررشته(آغاز، تعداد)بریدن تکه‌ای از رشته، با شمارش بایت
زیررشته نویسه(آغاز، تعداد)، نویسه شماره(ش)همان کار، با شمارش نویسه
بیاب(متن)، بیاب نویسه(متن)جایگاه یک متن (به بایت یا به نویسه)، یا -1
شروع با(متن)، ختم با(متن)، دربردارد(متن)آزمودن آغاز، پایان یا هر جای رشته
بشکاف(جداکننده)شکستن رشته به یک وکتور<رشته> از تکه‌ها
پیراست()حذف فاصله‌های آغاز و پایان
پیوست(متن)چسباندن متن به انتها
به صحیح()، به اعشار()تبدیل متن به عدد

رشته را هرگز نمی‌شود درجا تغییر داد و م[۰] = 'x' خطای کامپایل است. به‌جایش رشته‌ی تازه‌ای بسازید.

بسته‌ی رشته

برای کارهایی بیش از روش‌های بالا، بسته‌ی رشته را وارد کنید. این بسته متن را می‌شکافد و به هم می‌پیوندد، پیشوند و پسوند را برمی‌دارد، جایگزین می‌کند، می‌شمارد و بزرگی و کوچکی حروف لاتین را عوض می‌کند. مثال زیر یک خط تنظیمات را می‌خواند، از همان نوعی که در پرونده‌ی پیکربندی یا یک فرم پیدا می‌شود.

str-parse.salam
واردسازی رشته

تابع ریشه:
    خط := "نام = سارا؛ شهر = شیراز؛ زبان = فارسی"
    پاره‌ها := رشته.تفکیک کردن(خط، "؛")
    دیرکن پاره‌ها.آزادکن()
    هر پاره در پاره‌ها:
        جفت := رشته.تفکیک کردن(پاره، "=")
        سرچاپ رشته.پیرایش(جفت.بگیر(۰)) + ":"، رشته.پیرایش(جفت.بگیر(۱))
        جفت.آزادکن()
    پایان
    مسیر := "/home/sara/notes/todo.txt"
    سرچاپ رشته.حذف پیشوند(مسیر، "/home/sara/")، رشته.آخرین اندیس(مسیر، "/")
    سرچاپ رشته.جایگزینی("۱۴۰۵-۰۷-۱۵"، "-"، "/")، رشته.شمارش("بادبادک"، "با")
    واژه‌ها := رشته.واژه ها("  فاصله‌های    زیادی   این‌جاست  ")
    دیرکن واژه‌ها.آزادکن()
    سرچاپ رشته.بپیوند(واژه‌ها، " ")
    سرچاپ رشته.بزرگ("salam")، رشته.شامل است(مسیر، "notes")
پایان
خروجینام: سارا شهر: شیراز زبان: فارسی notes/todo.txt 16 ۱۴۰۵/۰۷/۱۵ 2 فاصله‌های زیادی این‌جاست SALAM true

تفکیک کردن و واژه ها یک وکتور برمی‌گردانند که باید آزاد شود؛ خط‌های دیرکن ... آزادکن() برای همین است. واژه ها روی هر رشته‌ای از فاصله‌ها می‌شکند، پس برای جدا کردن واژه‌هایی که کاربر تایپ کرده مناسب است.

ساختن متن تکه‌تکه

هر + رشته‌ی تازه‌ای می‌سازد. برای یکی دو خط اشکالی ندارد، اما متنی که در یک حلقه ساخته می‌شود بهتر است با سازنده‌ی رشته ساخته شود: رشته.سازنده یکی می‌سازد، رشته.افزودن به بافر متن می‌افزاید، رشته.رشته بافر نتیجه را می‌دهد و رشته.آزادسازی بافر آن را آزاد می‌کند. پرکردن چپ و پرکردن راست از بسته‌ی قالب بندی متن را تا پهنای معینی (به نویسه) پر می‌کنند و ستون‌های این رسید را هم‌تراز نگه می‌دارند.

str-builder.salam
واردسازی رشته
واردسازی قالب بندی

تابع ریشه:
    کالاها := ["چای"، "نان"، "پنیر"]
    بهاها := [۴۵، ۳۰، ۱۲۰]
    ناپایا ب := رشته.سازنده()
    دیرکن رشته.آزادسازی بافر(ب)
    ناپایا جمع := ۰
    هر ش، کالا در کالاها:
        رشته.افزودن به بافر(ب، قالب بندی.پرکردن راست(کالا، ۱۰))
        رشته.افزودن به بافر(ب، قالب بندی.پرکردن چپ("" + بهاها[ش]، ۶))
        رشته.افزودن به بافر(ب، "\n")
        جمع += بهاها[ش]
    پایان
    رشته.افزودن به بافر(ب، رشته.تکرار("-"، ۱۶) + "\n")
    رشته.افزودن به بافر(ب، قالب بندی.پرکردن راست("جمع"، ۱۰))
    رشته.افزودن به بافر(ب، قالب بندی.پرکردن چپ("" + جمع، ۶))
    سرچاپ رشته.رشته بافر(ب)
پایان
خروجیچای 45 نان 30 پنیر 120 ---------------- جمع 195

مقایسه‌ی رشته‌ها

== و != محتوای دو رشته را می‌سنجند و < و > ترتیبشان را. رشته.مقایسه یکی از -1، 0 یا 1 را برمی‌گرداند، همان چیزی که یک تابع مرتب‌سازی لازم دارد. رشته.یکسان بدون حساسیت بزرگی و کوچکی حروف لاتین را نادیده می‌گیرد و برای نام کاربری و نشانی ایمیل همین را می‌خواهید.

str-compare.salam
واردسازی رشته

تابع ریشه:
    سرچاپ "سیب" == "سیب"، "apple" < "banana"، "Zebra" < "apple"
    سرچاپ رشته.مقایسه("انار"، "سیب")، رشته.مقایسه("به"، "به")، رشته.مقایسه("سیب"، "انار")
    نوشته := "Sara"
    ذخیره := "SARA"
    سرچاپ نوشته == ذخیره، رشته.یکسان بدون حساسیت(نوشته، ذخیره)
    سرچاپ "گل" < "لاله"، "بابا" < "پدر"
پایان
خروجیtrue true true -1 0 1 false true false true

ترتیب، ترتیب کد نویسه‌هاست، نه ترتیب الفبا. حرف‌های پ چ ژ گ کدی بزرگ‌تر از بقیه‌ی الفبا دارند؛ پس "گل" < "لاله" نادرست است، با این‌که در الفبای فارسی گ پیش از ل می‌آید. در عوض "بابا" < "پدر" درست درمی‌آید، چون کد ب از پ کوچک‌تر است. در انگلیسی هم همه‌ی حروف بزرگ پیش از حروف کوچک‌اند و "Zebra" < "apple" درست است.

متن و عدد

به صحیح() و به اعشار() عدد را از آغاز رشته می‌خوانند و رقم‌های فارسی و عربی را هم مثل رقم‌های انگلیسی می‌پذیرند. این دو هیچ‌وقت خطا گزارش نمی‌کنند: به نخستین نویسه‌ای که جزو عدد نیست که برسند می‌ایستند و متنی که اصلا عددی ندارد 0 می‌دهد. پس اگر متن از کاربر می‌آید، اول آن را وارسی کنید. مثال زیر شماره‌تلفنی را که با رقم فارسی تایپ شده تمیز می‌کند و بعد می‌سنجد که جز رقم چیزی در آن نمانده باشد.

str-digits.salam
واردسازی رشته

تابع پاک‌سازی تلفن(خام: رشته): رشته:
    لاتین := رشته.به رقم انگلیسی(خام)
    برگشت رشته.جایگزینی(رشته.جایگزینی(لاتین، " "، "")، "-"، "")
پایان

تابع همه رقم(م: رشته): منطقی:
    اگر م.طول() == ۰:
        برگشت نادرست
    پایان
    کدها := رشته.کدنقطه ها(م)
    دیرکن کدها.آزادکن()
    هر ک در کدها:
        اگر وارونه رشته.رقم اسکی است(ک):
            برگشت نادرست
        پایان
    پایان
    برگشت درست
پایان

تابع ریشه:
    تلفن := پاک‌سازی تلفن("۰۹۱۲ ۳۴۵-۶۷۸۹")
    سرچاپ تلفن، تلفن.طول()، همه رقم(تلفن)
    سرچاپ همه رقم(پاک‌سازی تلفن("۰۹۱۲ ۳۴x ۶۷۸۹"))
    سرچاپ "۲۵۰۰۰".به صحیح() + ۱، "۱۲الف".به صحیح()، "الف".به صحیح()
    سرچاپ رشته.به رقم فارسی("فاکتور " + ۱۴۰۵ + "، جمع " + ۲۵۰۰۰ + " تومان")
پایان
خروجی09123456789 11 true false 25001 12 0 فاکتور ۱۴۰۵، جمع ۲۵۰۰۰ تومان

رشته.به رقم انگلیسی و رشته.به رقم فارسی رقم‌های درون رشته را عوض می‌کنند و به بقیه‌ی متن دست نمی‌زنند. سلام عددها را همیشه با رقم انگلیسی چاپ می‌کند؛ پس وقتی خروجی برای خواننده‌ی فارسی‌زبان است، به رقم فارسی آخرین گام است.

نکته‌هایی که باید حواستان باشد

  • طول() بایت می‌شمارد. برای هر چیزی که آدم‌ها می‌خوانند (سقف تعداد حروف، پهنای ستون، پیش‌نمایش) شمارنویسه() و روش‌های نویسه‌ای را به کار ببرید.
  • + از چپ به راست پیش می‌رود: "ن" + ۳ + ۴ می‌شود "ن34" و ۳ + ۴ + "ن" می‌شود "7ن".
  • "۱۲الف".به صحیح() عدد 12 است و "الف".به صحیح() عدد 0؛ هیچ‌کدام نمی‌گویند ورودی ایراد داشته است.
  • رشته در نخستین بایت صفر تمام می‌شود: "a\0b".طول() برابر 1 است. داده‌ی دودویی را در بافر بایتی نگه دارید، نه در رشته.
  • ترتیب با < از کد نویسه‌ها پیروی می‌کند، نه از الفبای هیچ زبانی.

فهرست کامل تابع‌های متنی، از دسته‌بندی نویسه‌های یونیکد تا یکسان‌سازی و خوشه‌های نویسه، در صفحه‌ی رشته در مرجع کتابخانه آمده است.