مسیرها
/
R
R
/
تمرین‌ها
/
سطح‌های لاگ
سطح‌های لاگ

سطح‌های لاگ

تمرین یادگیری

مقدمه

پیشزمینه

«رشته» قطعه‌ای از متن است که میان علامت‌های نقل‌قول قرار می‌گیرد. هر نسخه‌ی 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 وجود دارند که بسیار شهودی‌تر، انعطاف‌پذیرتر و خودسازگارترند.

R پایه

هنوز کد قدیمی زیادی وجود دارد که از توابع رشته‌ای سبک قدیم استفاده می‌کند. این شامل بیشتر راه‌حل‌های انجمن در Exercism است.

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

بنابراین، آشنایی با روش‌های قدیمی هنوز ارزش یادگیری دارد.

nchar

تعداد نویسه‌های یک رشته را برمی‌گرداند.

nchar("string")
#> [1] 6

nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7

موضوع (پیچیده‌ی!) اینکه «نویسه» یعنی چه، خارج از حوزه‌ی این بحث است. فقط به چند نکته در مثال‌های بالا توجه کنید:

  • با اینکه در R رشته‌ها به‌صورت داخلی با C پیاده‌سازی شده‌اند، با \0 پایان نمی‌یابند (که ۱ واحد به طول اضافه می‌کرد).
  • نویسه‌های یونیکد چندهسته‌ای همان‌طور که انتظار دارید (یا امیدوارید) مدیریت می‌شوند. «Hrōðgār» ۷ نویسه دارد (که «رون» یا «نقطه‌ی کد» هم نامیده می‌شوند)، اما برای ذخیره‌سازی به ۱۰ بایت نیاز دارد.
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"

Tidyverse

اگر خوش‌بین باشیم، می‌توانیم بگوییم توابع رشته‌ای سبک قدیم بهترین چیزی نیستند که R عرضه می‌کند (دیدگاه‌های کم‌خوش‌بینانه‌تری هم وجود دارد).

برای بهبود این وضعیت، کتابخانه‌ی stringr از سال ۲۰۰۹ در حال توسعه بوده و اکنون بخش کلیدی مجموعه‌ی Tidyverse است.

آن را با library(stringr) یا library(tidyverse) به فضای نام خود وارد کنید.

برخی از ویژگی‌های کلیدی عبارت‌اند از:

  • همه‌ی نام‌های توابع پیشوند str_ دارند و تلاش می‌کنند هدف را روشن کنند.
  • ترتیب پارامترها خودسازگار است و داده‌ی ورودی نخستین پارامتر است.
  • ورودی‌ها می‌توانند از یک رشته‌ی تکی تا ساختارهای داده‌ی پیچیده متغیر باشند. توابع تلاش می‌کنند خود را تطبیق دهند و معقول‌ترین کار را انجام دهند.

مانند همه‌ی بسته‌های Tidyverse، stringr مستندات خوبی دارد که معمولاً نویسنده(گان) بسته آن را نوشته‌اند.

یک وقفه: آشنایی سریع با پایپ

استفاده از پایپ‌ها |> برای اتصال توابع در بخش‌های دیگر برنامه‌ی درسی با جزئیات بیشتری بحث می‌شود.

فعلاً خوب است بدانید که:

  • نتیجه‌ی سمت چپ پایپ به نخستین پارامتر تابع سمت راست تبدیل می‌شود («راست» شکست‌های خط اختیاری را نادیده می‌گیرد).
  • هر پارامتر باقی‌مانده را می‌توان در فراخوانی تابع گنجاند.
  • در R باید به یک فراخوانی تابع پایپ کنیم، نه فقط یک نام، پس همیشه پرانتز لازم است (این با برخی زبان‌های دیگر تفاوت دارد).

مثال زیر این نحوه‌ی نگارش را نشان می‌دهد. توابع تک‌تک در ادامه بحث می‌شوند.

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>".

سه سطح لاگ متفاوت وجود دارد:

  • INFO
  • WARNING
  • ERROR

سه وظیفه دارید که هرکدام یک خط لاگ می‌گیرند و از شما می‌خواهند کاری روی آن انجام دهید.

می‌توانید آزادانه از توابع قدیمی R پایه یا از کتابخانه‌ی stringr استفاده کنید؛ هر دو می‌توانند راه‌حل‌های کوتاه و کارآمدی ارائه دهند.

1. دریافت پیام از یک خط لاگ

تابع message را طوری پیاده‌سازی کنید که پیام یک خط لاگ را برگرداند:

message("[ERROR]: Invalid operation")
#> "Invalid operation"

فاصله‌های سفید ابتدایی و انتهایی باید حذف شوند:

message("[WARNING]:  Disk almost full\r\n")
#> "Disk almost full"

2. دریافت سطح لاگ از یک خط لاگ

تابع log_level را پیاده‌سازی کنید تا سطح لاگ یک خط لاگ را برگرداند؛ این مقدار باید با حروف کوچک برگردانده شود:

log_level("[ERROR]: Invalid operation")
#> "error"

3. قالب‌بندی مجدد یک خط لاگ

تابع reformat را پیاده‌سازی کنید که خط لاگ را دوباره قالب‌بندی می‌کند، به این صورت که اول پیام را می‌آورد و بعد از آن سطح لاگ را داخل پرانتز قرار می‌دهد:

reformat("[INFO]: Operation completed")
#> "Operation completed (info)"
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
R Exercism

آماده‌اید سطح‌های لاگ را شروع کنید؟

در Exercism ثبت‌نام کنید تا R را همراه با 21 مفهوم111 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.