একটি স্ট্রিং হলো উদ্ধৃতি চিহ্নের মাঝে থাকা টেক্সটের একটি টুকরো।
mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"
দুর্ভাগ্যবশত, অন্য ভাষার সঙ্গে পরিচিত প্রোগ্রামারদের কাছে এই পরিভাষা বিভ্রান্তিকর লাগতে পারে।
এখানে type হলো character, তবু R একটি অক্ষর (অনেক ভাষায় যাকে char বলা হয়, কিন্তু R-এ নয়) আর একটি লম্বা স্ট্রিংয়ের মধ্যে কোনো পার্থক্য করে না। দুটোই একটিমাত্র আইটেম, আর একক ' ' বা ডাবল " " উদ্ধৃতি চিহ্নে খুশিমতো বদলে বদলে ব্যবহার করা যায়।
স্টাইল গাইড ডাবল উদ্ধৃতি চিহ্ন ব্যবহারের সুপারিশ করে, তবে সেটি ছাড়া যখন আপনি স্ট্রিংয়ের ভেতরে ডাবল উদ্ধৃতি চিহ্ন এস্কেপ না করেই বসাতে চান।
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(); তবে এর জন্য নির্দিষ্ট কিছু ফাংশন দরকার, যা নিচে আলোচনা করা হবে।
উপরে যেমন দেখলাম, কিছু যতিচিহ্নের আগে ব্যাকস্ল্যাশ \ বসিয়ে সেগুলোকে "এস্কেপ" করতে হয়।
এই তালিকা বেশিরভাগ অভিজ্ঞ প্রোগ্রামারের কাছে নতুন কিছু হবে না, কারণ ভাষাভেদে এই বিষয়টি মোটামুটি ভালোভাবেই একরকম।
উদ্ধৃতি চিহ্ন ছাড়াও (উপরে যেমন দেখানো হয়েছে), তালিকায় আছে নিউলাইন \n, ট্যাব \t, ব্যাকস্ল্যাশ \\, এবং \U বা \u দিয়ে শুরু হওয়া ইউনিকোড প্রতীক।
অনেক বেশি এস্কেপ ক্যারেক্টার এড়াতে চাইলে r"( )"-এর ভেতরে র' স্ট্রিং ডিফাইন করা যায়:
r"(raw "string")"
#> [1] "raw \"string\""
( ) বন্ধনীযুক্ত র' স্ট্রিংয়ের জন্য আরও অনেক সিনট্যাক্স অপশন আছে।
বর্তমানে সমর্থিত R-এর যেকোনো সংস্করণ স্ট্রিংগুলো UTF-8-এ এনকোড করে, তাই পৃথিবীর বেশিরভাগ ভাষাই সমর্থন করা সম্ভব।
"आधुनिक मानक हिन्दी"
#> [1] "आधुनिक मानक हिन्दी"
এর বাইরে, R-এর অনেক ফাংশন locale সম্পর্কে সচেতন, এবং স্ট্রিং সাজানো, আপার/লোয়ার কেসে রূপান্তর, তারিখ ও সময় ইত্যাদির ক্ষেত্রে স্থানীয় রীতিনীতি অনুসরণ করার চেষ্টা করে।
এই সিলেবাসে উদাহরণগুলো মূলত মার্কিন ইংরেজির জন্য দেখানো হবে: একদিকে এটি একটি কমন ডিফল্ট, আর অন্যদিকে সিলেবাসের লেখকেরা থাকেন মার্কিন যুক্তরাষ্ট্রে।
লোকেল একটি জটিল বিষয়, তাই আপনার নিজের পরিস্থিতির জন্য ডকুমেন্টেশন খুঁজে দেখুন।
বেস R-এ বেশিরভাগ কাজই সম্ভব, তবে স্ট্রিং নিয়ে কাজ করার সমর্থন অনেক প্রোগ্রামারের কাছে বেশ সীমিত ও বিভ্রান্তিকর মনে হয়।
ভাগ্যক্রমে, tidyverse-এর কিছু প্যাকেজ আছে যেগুলো অনেক বেশি সহজবোধ্য, নমনীয় ও নিজেদের সঙ্গে সঙ্গতিপূর্ণ।
পুরোনো ধাঁচের স্ট্রিং ফাংশন ব্যবহার করা এখনো অনেক পুরোনো কোড আছে। এর মধ্যে Exercism-এর বেশিরভাগ কমিউনিটি সমাধানও পড়ে।
এছাড়া ছোট কাজের ক্ষেত্রে বড় কোনো লাইব্রেরি ইমপোর্ট না করে বিল্ট-ইন ফাংশন ব্যবহার করা সুবিধাজনক হতে পারে। আমাদের GitHub রিপো-তে অনুশীলনীগুলো যাচাই করার জন্য আদর্শ সমাধান লেখার সময় R ট্র্যাকের মেইনটেইনাররা সাধারণত এই পথেই হাঁটেন।
তাই পুরোনো পদ্ধতিগুলোর কিছুটা জানা এখনো শেখার মতোই।
নিচের ফাংশনগুলো তাদের সরলতম রূপে দেখানো হয়েছে, তবে বেশিরভাগেরই ঐচ্ছিক আর্গুমেন্ট আছে। বিস্তারিত জানতে ডকুমেন্টেশন দেখুন।
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, C থেকে sprintf ফাংশনটি নকল করেছে (আর পরের বেশ কিছু ভাষাও: উইকিপিডিয়ায় এদের তালিকা আছে প্রায় ৩০টির)।
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" মানে "গ্লোবাল" সার্চ)।
নাম থেকেই বোঝা যায়, প্যাটার্নটি একটি রেজেক্স হতে পারে, যা নিয়ে আলোচনা হবে পরের একটি কনসেপ্টে।
এই পুরোনো স্ট্রিং ফাংশনগুলো দেখতে Unix শেল কমান্ডের মতো কেন?
R তৈরি হয়েছিল ১৯৯৩ সালে, তবু এটিকে ডিজাইন করা হয়েছিল S ভাষার সঙ্গে বহুলাংশে পিছনের দিকেও সামঞ্জস্যপূর্ণ হতে।
S ডিজাইন করা হয়েছিল Bell Labs-এ, আর প্রথম আবির্ভাব ১৯৭৬ সালে। Unix (১৯৬৯ থেকে) আর C ভাষার (১৯৭২) দায়িত্বও ছিল Bell Labs-এর ঘাড়ে।
সেই অনেক আগের যুগের দারুণ এক দল! আমরা শুধু ধরে নিতে পারি যে তারা একে অন্যের সঙ্গে কথা বলতেন আর ভাব বিনিময় করতেন।
উদার হয়ে বললে, বলা যায় পুরোনো ধাঁচের স্ট্রিং ফাংশনগুলো 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-এর ডিফল্ট যথাক্রমে প্রথম ও শেষ ক্যারেক্টার।
এগুলো ইনডেক্সের ভেক্টরও হতে পারে।
ভেক্টর ইনডেক্সিংয়ের বিপরীতে, স্বস্তির ব্যাপার হলো ঋণাত্মক মান স্ট্রিংয়ের শেষ থেকে পিছনে গোনে, Python-এর ধাঁচে।
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() "সমানতা"-র নানা সংজ্ঞা ব্যবহার করতে পারে।
সহজ কথায়, ডিফল্টটি কেস সেনসিটিভ, তবে তা বদলানো যায়।
লোকেল আর ইউনিকোড আরও অনেক সম্ভাবনা তৈরি করে।
str_equal("Odin", "odin")
#> [1] FALSE
str_equal("Odin", "odin", ignore_case = TRUE)
#> [1] TRUE
আরও অনেক ফাংশন আছে, যেগুলো নিয়ে রেগুলার এক্সপ্রেশন কনসেপ্টে আরও বিস্তারিত আলোচনা হবে।
কিছু ভাষা (মূলত ইউরোপীয়) আপারকেস আর লোয়ারকেস অক্ষরের মধ্যে পার্থক্য করে।
এগুলোকে পরস্পরে রূপান্তর করার জন্য stringr-এর একগুচ্ছ ফাংশন আছে, যা লোকেল অনুযায়ী কাজ করে:
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-এর ফাংশনটি কেস সেনসিটিভিটি আর নানা লোকেল-নির্দিষ্ট মিলও সামলাতে পারে।
inp <- c("some", "strings", "Some", "Strings")
str_unique(inp)
#> [1] "some" "strings" "Some" "Strings"
str_unique(inp, ignore_case = TRUE)
#> [1] "some" "strings"