السلسلة النصية هي قطعة من النص توضع بين علامتي تنصيص.
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.
بالإضافة إلى ذلك، قد يكون من المفيد في المهام الصغيرة استخدام دوال مدمجة بدلًا من استيراد مكتبة كبيرة. يميل مشرفو مسار R إلى اتباع هذا النهج عند كتابة حلول نموذجية للتحقق من صحة التمارين على مستودع GitHub الخاص بنا.
لذلك لا يزال من المفيد تعلّم شيء عن الأساليب القديمة.
تُعرض الدوال أدناه في أبسط صورها، لكن لمعظمها أيضًا وسائط اختيارية. راجع الوثائق لمعرفة التفاصيل.
ncharتُرجع عدد المحارف في السلسلة النصية.
nchar("string")
#> [1] 6
nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7
موضوع معنى "المحرف" (معقّد!) خارج نطاقنا. لاحظ فقط نقطتين في الأمثلة أعلاه:
\0 (وهو ما كان سيضيف 1 إلى الطول).كن حذرًا بشأن ما تمرره إلى 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 (وعدة لغات لاحقة: تسرد ويكيبيديا نحو 30 منها).
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" تعني بحثًا "شاملًا").
وكما يوحي الاسمان، يمكن أن يكون النمط تعبيرًا نمطيًا، وهو ما سيُغطى في مفهوم لاحق.
لماذا تبدو دوال السلاسل النصية القديمة هذه مثل أوامر صدفة يونكس؟
رغم أن R أُنشئت عام 1993، فقد صُممت لتكون متوافقة إلى حد كبير مع اللغة S.
صُممت S في مختبرات بيل، وظهرت لأول مرة عام 1976. كانت مختبرات بيل مسؤولة أيضًا عن يونكس (منذ 1969) واللغة C (1972).
فريق رائع من حقبة أقدم بكثير! لا يسعنا إلا أن نفترض أنهم تحدثوا مع بعضهم وتبادلوا الأفكار.
وإن تحلّينا بحسن الظن، يمكننا القول إن دوال السلاسل النصية بالأسلوب القديم ليست أفضل ما تقدمه R (وهناك آراء أقل تسامحًا).
لتحسين الوضع، ما زالت مكتبة stringr قيد التطوير منذ عام 2009، وهي الآن جزء أساسي من مجموعة 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"