রেগুলার এক্সপ্রেশন হলো স্ট্রিং প্যাটার্ন ম্যাচ করার একটি অত্যন্ত বহুমুখী উপায়, যার জন্য বিশেষভাবে তৈরি করা একটি ডোমেইন স্পেসিফিক ভাষা (DSL) ব্যবহার করা হয়।
অনেক রেগেক্স সুবিধা Base R-এর ভেতরেই থাকে, আর পুরোনো কোডে এর উদাহরণ আপনি দেখতে পাবেন।
নতুন কোডের জন্য stringr লাইব্রেরি ব্যবহার করার পরামর্শ দৃঢ়ভাবে দেওয়া হচ্ছে।
স্ট্রিংস কনসেপ্ট-এ যেমন বলা হয়েছে, stringr-এর ডকুমেন্টেশন ভালো, যা সাধারণত প্যাকেজের লেখক (বা লেখকরা) নিজেই লেখেন।
এই R পাঠ্যক্রমে ধরে নেওয়া হয়েছে যে আপনি মৌলিক রেগেক্স সিনট্যাক্সের সাথে আগেই পরিচিত। আমরা শুধু R-নির্দিষ্ট বৈশিষ্ট্যগুলোতেই মন দেব।
নিচে আপনার রেগুলার এক্সপ্রেশনের জ্ঞান ঝালিয়ে নেওয়ার কিছু রিসোর্স দেওয়া হলো।
Strings কনসেপ্টে আগেই আলোচনা করা অনেক ফাংশনই মূলত রেগেক্স সার্চের জন্য তৈরি।
স্ট্রিংস কনসেপ্টটি আমরা তুলনামূলক সহজ রেখেছিলাম, শুধু স্ট্রিং লিটারেল ম্যাচ করিয়ে; কিন্তু তার বদলে যেকোনো রেগেক্স প্যাটার্ন ব্যবহার করা যায়।
আনুষ্ঠানিকভাবে, R-এ একটি রেগেক্স তৈরি হয় regex() কল করে। সুবিধার জন্য stringr-এর ফাংশনগুলো যেকোনো প্যাটার্ন স্ট্রিংকেই একটি রেগেক্স হিসেবে ধরে নেয়। নিচের উদাহরণে প্রথম str_replace_all() হলো দ্বিতীয়টির সিনট্যাকটিক সুগার:
input <- "Some input string"
# "\\s" matches any whitespace character
input |> str_replace_all("\\s", "_")
[1] "Some_input_string"
input |> str_replace_all(regex("\\s"), "_")
[1] "Some_input_string"
চিটশিট একটি কাজের গাইড: pattern আর্গুমেন্ট আছে এমন সব ফাংশন (লাল রঙে ছাপা) একটি রেগেক্স গ্রহণ করে, আর ২য় পৃষ্ঠায় রেগেক্স সিনট্যাক্সের সারসংক্ষেপ আছে।
মনে রাখবেন, ডিফল্ট সেটিং বদলাতে হলে, যেমন কেস সেন্সিটিভিটি বা লোকেল, regex() ফাংশনটি তখনো গুরুত্বপূর্ণ হতে পারে।
# default is case sensitive
"A Mixed Assortment" |>
str_replace_all(regex("[aeiou]", ignore_case = TRUE), "*")
#> [1] "* M*x*d *ss*rtm*nt"
অনেক রেগেক্স কোডে ব্যাকস্ল্যাশ থাকে, যেমন উপরে ব্যবহৃত \s। সাধারণ R স্ট্রিংয়ে \-কে ডাবল করে এস্কেপ করতে হয়, তাই "\\s"।
ব্যাকস্ল্যাশ জমে গিয়ে বিভ্রান্তিকর হয়ে উঠতে পারে, তাই জটিল ক্ষেত্রে raw স্ট্রিং ব্যবহার করলে সুবিধা হতে পারে।
ডিফল্টভাবে raw স্ট্রিং ডিফাইন করার সিনট্যাক্স হলো r"( )", তবে অন্য ডেলিমিটারও ব্যবহার করা যায়। রেগেক্স সিনট্যাক্সে প্রথম বন্ধনী ( ) ও তৃতীয় বন্ধনী [ ] দুটোই কমন (ক্যাপচার গ্রুপ ও অপশন গ্রুপের জন্য), তাই দ্বিতীয় বন্ধনী { } একটি ভালো পছন্দ হতে পারে।
input |> str_replace_all(r"{\s\w}", "__")
#> [1] "Some__nput__tring"
এখানে কিছু প্রশ্ন আছে, আর সেগুলোর উত্তর দিতে সাহায্য করতে পারে এমন কিছু ফাংশনও।
কিছু উদাহরণে fruit ব্যবহার করা হয়েছে; এটি stringr-এর সাথে অনুশীলনের জন্য দেওয়া ৮০টি ফলের নামের একটি ভেক্টর।
কোনো স্ট্রিংয়ে ম্যাচ আছে কি?
str_detect(), অথবা ^ বা $ অ্যাঙ্কর এড়াতে str_starts(), str_ends()।
# berries begining with b
fruit |> str_detect("^b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE
# alternative syntax
fruit |> str_starts("b.*berry") |> head(15)
#> [1] FALSE FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE
শুধু ম্যাচ আছে এমন স্ট্রিং রিটার্ন করবেন?
স্ট্রিংগুলো পেতে str_subset() ব্যবহার করুন, আর ইনডেক্সগুলো পেতে str_which()।
fruit |> str_subset("a...e")
[1] "apple" "blackberry" "mandarine" "nectarine" "pineapple" "pomegranate" "raspberry" "salal berry"
> fruit |> str_which("a...e")
[1] 1 7 48 51 62 64 70 73
প্রতিটি স্ট্রিংয়ে কতগুলো ম্যাচ আছে?
str_count()
# count vowels
fruit |> str_count("[aeiou]") |> head(10)
#> [1] 2 3 4 3 3 2 2 3 5 3
ম্যাচটি কোথায়?
ইন্টার্যাক্টিভ ব্যবহারের জন্য str_view(), আর সাধারণভাবে str_locate() বা str_locate_all()।
# str_view only shows matching elements, with match in < > delimiters
fruit |> str_view("a...e")
[1] │ <apple>
[7] │ bl<ackbe>rry
[48] │ mand<arine>
[51] │ nect<arine>
[62] │ pine<apple>
[64] │ pomegr<anate>
[70] │ r<aspbe>rry
[73] │ sal<al be>rry
# str_locate returns all elements
fruit |> str_locate("a...e") |> head(10)
start end
[1,] 1 5
[2,] NA NA
[3,] NA NA
[4,] NA NA
[5,] NA NA
[6,] NA NA
[7,] 3 7
[8,] NA NA
[9,] NA NA
[10,] NA NA
সাধারণত আমরা জানতে চাই কী ম্যাচ করেছে। এটি করা হয় রেগেক্সের ভেতরে প্রথম বন্ধনীতে ক্যাপচার গ্রুপ যোগ করে।
একই রেগেক্সের ভেতরেই ক্যাপচারকে আবার উল্লেখ করা সম্ভব, \1, \2 ইত্যাদি দিয়ে।
যেমন, একই জোড়া অক্ষরের পুনরাবৃত্তি খুঁজে বের করা:
fruit |> str_subset("(..)\\1")
[1] "banana" "coconut" "cucumber" "jujube" "papaya" "salal berry"
একই ধরনের সিনট্যাক্স দিয়ে রিপ্লেসমেন্ট স্ট্রিংয়েও ক্যাপচার ব্যবহার করা যায়:
# double to triple
fruit |>
str_subset("(..)\\1") |>
str_replace("(..)\\1", "\\1\\1\\1")
[1] "bananana" "cococonut" "cucucumber" "jujujube" "papapaya" "salalal berry"
পরে প্রসেস করার জন্য ম্যাচ জমা করতে শুধু প্রথম ম্যাচের জন্য str_match() ফাংশন, আর সব ম্যাচের জন্য str_match_all() ব্যবহার করুন।
ক্যাপচার গ্রুপ যেকোনো ভাষাতেই জটিল আউটপুট তৈরি করে।
R-এর পদ্ধতি (যা বাড়তি চ্যালেঞ্জ হিসেবে ভেক্টরাইজডও) হলো str_match() থেকে একটি ম্যাট্রিক্স তৈরি করা, যেখানে প্রতিটি ইনপুট স্ট্রিংয়ের জন্য একটি সারি, ১ম কলামে পুরো ম্যাচ হওয়া স্ট্রিং, আর বাকি কলামগুলোতে নানা ক্যাপচার গ্রুপ থাকে।
নামযুক্ত গ্রুপ ব্যবহার করলে সুবিধা হতে পারে, কারণ সেগুলো ফলাফলের ম্যাট্রিক্সে কলামের নাম হয়ে যায়।
recipe <- "dissolve 25g sugar in 200ml water"
recipe |> str_match("(\\d+g) .* (\\d+ml)")
[,1] [,2] [,3]
[1,] "25g sugar in 200ml" "25g" "200ml"
# use named capture groups
recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
wt vol
[1,] "25g sugar in 200ml" "25g" "200ml"
এই পাঠ্যক্রমে ম্যাট্রিক্স নিয়ে আমরা বেশি কিছু বলিনি, তবে আপনি অ্যাক্সেস করতে পারেন এমন একটি কনসেপ্ট ডকুমেন্ট আছে। ম্যাট্রিক্স টাইডিভার্স ইকোসিস্টেমের সাথে ভালোভাবে খাপ খায় না, যদিও এটি দীর্ঘদিন ধরে Base R-এর অংশ।
সংক্ষেপে: ম্যাট্রিক্সকে দুইটি ইনডেক্সযুক্ত ভেক্টরের মতো ধরুন, [rows, cols] ক্রমে।
পুরো সারি বা কলাম পেতে যেকোনো একটি ইনডেক্স খালি রাখুন।
m[r,] দিয়ে একটি সারি r বা m[, c] দিয়ে একটি কলাম c নেওয়ার সময়, রিটার্ন হওয়া মানটি ডিফল্টভাবে একটি ভেক্টরে চ্যাপ্টা করে দেওয়া হয়।
> amounts <- recipe |> str_match("(?<wt>\\d+g) .* (?<vol>\\d+ml)")
# get all of row 1, as a named vector
> amt1 <- amounts[1,]
> amt1
wt vol
"25g sugar in 200ml" "25g" "200ml"
# get a vector element
amt1["wt"] |> unname()
[1] "25g"
অবশ্যম্ভাবীভাবেই, str_match_all() আরও বেশি কাঠামোযুক্ত আউটপুট তৈরি করে:
[[1]] ইনডেক্সিং দিয়ে আউটপুট আনলিস্ট করুন।match()-এর মতো।mix <- "Mix 200g of sugar, 180g of flour and 150g of butter"
# raw output
> str_match_all(mix, "(\\d+)g")
[[1]]
[,1] [,2]
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"
# we just want the first list element
> str_match_all(mix, "(\\d+)g")[[1]]
[,1] [,2]
[1,] "200g" "200"
[2,] "180g" "180"
[3,] "150g" "150"
# captures are in column 2, so get that as a vector
> str_match_all(mix, "(\\d+)g")[[1]][,2]
[1] "200" "180" "150"
অবশ্যই, ম্যাচ ব্যর্থও হতে পারে।
তখন ফলাফল হবে বিশেষ মান NA।
এটি পরীক্ষা করার জন্য প্রস্তুত থাকুন, যেমন নাথিংনেস কনসেপ্ট-এ বর্ণনা করা হয়েছে।
# failed match
recipe |> str_match("(not here)")
[,1] [,2]
[1,] NA NA
R for Data science-এ বলা হয়েছে:
str_match()একটি ম্যাট্রিক্স রিটার্ন করে, তাই এটি নিয়ে কাজ করা বিশেষ সহজ নয়।
সাথে এই পাদটীকা:
মূলত কারণ আমরা এই বইয়ে কখনোই ম্যাট্রিক্স নিয়ে আলোচনা করি না!
বইটির একজন লেখক stringr লাইব্রেরিটিও লিখেছেন, আর পরোক্ষভাবে নিজের কাজেরই সমালোচনা করছেন।
ভবিষ্যতের রিলিজগুলোতে এই ফাংশনের ইমপ্লিমেন্টেশন কীভাবে বদলে যায়, সেটি দেখতে কৌতূহলোদ্দীপক হবে।