«رشته» تکهای از متن است که بین دو علامت نقل قول قرار میگیرد.
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() در مثال بالا دیدیم، اما به توابع خاصی نیاز دارد که در ادامه بحث خواهیم کرد.
همانطور که در بالا دیدیم، بعضی از کاراکترهای نشانهگذاری باید با یک بکاسلش \ در ابتدایشان escape شوند.
این فهرست برای بیشتر برنامهنویسان باتجربه غافلگیرکننده نخواهد بود، چون این موضوع در میان زبانها نسبتاً یکسانسازی شده است.
علاوه بر گیومهها (همانطور که در بالا نشان داده شد)، این فهرست شامل خط جدید \n، تب \t، بکاسلش \\ و نمادهای یونیکد است که با \U یا \u شروع میشوند.
برای اینکه از استفادهی زیاد از کاراکترهای escape پرهیز کنید، میتوانید رشتههای خام را درون r"( )" تعریف کنید:
r"(raw "string")"
#> [1] "raw \"string\""
برای رشتههای خامی که پرانتز ( ) دارند، گزینههای نحوهی نگارش دیگری هم وجود دارد.
هر نسخهای از R که امروز پشتیبانی میشود، رشتهها را در UTF-8 رمزگذاری میکند، بنابراین بیشتر زبانهای جهان پشتیبانی میشوند.
"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"
علاوه بر این، بسیاری از توابع R از locale آگاه هستند و میکوشند از قواعد محلی برای مرتبسازی رشته، تبدیل به حروف بزرگ/کوچک، تاریخ و زمان و غیره پیروی کنند.
این سرفصل نمونهها را عمدتاً برای انگلیسی آمریکایی نشان میدهد: هم به این دلیل که این یک پیشفرض رایج است، و هم به این دلیل که نویسندگان سرفصل بهطور اتفاقی در آمریکا زندگی میکنند.
موضوع locale پیچیده است، پس لطفاً مستندات مربوط به وضعیت خودتان را جستوجو کنید.
بیشتر کارها در R پایه ممکن است، اما بسیاری از برنامهنویسان پشتیبانی از کار با رشته را نسبتاً محدود و گیجکننده مییابند.
خوشبختانه بستههای tidyverse وجود دارند که بسیار شهودیتر، انعطافپذیرتر و خودسازگارترند.
هنوز کد قدیمی زیادی وجود دارد که از توابع رشتهی سبک قدیمی استفاده میکند. این شامل بیشتر راهحلهای جامعه در Exercism میشود.
علاوه بر این، برای کارهای کوچک ممکن است استفاده از توابع داخلی بهجای وارد کردن یک کتابخانهی بزرگ مفید باشد. نگهدارندگان مسیر R هنگام نوشتن راهحلهای نمونه برای اعتبارسنجی تمرینها در مخزن GitHub ما معمولاً همین رویکرد را در پیش میگیرند.
بنابراین، دانستن مقداری از رویکردهای قدیمی هنوز ارزش یادگیری دارد.
توابع زیر در سادهترین شکلشان ارائه شدهاند، اما بیشتر آنها آرگومانهای اختیاری هم دارند. برای جزئیات به مستندات مراجعه کنید.
ncharتعداد کاراکترهای یک رشته را برمیگرداند.
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
موضوع (پیچیدهی!) اینکه «کاراکتر» یعنی چه، خارج از حوصلهی این بحث است. فقط به چند نکته در مثالهای بالا توجه کنید:
\0 پایان نمییابند (که میتوانست ۱ به طول اضافه کند).مراقب باشید چه چیزی را به nchar میدهید.
نتایج برای مقادیر غیر character هنوز میتواند غیرقابلپیشبینی باشد، هرچند این وضعیت در نسخههای اخیر R بهتر شده است.
grepدر برداری از رشتهها به دنبال یک الگو میگردد و اندیسهای دارای تطابق را برمیگرداند.
grep("Jav", c("Java", "Python", "Javascript"))
#> [1] 1 3
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"
regexpr و regexpr
این توابع که کوتاهشدهی «عبارت باقاعده» هستند، به دنبال رخدادهای یک الگو در یک رشته میگردند و لیستی از دادههای (احتمالاً) مفید را برمیگردانند. اینها فقط برای کامل بودن ذکر شدهاند: گزینههای بهتری هم وجود دارد!
regexpr فقط اولین رخداد را برمیگرداند و gregexpr همهی رخدادها را («g» به معنی جستوجوی «global»).
همانطور که از نامها پیداست، الگو میتواند یک regex باشد که در یک مفهوم بعدی پوشش داده میشود.
چرا این توابع رشتهی قدیمی شبیه دستورهای پوستهی یونیکس هستند؟
با اینکه R در سال ۱۹۹۳ ساخته شد، طوری طراحی شد که تا حد زیادی با زبان S سازگار به عقب باشد.
زبان S در آزمایشگاههای بل طراحی شد و نخستین بار در ۱۹۷۶ ظاهر شد. آزمایشگاههای بل مسئول یونیکس (از ۱۹۶۹) و زبان C (۱۹۷۲) هم بودند.
تیم قابلتوجهی از آن دوران بسیار پیشتر! فقط میتوانیم فرض کنیم که با هم صحبت میکردند و ایده رد و بدل میکردند.
اگر خوشبین باشیم، میتوانیم بگوییم توابع رشتهی سبک قدیمی بهترین چیزی نیستند که R ارائه میدهد (دیدگاههای کمخوشبینانهتر هم وجود دارد).
برای بهبود این وضعیت، کتابخانهی stringr از سال ۲۰۰۹ در حال توسعه بوده است و اکنون بخش کلیدی از مجموعهی Tidyverse است.
آن را با library(stringr) یا library(tidyverse) به فضای نام خود وارد کنید.
برخی ویژگیهای کلیدی عبارتاند از:
str_ دارند و میکوشند هدف را روشن کنند.مانند همهی بستههای Tidyverse، stringr مستندات خوبی دارد که معمولاً توسط نویسنده(های) بسته نوشته شده است.
دهها تابع در 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_sub_all takes vector input, gives list output
str_sub_all(c(s, "ijklmnop"), c(1, 4, 6), c(2, 5, -1))
[[1]]
#> [1] "ab" "de" "fgh"
[[2]]
#> [1] "ij" "lm" "nop"
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 میآورد.
عبارتهای درون آکولادهای { } تفسیر میشوند و نمایش متنی آنها جایگزین میشود.
اگر چندین رشتهی عبارت داده شود، آنها هم مانند str_flatten به هم متصل میشوند.
همچنین میتوان بهصورت اختیاری فضای خالی ابتدا و انتهای رشتهها را حذف کرد.
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
هرچند str_glue انعطافپذیر است (و نسبتاً پیچیده)، دامنهی وسیعتری از امکانات در کتابخانهی زیربنایی glue در دسترس است.
یکی از کاربردهای بسیار رایج R این است که دادههای بههمریخته را به قالبی یکدست برای تحلیل درآوریم. برای این کار، روشهای متعددی برای تقسیم متن به زیررشتهها وجود دارد.
str_split_1 سادهترین است و یک رشتهی تکی را به برداری از قطعهها تبدیل میکند که در یک الگوی مشخص تقسیم شدهاند (الگوی پیشفرضی وجود ندارد).
s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R" "Julia" "Python"
str_split_i یک بردار کاراکتری و یک اندیس n میگیرد و برداری از nاُمین زیررشتهی هر رشتهی ورودی را برمیگرداند.
s2 <- c(s, "C Kotlin, F#") #> [1] "R Julia Python" "C Kotlin, F#"
str_split_i(s2, " ", 2) # 2nd element of each
#> [1] "Julia" "Kotlin,"
str_split یک بردار کاراکتری میگیرد و لیستی از بردارها را برمیگرداند.
یک مفهوم لیستها در سرفصل ما وجود دارد که خروجی را توضیح میدهد.
str_split(s2, " ")
[[1]]
#> [1] "R" "Julia" "Python"
[[2]]
#> [1] "C" "Kotlin," "F#"
همچنین str_split_fixed وجود دارد که یک ماتریس برمیگرداند.
ماتریسها در یک مفهوم بعدی پوشش داده میشوند.
برای تطبیق کل رشته، str_equal() میتواند از تعریفهای مختلفی از «برابری» استفاده کند.
سادهترین حالت این است که پیشفرض به بزرگی و کوچکی حروف حساس است، اما میتوان آن را تغییر داد.
locale و یونیکد امکانات بسیار بیشتری ایجاد میکنند.
str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE
توابع دیگر بسیاری هستند که در مفهوم عبارتهای باقاعده با جزئیات بیشتری بحث خواهند شد.
بعضی زبانها (عمدتاً اروپایی) بین حروف بزرگ و کوچک تفاوت قائل میشوند.
مجموعهای از توابع 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"
str_dup نسخههای تکراری میسازد و str_unique نسخههای تکراری را حذف میکند.
بهطور پیشفرض str_dup جداکنندهای ندارد، اما میتوان یکی مشخص کرد.
str_dup("xyz", 4, sep = ", ")
#> [1] "xyz, xyz, xyz, xyz"
str_unique توسعهای از unique() است.
هر دو ورودیهای تکراری را از یک بردار حذف میکنند، اما تابع stringr میتواند حساسیت به بزرگی و کوچکی حروف و تطبیقهای مختلف مخصوص locale را هم مدیریت کند.
inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some" "strings" "Some" "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some" "strings"
کتابخانهی stringr بر پایهی کتابخانهی stringi از توابع سطح پایینتر بنا شده است.
اگر نیازهای خاصی دارید که در بالا بحث نشده است، آنگاه: