معالجة النصوص
الدرس 19: معالجة النصوص
🎯 تشبيه من الحياة
تخيل أنك أمين مكتبة. كل يوم تتعامل مع كمية كبيرة من العمل النصي:
- بحث: التحقق مما إذا كان عنوان كتاب يحتوي على كلمة معينة →
strings.Contains - استبدال: استبدال البطاقات القديمة بجديدة →
strings.Replace - تقسيم: فصل قائمة علامات مفصولة بفواصل →
strings.Split - دمج: دمج عدة كلمات مفتاحية في استعلام بحث →
strings.Join - إزالة الفراغات: إزالة المسافات الزائدة من بداية وعنوان الكتاب →
strings.Trim
معالجة النصوص هي "العمل النصي" في البرامج — يحتاج إليها كل برنامج يتعامل مع النصوص.
المفاهيم الأساسية
توفر Go عدة مكتبات قياسية للتعامل مع النصوص:
| الحزمة | الغرض | الدوال الشائعة |
|---|---|---|
strings |
البحث والاستبدال والتقسيم والدمج في النصوص | Contains, Replace, Split, Join, Trim |
strconv |
التحويل بين النصوص والأنواع الأخرى | Atoi, Itoa, ParseBool, FormatFloat |
unicode/utf8 |
عمليات الترميز UTF-8 | RuneCountInString, ValidString |
strings.Builder |
الربط الفعال للعديد من النصوص | WriteString, String |
نقاط أساسية:
- النصوص في Go غير قابلة للتعديل — أي تعديل ينشئ نصاً جديداً
- النصوص في Go هي تسلسلات بايت مرمزة بـ UTF-8 في الأساس
len(str)تعيد عدد البايتات، وليس الأحرفruneهو نوع Go لتمثيل نقاط Unicode (أساساًint32)
الصياغة الأساسية والاستخدام
1. حزمة strings
package main
import (
"fmt"
"strings"
)
func main() {
str := "Hello, Go!"
// البحث
fmt.Println(strings.Contains(str, "Go")) // true
fmt.Println(strings.HasPrefix(str, "Hello")) // true
fmt.Println(strings.HasSuffix(str, "!")) // true
fmt.Println(strings.Index(str, "Go")) // 7
// الاستبدال
result := strings.Replace(str, "Go", "Golang", 1)
fmt.Println(result) // "Hello, Golang!"
// استبدال الكل
s := "aabbcc"
fmt.Println(strings.ReplaceAll(s, "a", "x")) // "xxbbcc"
// التقسيم والدمج
csv := "apple,banana,cherry"
fruits := strings.Split(csv, ",")
fmt.Println(fruits) // [apple banana cherry]
joined := strings.Join(fruits, " | ")
fmt.Println(joined) // "apple | banana | cherry"
// إزالة الفراغات
padded := " Hello World "
fmt.Println(strings.TrimSpace(padded)) // "Hello World"
fmt.Println(strings.Trim("##Hello##", "#")) // "Hello"
fmt.Println(strings.TrimLeft("##Hello##", "#")) // "Hello##"
// تحويل الحالة
fmt.Println(strings.ToUpper("hello")) // "HELLO"
fmt.Println(strings.ToLower("HELLO")) // "hello"
// التكرار
fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"
// العد
fmt.Println(strings.Count("banana", "an")) // 2
}
strings.Split فارغاً، يتم تقسيم النص إلى شريحة من الأحرف الفردية.
2. حزمة strconv
package main
import (
"fmt"
"strconv"
)
func main() {
// نص ← عدد صحيح
num, err := strconv.Atoi("42")
if err != nil {
fmt.Println("فشل التحويل:", err)
}
fmt.Println(num) // 42
// عدد صحيح ← نص
str := strconv.Itoa(42)
fmt.Println(str) // "42"
// نص ← منطقي
b, err := strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>
// نص ← عدد عشري
f, err := strconv.ParseFloat("3.14", 64)
fmt.Println(f, err) // 3.14 <nil>
// عدد عشري ← نص
// 'f' يعني التنسيق العادي، -1 يعني الحد الأدنى من الأرقام، 64 يعني float64
s := strconv.FormatFloat(3.14, 'f', -1, 64)
fmt.Println(s) // "3.14"
// مخرجات منسقة (مشابهة لـ sprintf في C)
formatted := strconv.FormatInt(255, 16) // سداسي عشري
fmt.Println(formatted) // "ff"
}
strconv.Atoi تعادل strconv.ParseInt(s, 10, 0)، وتعيد نوع int التابع للمنصة.
3. حزمة unicode/utf8
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "مرحبا"
// len() تعيد عدد البايتات
fmt.Println(len(str)) // 10 (كل حرف عربي يأخذ 2 بايت)
// utf8.RuneCountInString() تعيد عدد الأحرف
fmt.Println(utf8.RuneCountInString(str)) // 5
// التحقق من صحة UTF-8
fmt.Println(utf8.ValidString(str)) // true
fmt.Println(utf8.ValidString("abc")) // true
// التكرار على كل rune في النص
for i, r := range str {
fmt.Printf("المؤشر:%d الحرف:%c Unicode:%U\n", i, r, r)
}
}
range للتكرار على نص، تقوم Go تلقائياً بالتكرار حسب rune (حرف Unicode)، وليس حسب البايت.
4. strings.Builder (ربط فعال)
package main
import (
"fmt"
"strings"
)
func main() {
// ❌ غير فعال: ينشئ نصاً جديداً في كل ربط
// result := ""
// for i := 0; i < 1000; i++ {
// result += "a" // يخصص ذاكرة جديدة في كل مرة
// }
// ✅ فعال: استخدم strings.Builder
var builder strings.Builder
for i := 0; i < 1000; i++ {
builder.WriteString("a")
}
result := builder.String()
fmt.Println(len(result)) // 1000
// تخصيص مسبق للسعة لأداء أفضل
var builder2 strings.Builder
builder2.Grow(1000) // تخصيص 1000 بايت مسبقاً
for i := 0; i < 1000; i++ {
builder2.WriteString("b")
}
fmt.Println(builder2.Len()) // 1000
}
strings.Builder يستخدم داخلياً شريحة []byte، مما يتجنب التخصيص المتكرر للذاكرة الناتج عن عدم قابلية تعديل النصوص.
أمثلة الكود
مثال: إحصائيات وتحليل النصوص (الصعوبة ⭐)
package main
import (
"fmt"
"strings"
"unicode"
)
// تحليل عدد أنواع الأحرف المختلفة في نص
func analyzeString(s string) (letters, digits, spaces, others int) {
for _, r := range s {
switch {
case unicode.IsLetter(r):
letters++
case unicode.IsDigit(r):
digits++
case unicode.IsSpace(r):
spaces++
default:
others++
}
}
return
}
func main() {
text := "Hello, Go! 2024 Version 2.0"
letters, digits, spaces, others := analyzeString(text)
fmt.Printf("النص: %q\n", text)
fmt.Printf("الأحرف: %d\n", letters)
fmt.Printf("الأرقام: %d\n", digits)
fmt.Printf("المسافات: %d\n", spaces)
fmt.Printf("أخرى: %d\n", others)
// عدد تكرارات الكلمات
words := strings.Fields("the go the language the world")
freq := make(map[string]int)
for _, w := range words {
freq[strings.ToLower(w)]++
}
fmt.Println("\nتكرار الكلمات:", freq)
}
المخرجات:
النص: "Hello, Go! 2024 Version 2.0"
الأحرف: 17
الأرقام: 6
المسافات: 5
أخرى: 3
تكرار الكلمات: map[go:1 language:1 the:3 world:1]
مثال: محلل CSV (الصعوبة ⭐⭐)
package main
import (
"fmt"
"strings"
)
// محلل سطر CSV بسيط يدعم الحقول المقتبسة
func parseCSVLine(line string) []string {
var fields []string
var current strings.Builder
inQuotes := false
for _, r := range line {
switch {
case r == '"' && !inQuotes:
// الدخول في منطقة الاقتباس
inQuotes = true
case r == '"' && inQuotes:
// الخروج من منطقة الاقتباس
inQuotes = false
case r == ',' && !inQuotes:
// مصادرة فاصل، حفظ الحقل الحالي
fields = append(fields, current.String())
current.Reset()
default:
// حرف عادي
current.WriteRune(r)
}
}
// حفظ الحقل الأخير
fields = append(fields, current.String())
return fields
}
// تنظيف وتنسيق الحقول
func cleanFields(fields []string) []string {
cleaned := make([]string, len(fields))
for i, f := range fields {
cleaned[i] = strings.TrimSpace(f)
}
return cleaned
}
func main() {
// بيانات CSV محاكاة
csvData := []string{
`Alice,28,"Beijing, China"`,
`Bob,35,"New York, USA"`,
`Charlie,42,"London, UK"`,
}
fmt.Println("=== نتائج تحليل CSV ===")
for _, line := range csvData {
fields := parseCSVLine(line)
fields = cleanFields(fields)
fmt.Printf("الاسم: %-10s العمر: %-4s الموقع: %s\n",
fields[0], fields[1], fields[2])
}
// العملية العكسية: دمج شريحة في سطر CSV
record := []string{"David", "30", "Shanghai, China"}
csvLine := strings.Join(record, ",")
fmt.Println("\nسطر CSV المُنشأ:", csvLine)
}
المخرجات:
=== نتائج تحليل CSV ===
الاسم: Alice العمر: 28 الموقع: Beijing, China
الاسم: Bob العمر: 35 الموقع: New York, USA
الاسم: Charlie العمر: 42 الموقع: London, UK
سطر CSV المُنشأ: David,30,Shanghai, China
مثال: محرك القوالب (الصعوبة ⭐⭐⭐)
package main
import (
"fmt"
"strconv"
"strings"
)
// محرك قوالب بسيط: يستبدل {{key}} بالقيم المقابلة
func renderTemplate(template string, data map[string]string) string {
var result strings.Builder
result.Grow(len(template) * 2) // تقدير السعة
i := 0
for i < len(template) {
// البحث عن "{{"
if i+1 < len(template) && template[i] == '{' && template[i+1] == '{' {
// البحث عن "}}" المقابل
end := strings.Index(template[i+2:], "}}")
if end != -1 {
key := strings.TrimSpace(template[i+2 : i+2+end])
if value, ok := data[key]; ok {
result.WriteString(value)
} else {
// المفتاح غير موجود، الإبقاء كما هو
result.WriteString("{{" + key + "}}")
}
i += end + 4 // تخطي "}}"
continue
}
}
result.WriteByte(template[i])
i++
}
return result.String()
}
// تنسيق مخرجات الجدول
func formatTable(headers []string, rows [][]string) string {
// حساب أقصى عرض لكل عمود
colWidths := make([]int, len(headers))
for i, h := range headers {
colWidths[i] = len(h)
}
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) && len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}
var b strings.Builder
// كتابة الرأس
for i, h := range headers {
b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], h))
}
b.WriteString("\n")
// كتابة خط الفصل
for i := range headers {
b.WriteString(strings.Repeat("-", colWidths[i]) + "-+-")
}
b.WriteString("\n")
// كتابة صفوف البيانات
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) {
b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], cell))
}
}
b.WriteString("\n")
}
return b.String()
}
// تحويل سطر رقمي إلى تمثيلات أساس مختلفة
func toBases(numStr string) (map[string]string, error) {
num, err := strconv.ParseInt(numStr, 10, 64)
if err != nil {
return nil, err
}
return map[string]string{
"عشري": strconv.FormatInt(num, 10),
"ثنائي": strconv.FormatInt(num, 2),
"ثماني": strconv.FormatInt(num, 8),
"سداسي عشري": strconv.FormatInt(num, 16),
}, nil
}
func main() {
// 1. عرض القوالب
fmt.Println("=== عرض القوالب ===")
template := "Hello, {{name}}! Welcome to {{city}}. You have {{count}} new messages."
data := map[string]string{
"name": "Alice",
"city": "Beijing",
"count": "5",
}
fmt.Println(renderTemplate(template, data))
// 2. تنسيق الجدول
fmt.Println("\n=== تنسيق الجدول ===")
headers := []string{"الاسم", "العمر", "المدينة"}
rows := [][]string{
{"Alice", "28", "Beijing"},
{"Bob", "35", "New York"},
{"Charlie", "42", "London"},
}
fmt.Print(formatTable(headers, rows))
// 3. تحويل الأساس
fmt.Println("\n=== تحويل الأساس ===")
bases, _ := toBases("255")
for name, value := range bases {
fmt.Printf("%-12s: %s\n", name, value)
}
}
المخرجات:
=== عرض القوالب ===
Hello, Alice! Welcome to Beijing. You have 5 new messages.
=== تنسيق الجدول ===
الاسم | العمر | المدينة |
--------+-----+---------+-
Alice | 28 | Beijing |
Bob | 35 | New York|
Charlie | 42 | London |
=== تحويل الأساس ===
عشري : 255
ثنائي : 11111111
ثماني : 377
سداسي عشري : ff
سيناريوهات التطبيق العملي
السيناريو 1: محلل السجلات
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
// هيكل إدخال السجل
type LogEntry struct {
Timestamp string
Level string
Message string
Source string
}
// تحليل سطر سجل
// الصيغة: [2024-01-15 10:30:00] [ERROR] Database connection failed (db-service)
func parseLogLine(line string) (*LogEntry, error) {
entry := &LogEntry{}
// استخراج الطابع الزمني
if start := strings.Index(line, "["); start != -1 {
if end := strings.Index(line, "]"); end != -1 {
entry.Timestamp = line[start+1 : end]
line = line[end+1:]
}
}
// استخراج مستوى السجل
line = strings.TrimSpace(line)
if start := strings.Index(line, "["); start != -1 {
if end := strings.Index(line, "]"); end != -1 {
entry.Level = line[start+1 : end]
line = line[end+1:]
}
}
// استخراج الرسالة والمصدر
line = strings.TrimSpace(line)
if parenStart := strings.LastIndex(line, "("); parenStart != -1 {
if parenEnd := strings.LastIndex(line, ")"); parenEnd != -1 {
entry.Source = line[parenStart+1 : parenEnd]
entry.Message = strings.TrimSpace(line[:parenStart])
}
} else {
entry.Message = line
}
return entry, nil
}
// تحليل مستويات السجلات
func analyzeLogs(entries []LogEntry) map[string]int {
stats := make(map[string]int)
for _, e := range entries {
stats[strings.ToUpper(e.Level)]++
}
return stats
}
// تصفية السجلات حسب كلمة مفتاحية
func filterLogs(entries []LogEntry, keyword string) []LogEntry {
var filtered []LogEntry
keyword = strings.ToLower(keyword)
for _, e := range entries {
if strings.Contains(strings.ToLower(e.Message), keyword) {
filtered = append(filtered, e)
}
}
return filtered
}
func main() {
// بيانات سجلات محاكاة
logLines := []string{
"[2024-01-15 10:30:00] [INFO] Application started (main-service)",
"[2024-01-15 10:30:05] [INFO] Connected to database (db-service)",
"[2024-01-15 10:31:00] [WARN] High memory usage detected (monitor)",
"[2024-01-15 10:32:00] [ERROR] Database connection timeout (db-service)",
"[2024-01-15 10:32:01] [ERROR] Retry failed, switching to backup (db-service)",
"[2024-01-15 10:33:00] [INFO] Backup database connected (db-service)",
"[2024-01-15 10:35:00] [DEBUG] Cache cleared (cache-service)",
}
// تحليل جميع السجلات
var entries []LogEntry
for _, line := range logLines {
entry, err := parseLogLine(line)
if err == nil {
entries = append(entries, *entry)
}
}
// إحصائيات مستويات السجلات
fmt.Println("=== إحصائيات مستويات السجلات ===")
stats := analyzeLogs(entries)
for level, count := range stats {
fmt.Printf(" %s: %d إدخال\n", level, count)
}
// تصفية سجلات الأخطاء
fmt.Println("\n=== سجلات الأخطاء ===")
for _, e := range entries {
if strings.ToUpper(e.Level) == "ERROR" {
fmt.Printf(" %s | %s | %s\n", e.Timestamp, e.Message, e.Source)
}
}
// البحث بكلمة مفتاحية
fmt.Println("\n=== السجلات التي تحتوي على 'database' ===")
filtered := filterLogs(entries, "database")
for _, e := range filtered {
fmt.Printf(" [%s] %s\n", e.Level, e.Message)
}
}
المخرجات:
=== إحصائيات مستويات السجلات ===
INFO: 3 إدخال
WARN: 1 إدخال
ERROR: 2 إدخال
DEBUG: 1 إدخال
=== سجلات الأخطاء ===
2024-01-15 10:32:00 | Database connection timeout | db-service
2024-01-15 10:32:01 | Retry failed, switching to backup | db-service
=== السجلات التي تحتوي على 'database' ===
[INFO] Connected to database
[ERROR] Database connection timeout
[INFO] Backup database connected
السيناريو 2: التحقق من مدخلات المستخدم وتنظيفها
package main
import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
)
// تنظيف مدخلات المستخدم لاسم المستخدم
func sanitizeUsername(name string) (string, error) {
// إزالة المسافات من البداية والنهاية
name = strings.TrimSpace(name)
// التحقق من الطول
if len(name) < 3 {
return "", fmt.Errorf("اسم المستخدم قصير جداً (الحد الأدنى 3 أحرف)")
}
if len(name) > 20 {
return "", fmt.Errorf("اسم المستخدم طويل جداً (الحد الأقصى 20 حرفاً)")
}
// السماح فقط بالأحرف والأرقام والشرطة السفلية
var cleaned strings.Builder
for _, r := range name {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
cleaned.WriteRune(r)
}
}
result := cleaned.String()
if len(result) < 3 {
return "", fmt.Errorf("أحرف صالحة قليلة جداً")
}
return strings.ToLower(result), nil
}
// التحقق من رقم الهاتف وتحليله
func parsePhone(phone string) (string, error) {
// إزالة جميع المسافات والشرطات
phone = strings.ReplaceAll(phone, " ", "")
phone = strings.ReplaceAll(phone, "-", "")
// التحقق من البدء بـ +86
if strings.HasPrefix(phone, "+86") {
phone = phone[3:]
} else if strings.HasPrefix(phone, "86") {
phone = phone[2:]
}
// التحقق من الطول
if len(phone) != 11 {
return "", fmt.Errorf("طول رقم الهاتف غير صحيح: %d أرقام", len(phone))
}
// التحقق من أن جميع الأحرف أرقام
for _, r := range phone {
if !unicode.IsDigit(r) {
return "", fmt.Errorf("رقم الهاتف يحتوي على حرف غير رقمي: %c", r)
}
}
// التحقق من البدء بـ 1
if !strings.HasPrefix(phone, "1") {
return "", fmt.Errorf("رقم الهاتف يجب أن يبدأ بـ 1")
}
return phone, nil
}
// تحليل سلسلة الحجم بالوحدات
func parseSize(sizeStr string) (int64, error) {
sizeStr = strings.TrimSpace(strings.ToUpper(sizeStr))
// استخراج الأجزاء الرقمية والوحدات
var numPart strings.Builder
var unitPart strings.Builder
for _, r := range sizeStr {
if unicode.IsDigit(r) || r == '.' {
numPart.WriteRune(r)
} else if unicode.IsLetter(r) {
unitPart.WriteRune(r)
}
}
num, err := strconv.ParseFloat(numPart.String(), 64)
if err != nil {
return 0, fmt.Errorf("رقم غير صالح: %s", numPart.String())
}
// التحويل إلى بايتات بناءً على الوحدة
unit := unitPart.String()
multipliers := map[string]int64{
"B": 1,
"KB": 1024,
"MB": 1024 * 1024,
"GB": 1024 * 1024 * 1024,
"TB": 1024 * 1024 * 1024 * 1024,
}
multiplier, ok := multipliers[unit]
if !ok {
return 0, fmt.Errorf("وحدة غير معروفة: %s", unit)
}
return int64(num * float64(multiplier)), nil
}
func main() {
// اختبارات تنظيف اسم المستخدم
fmt.Println("=== التحقق من اسم المستخدم ===")
usernames := []string{" Alice_123 ", "ab", "A!@#B", "GoDeveloper2024"}
for _, u := range usernames {
result, err := sanitizeUsername(u)
if err != nil {
fmt.Printf(" %q ← خطأ: %v\n", u, err)
} else {
fmt.Printf(" %q ← %q\n", u, result)
}
}
// اختبارات تحليل رقم الهاتف
fmt.Println("\n=== تحليل رقم الهاتف ===")
phones := []string{"138 0013 8000", "+86-138-0013-8000", "12345", "23800138000"}
for _, p := range phones {
result, err := parsePhone(p)
if err != nil {
fmt.Printf(" %q ← خطأ: %v\n", p, err)
} else {
fmt.Printf(" %q ← %s\n", p, result)
}
}
// اختبارات تحليل حجم الملف
fmt.Println("\n=== تحليل حجم الملف ===")
sizes := []string{"1.5GB", "512MB", "1024KB", "100B", "2TB"}
for _, s := range sizes {
bytes, err := parseSize(s)
if err != nil {
fmt.Printf(" %s ← خطأ: %v\n", s, err)
} else {
fmt.Printf(" %s ← %d بايت\n", s, bytes)
}
}
}
المخرجات:
=== التحقق من اسم المستخدم ===
" Alice_123 " ← "alice_123"
"ab" ← خطأ: اسم المستخدم قصير جداً (الحد الأدنى 3 أحرف)
"A!@#B" ← خطأ: أحرف صالحة قليلة جداً
"GoDeveloper2024" ← "godeveloper2024"
=== تحليل رقم الهاتف ===
"138 0013 8000" ← 13800138000
"+86-138-0013-8000" ← 13800138000
"12345" ← خطأ: طول رقم الهاتف غير صحيح: 5 أرقام
"23800138000" ← خطأ: رقم الهاتف يجب أن يبدأ بـ 1
=== تحليل حجم الملف ===
1.5GB ← 1610612736 بايت
512MB ← 536870912 بايت
1024KB ← 1048576 بايت
100B ← 100 بايت
2TB ← 2199023255552 بايت
❓ أسئلة شائعة
س1: لماذا len("مرحبا") تعيد 10 بدلاً من 5؟
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "مرحبا"
// len() تعيد البايتات، وليس الأحرف
fmt.Println("len():", len(s)) // 10
// الأحرف العربية تأخذ 2 بايت لكل حرف في UTF-8
// م(2) + ر(2) + ح(2) + ب(2) + ا(2) = 10
// الطريقة الصحيحة للحصول على عدد الأحرف
fmt.Println("RuneCountInString():", utf8.RuneCountInString(s)) // 5
// أو استخدام range للعد
count := 0
for range s {
count++
}
fmt.Println("range count:", count) // 4
}
نقطة أساسية: عند التعامل مع أحرف متعددة البايتات مثل الصينية، استخدم دائماً utf8.RuneCountInString() أو range للحصول على العدد الحقيقي للأحرف.
س2: ربط النصوص — استخدام + أم strings.Builder؟
package main
import (
"fmt"
"strings"
)
func main() {
// ربط بسيط: استخدم + فقط (المحسن يحسنه)
s := "Hello" + " " + "World"
fmt.Println(s)
// ربط كثير: يجب استخدام strings.Builder
var builder strings.Builder
for i := 0; i < 10000; i++ {
builder.WriteString("a")
}
fmt.Println("الطول:", builder.Len())
// التخصيص المسبق يحسّن الأداء أكثر
var builder2 strings.Builder
builder2.Grow(10000) // تخصيص 10000 بايت مسبقاً
for i := 0; i < 10000; i++ {
builder2.WriteString("b")
}
fmt.Println("الطول:", builder2.Len())
}
قواعد عامة:
| السيناريو | النهج الموصى به |
|---|---|
| ربط 2-3 نصوص | + أو fmt.Sprintf |
| ربط في حلقة (عدد معروف) | strings.Builder + Grow() |
| ربط في حلقة (عدد غير معروف) | strings.Builder |
س3: كيف أتحقق مما إذا كان نص يحتوي فقط على أحرف محددة؟
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "Hello123"
// التحقق من أنه يحتوي فقط على أحرف وأرقام
isAlphanumeric := true
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
isAlphanumeric = false
break
}
}
fmt.Println("أحرف وأرقام فقط:", isAlphanumeric)
// التحقق من أنه يحتوي فقط على أحرف ASCII
isASCII := true
for _, r := range s {
if r > 127 {
isASCII = false
break
}
}
fmt.Println("ASCII فقط:", isASCII)
// التحقق من أنه يحتوي فقط على مجموعة أحرف محددة
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
containsOnlyAllowed := true
for _, r := range s {
if !strings.ContainsRune(allowed, r) {
containsOnlyAllowed = false
break
}
}
fmt.Println("ضمن النطاق المسموح:", containsOnlyAllowed)
}
س4: متى أستخدم strings.Contains مقابل التعبيرات النمطية؟
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
text := "My email is test@example.com, phone is 13800138000"
// بحث بسيط → استخدم حزمة strings (أسرع)
fmt.Println(strings.Contains(text, "example.com")) // true
// مطابقة أنماط → استخدم التعبيرات النمطية
emailRegex := regexp.MustCompile(`[\w.]+@[\w.]+\.\w+`)
fmt.Println("البريد:", emailRegex.FindString(text))
phoneRegex := regexp.MustCompile(`1[3-9]\d{9}`)
fmt.Println("الهاتف:", phoneRegex.FindString(text))
}
إرشادات:
- بحث نص ثابت: استخدم حزمة strings — أداء أفضل
- مطابقة أنماط (مثل البريد، صيغة الهاتف): استخدم حزمة
regexp - تجنب تجميع التعبيرات النمطية المتكرر داخل الحلقات؛ قم بتجميعها مسبقاً
📖 ملخص
| الموضوع | المحتوى الأساسي |
|---|---|
| حزمة strings | Contains, HasPrefix, HasSuffix, Index, Replace, Split, Join, Trim, ToUpper, ToLower, Count, Repeat, Fields |
| حزمة strconv | Atoi, Itoa, ParseBool, ParseFloat, FormatInt, FormatFloat |
| unicode/utf8 | RuneCountInString, ValidString, unicode.IsLetter/IsDigit/IsSpace |
| strings.Builder | WriteString, WriteRune, WriteByte, Grow, String, Len |
| المبادئ الأساسية | النصوص غير قابلة للتعديل، len تعيد البايتات، range يكرر حسب rune، استخدم Builder للربط الكثير |
📝 تمارين
التمرين 1: أساسي — عكس النص
اكتب دالة reverseString(s string) string تعكس نصاً. يجب أن تتعامل بشكل صحيح مع الأحرف الصينية.
تلميح: لا يمكنك ببساطة تحويل النص إلى []byte وعكسه، لأن الأحرف الصينية تشغل بايتات متعددة.
// النتائج المتوقعة
reverseString("Hello") // "olleH"
reverseString("مرحبا") // "ابحرم"
التمرين 2: متوسط — تحويل CamelCase و Snake_case
اكتب دالتين:
camelToSnake(s string) string: تحويل camelCase إلى snake_casesnakeToCamel(s string) string: تحويل snake_case إلى camelCase
تلميح: استخدم unicode.IsUpper لتحديد مواضع الأحرف الكبيرة.
// النتائج المتوقعة
camelToSnake("helloWorld") // "hello_world"
camelToSnake("HTTPResponse") // "http_response"
snakeToCamel("hello_world") // "helloWorld"
snakeToCamel("http_response") // "httpResponse"
التمرين 3: تحدي — مستخرج عناوين Markdown بسيط
اكتب دالة extractHeadings(md string) []string تستخرج جميع العناوين من مستند Markdown.
تلميح: العناوين تبدأ بـ #، وعدد رموز # يشير إلى مستوى العنوان.
// المدخلات
md := `# Heading 1
This is body text
## Heading 2
### Heading 3
## Another Heading 2`
// المخرجات المتوقعة
// ["# Heading 1", "## Heading 2", "### Heading 3", "## Another Heading 2"]
الدرس التالي
تهانينا على إكمال معالجة النصوص! في الدرس التالي، سنتعلم عمليات الإدخال/الإخراج للملفات — كيفية قراءة وكتابة الملفات، والتعامل مع المجلدات، واستخدام الإدخال/الإخراج المؤقت للأداء الأفضل.



