«رشته» قطعهای از متن است که میان علامتهای نقلقول قرار میگیرد. هر نسخهی R که امروزه پشتیبانی میشود، رشتهها را با UTF-8 کدگذاری میکند، بنابراین میتوان بیشتر زبانهای جهان را پشتیبانی کرد.
mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"
متأسفانه، این اصطلاحات ممکن است برای برنامهنویسانی که با زبانهای دیگر آشنا هستند گیجکننده باشد.
هرچند type برابر character است، R میان یک حرف تکی (که در چند زبان دیگر char نامیده میشود، اما در R نه) و یک رشتهی طولانی هیچ تفاوتی نمیگذارد.
هر دو یک عنصر واحدند و میتوانند بهجای هم در گیومهی تکی ' ' یا دوگانه " " قرار بگیرند.
راهنمای سبک، استفاده از گیومههای دوگانه را توصیه میکند، مگر زمانی که بخواهید بدون escape کردن از گیومهی دوگانه درون رشته استفاده کنید.
str2 <- 'String with "quotes"' # convenience format
str2
#> [1] "String with \"quotes\"" # standard format
پس یک character vector برداری از رشتههاست که هر رشته یک عنصر واحد آن است.
s <- c("some", "strings")
s
#> [1] "some" "strings"
length(s)
#> [1] 2
length(s[1]) # the number of vector elements, not the string length
#> [1] 1
nchar(s[1]) # the number of characters in the string "some"
#> [1] 4
در مثال بالا، length(s[1]) معادل length("some") است: تعداد عنصرهای بردار، نه طول رشته.
بدیهی است که دستکاری یک رشتهی تکی ممکن است، همانطور که در مثال بالا با nchar() دیدید، اما به توابع خاصی نیاز دارد که در ادامه بحث میشوند.
بیشتر کارها در R پایه ممکن است، اما بسیاری از برنامهنویسان پشتیبانی آن از دستکاری رشته را نسبتاً محدود و گیجکننده میدانند.
خوشبختانه بستههای tidyverse وجود دارند که بسیار شهودیتر، انعطافپذیرتر و خودسازگارترند.
هنوز کد قدیمی زیادی وجود دارد که از توابع رشتهای سبک قدیم استفاده میکند. این شامل بیشتر راهحلهای انجمن در Exercism است.
علاوه بر این، برای کارهای کوچک ممکن است استفاده از توابع داخلی بهجای وارد کردن یک کتابخانهی بزرگ مفید باشد. نگهدارندگان ترک R هنگام نوشتن راهحلهای نمونه برای اعتبارسنجی تمرینها معمولاً همین رویکرد را در پیش میگیرند.
بنابراین، آشنایی با روشهای قدیمی هنوز ارزش یادگیری دارد.
ncharتعداد نویسههای یک رشته را برمیگرداند.
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
موضوع (پیچیدهی!) اینکه «نویسه» یعنی چه، خارج از حوزهی این بحث است. فقط به چند نکته در مثالهای بالا توجه کنید:
\0 پایان نمییابند (که ۱ واحد به طول اضافه میکرد).paste و paste0
تعداد دلخواهی از رشتهها را در یک رشتهی واحد ترکیب میکند.
بهصورت اختیاری میتوان برای paste یک جداکننده تعیین کرد: مقدار پیشفرض یک فاصله است.
paste0(v) معادل paste(v, sep = "") است.
paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintfبرای کنترل دقیقتر بر سرهمبندی رشتهها، R تابع sprintf را از C وام گرفته است (و چند زبان بعدی: ویکیپدیا فهرست حدود ۳۰ مورد را آورده است).
r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimwsفضای خالی ابتدا و/یا انتهای رشته را میتوان با trimws() حذف کرد:
s <- " messy string "
trimws(s)
#> [1] "messy string"
پارامتر which = "left" (یا "right") وجود دارد تا از حذف هر دو سر جلوگیری شود.
substrروی یک رشتهی تکی عمل میکند و زیررشتهای بین دو حد (شامل هر دو حد) را برمیگرداند.
substr("Exercism", 5, 7)
#> [1] "cis"
strsplitیک رشته را بر پایهی یک رشتهی جداکننده به یک لیست R تقسیم میکند. جداکننده الزامی است (پیشفرضی ندارد)، اما میتواند یک رشتهی خالی باشد.
strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R" "Python" "Julia"
strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
لیستها در یک مفهوم جداگانه پوشش داده میشوند.
اگر هنوز به آن بخش از برنامهی درسی نرسیدهاید، فعلاً همینقدر بدانید که تابع unlist() (در این مورد) یک لیست را به بردار تبدیل میکند.
unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"
اگر خوشبین باشیم، میتوانیم بگوییم توابع رشتهای سبک قدیم بهترین چیزی نیستند که R عرضه میکند (دیدگاههای کمخوشبینانهتری هم وجود دارد).
برای بهبود این وضعیت، کتابخانهی stringr از سال ۲۰۰۹ در حال توسعه بوده و اکنون بخش کلیدی مجموعهی Tidyverse است.
آن را با library(stringr) یا library(tidyverse) به فضای نام خود وارد کنید.
برخی از ویژگیهای کلیدی عبارتاند از:
str_ دارند و تلاش میکنند هدف را روشن کنند.مانند همهی بستههای Tidyverse، stringr مستندات خوبی دارد که معمولاً نویسنده(گان) بسته آن را نوشتهاند.
استفاده از پایپها |> برای اتصال توابع در بخشهای دیگر برنامهی درسی با جزئیات بیشتری بحث میشود.
فعلاً خوب است بدانید که:
مثال زیر این نحوهی نگارش را نشان میدهد. توابع تکتک در ادامه بحث میشوند.
library(stringr)
"Monday-25-February" |>
str_split("-") |>
unlist() |> # [1] "Monday" "25" "February"
str_sub(1, 3) |> # [1] "Mon" "25" "Feb"
str_to_upper() |> # [1] "MON" "25" "FEB"
str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
str_length نقاط کد را میشمارد (مشابه nchars).
توابع گوناگون دیگری فضای خالی را دستکاری میکنند، از جمله str_trim() و str_squish().
این اغلب کاری حیاتی برای پاکسازی در آغاز هر پروژهی علم داده است.
# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R")) #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right") # add spaces if necessary
#> [1] "Julia " "Python " "R "
str_trim("Julia Python R ") # remove leading & trailing whitespace
#> [1] "Julia Python R"
str_squish("Julia Python R ") # trim, collapse multiple spaces
#> [1] "Julia Python R"
تابع str_sub(string, start, end) زیررشته را از start تا end (شامل هر دو سر) برمیگرداند، در حالی که str_sub_all() میتواند روی چندین رشته عمل کند.
انعطاف زیادی وجود دارد.
مقدار پیشفرض start و end بهترتیب نخستین و آخرین نویسه است.
میتوانند بردارهایی از اندیسها باشند.
برخلاف اندیسگذاری بردارها، که این رفتار خوشایند است، مقادیر منفی به سبک پایتون از انتهای رشته به عقب شمرده میشوند.
s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab" "de" "fgh"
s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"
str_flatten یک بردار نویسهای را به یک رشتهی تکی تبدیل میکند.
پارامتر collapse بین هر عنصر درج میشود و مقدار پیشفرضش "" است.
c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"
str_c چندین بردار نویسهای را به یک بردار نویسهای تکی تبدیل میکند و در صورت نیاز بازچرخانی انجام میدهد.
str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"
str_glue درونیابی سادهی رشته را به R میآورد.
عبارتهای درون آکولادهای { } تفسیر میشوند و نمایش متنی آنها جایگزین میگردد.
r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631
یکی از کاربردهای بسیار رایج R این است که دادههای بههمریخته را برای تحلیل به قالبی یکدست تبدیل کند. برای کمک به این کار، روشهای متعددی برای تقسیم متن به زیررشتهها وجود دارد. بیشترشان از حوزهی فعلی ما خارجاند.
str_split_1 سادهترین است و یک رشتهی تکی را به برداری از قطعهها تبدیل میکند که در یک الگوی مشخص تقسیم شدهاند (الگوی پیشفرضی وجود ندارد).
s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R" "Julia" "Python"
برخی زبانها (عمدتاً اروپایی) میان حروف بزرگ و کوچک تفاوت میگذارند.
مجموعهای از توابع stringr برای تبدیل این دو به هم، به شیوههای متناسب با locale وجود دارد:
str_to_lower()str_to_upper()str_to_title() : حرف اول هر واژه به بزرگstr_to_sentence() : حرف اول جمله به بزرگstr_to_upper("elixir") # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά") # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"
str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"
برای جایگزینی زیررشته بر پایهی تطبیق الگو، str_replace() نخستین رخداد الگو را تغییر میدهد و str_replace_all() همهی رخدادها را.
برای جایگزینی زیررشته بر پایهی اندیسگذاری نویسه، شکل انتسابی str_sub() هم وجود دارد.
s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"
در این تمرین با خطوط لاگ کار میکنید.
هر خط لاگ رشتهای است که به این شکل قالببندی میشود: "[<LEVEL>]: <MESSAGE>".
سه سطح لاگ متفاوت وجود دارد:
INFOWARNINGERRORسه وظیفه دارید که هرکدام یک خط لاگ میگیرند و از شما میخواهند کاری روی آن انجام دهید.
میتوانید آزادانه از توابع قدیمی R پایه یا از کتابخانهی stringr استفاده کنید؛ هر دو میتوانند راهحلهای کوتاه و کارآمدی ارائه دهند.
تابع message را طوری پیادهسازی کنید که پیام یک خط لاگ را برگرداند:
message("[ERROR]: Invalid operation")
#> "Invalid operation"
فاصلههای سفید ابتدایی و انتهایی باید حذف شوند:
message("[WARNING]: Disk almost full\r\n")
#> "Disk almost full"
تابع log_level را پیادهسازی کنید تا سطح لاگ یک خط لاگ را برگرداند؛ این مقدار باید با حروف کوچک برگردانده شود:
log_level("[ERROR]: Invalid operation")
#> "error"
تابع reformat را پیادهسازی کنید که خط لاگ را دوباره قالببندی میکند، به این صورت که اول پیام را میآورد و بعد از آن سطح لاگ را داخل پرانتز قرار میدهد:
reformat("[INFO]: Operation completed")
#> "Operation completed (info)"