التعبيرات النمطية طريقة شديدة التنوع لإجراء مطابقة الأنماط على السلاسل النصية، وذلك باستخدام لغة خاصة بالمجال (Domain Specific Language، أو 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".
قد تتكاثر الشرطات المائلة العكسية وتصبح مربكة، لذا قد يساعد استخدام السلاسل النصية الخام في الحالات المعقدة.
افتراضيًا، الصياغة المستخدمة لتعريف سلسلة نصية خام هي r"( )"، لكن يُسمح بفواصل أخرى.
ولأن الأقواس الهلالية ( ) والأقواس المربعة [ ] شائعة كلتاهما في صياغة التعبيرات النمطية (لمجموعات الالتقاط ومجموعات الخيارات)، فقد تكون الأقواس المعقوفة { } خيارًا جيدًا.
input |> str_replace_all(r"{\s\w}", "__")
#> [1] "Some__nput__tring"
فيما يلي بعض الأسئلة، ودوال يمكن أن تساعد في الإجابة عنها.
تستخدم بعض الأمثلة fruit، وهو متجه يضم 80 اسمًا من أسماء الفواكه مرفق مع 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()، بحيث يكون لكل سلسلة نصية مُدخَلة صف واحد، ويحتوي العمود 1 على السلسلة النصية المطابقة كاملة، بينما تحتوي الأعمدة المتبقية على مجموعات الالتقاط المختلفة.
قد يكون استخدام المجموعات المسماة ميزة، إذ تصبح أسماء الأعمدة في مصفوفة النتائج.
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"
لم نقُل الكثير عن المصفوفات في هذا المنهج، مع أن هناك مستندًا لمفهوم يمكنك الرجوع إليه. ولا تنسجم المصفوفات جيدًا مع منظومة Tidyverse، رغم أنها جزء من Base R منذ زمن طويل.
بإيجاز سريع: تعامل مع المصفوفات كمتجهات ذات فهرسين، بالترتيب [rows, cols].
واترك أيًّا من الفهرسين فارغًا للحصول على الصف أو العمود كاملًا.
عند الحصول على صف واحد r بواسطة m[r,] أو عمود واحد c بواسطة m[, 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، وهو ينتقد عمله بنفسه ضمنًا.
سيكون من المثير للاهتمام أن نرى كيف يتطور تنفيذ هذه الدالة في الإصدارات المستقبلية.