Go: المصفوفات والشرائح في لغة Go

تُعد الشرائح جوهر الطريقة التي تتعامل بها لغة Go مع بيانات المجموعات — فهي «طرق عرض» خفيفة الوزن للمصفوفات، وقدرتها على تغيير الحجم ديناميكيًا تجعل عمليات المجموعات سريعة ومرنة في آن واحد.

نادرًا ما تُستخدم المصفوفات بشكل مباشر في لغة Go (لأن طولها ثابت)، لكن الشرائح موجودة في كل مكان. لفهم الشرائح، يجب عليك أولاً فهم المصفوفات — ففي جوهرها، الشريحة هي مؤشر إلى مصفوفة بالإضافة إلى طولها وسعتها. في هذا الدرس، ستتقن جميع المفاهيم الأساسية لأنواع المجموعات في لغة Go.

1. ستتعلم



2. قصة حقيقية لمهندس جمع البيانات

(1) المشكلة: لا يمكن للمصفوفة ذات الحجم الثابت استيعاب البيانات الديناميكية

أليس هي مهندسة في مجال جمع البيانات. وقد احتاجت مؤخرًا إلى كتابة برنامج زحف على الويب:

"أحتاج إلى استرداد 10,000 سجل من كل مصدر من مصادر البيانات الخمسة وتخزينها في الذاكرة لإزالة التكرارات. في البداية، استخدمت قوائم Python — مجرد عملية «إلحاق» بسيطة وانتهى الأمر. بعد التحول إلى لغة Go، استخدمت مصفوفة ذات طول ثابت مثل [10000]int، لكن البرنامج تعطل عند السجل الأول مباشرةً: «المؤشر خارج النطاق»."

فتحت شفرة لغة «Go» التي كتبتها:

GO
// Version 1: Fixed-length array; cannot grow
var records [10000]int
for i := 0; i < 50000; i++ {
    records[i] = i  // ❌ i >= 10000 causes panic!
}

قال المدير: «يتغير عدد مصادر البيانات يوميًا، لذا فإن مصفوفتك ذات الطول الثابت لن تعمل». عندها فقط أدركت أليس أن مصفوفات لغة Go هي أنواع قيمية، وأن الطول جزء من النوع — فـ [5]int و [10]int نوعان مختلفان.

(2) الحل بلغة Go: استخدام الشرائح

GO
// data_collector.go
package main

import "fmt"

func main() {
    // Slices: Dynamic length, automatic resizing
    var records []int  // nil slice, can append

    // Simulate retrieving 10,000 records from each of 5 data sources
    for source := 1; source <= 5; source++ {
        for i := 0; i < 10000; i++ {
            value := source*10000 + i
            records = append(records, value)  // auto-resize
        }
    }

    fmt.Printf("Total number of records: %d\n", len(records))
    fmt.Printf("Underlying capacity: %d\n", cap(records))
    fmt.Printf("Top 3: %v\n", records[:3])
    fmt.Printf("Last 3: %v\n", records[len(records)-3:])
}

الناتج:

TEXT 📖 للعرض فقط
Total number of records: 50000
Underlying capacity: 65536
Top 3: [10000 10001 10002]
Last 3: [59997 59998 59999]

(3) الأداء: المصفوفة مقابل الشريحة

البعد مصفوفة [n]T شريحة []T
الطول ثابت في وقت التحويل البرمجي متغير في وقت التشغيل
النوع [5]int[10]int []int عام
تمرير المعلمات تمرير القيمة (نسخ المصفوفة بأكملها) تمرير المرجع (24 بايت فقط)
تغيير الحجم غير مدعوم append تلقائي
حالة الاستخدام الحجم الثابت (مثل المخزن المؤقت) 99% من الحالات
💡 نصيحة: في لغة Go، تُستخدم الشرائح (slices) في 99% من سيناريوهات «المجموعات»؛ أما المصفوفات (arrays) فتُستخدم فقط عندما يكون الحجم ثابتًا (مثل المخزن المؤقت سعة 32 بايت لـ SHA-256) أو عندما تكون دلالات القيم مطلوبة.



3. المصفوفة

(1) تعريف المصفوفات وتهيئتها

GO
package main

import "fmt"

func main() {
    // Method 1: Declaration + Initialization
    var a1 [5]int = [5]int{1, 2, 3, 4, 5}

    // Method 2: Type Inference
    a2 := [5]int{1, 2, 3, 4, 5}

    // Method 3: Partial Initialization (with the Rest Set to Zero)
    a3 := [5]int{1, 2}  // [1 2 0 0 0]

    // Method 4: Use ... to let the compiler infer the length
    a4 := [...]int{1, 2, 3, 4}  // length 4

    // Method 5: Specifying Index Initialization
    a5 := [5]int{0: 10, 4: 50}  // [10 0 0 0 50]

    fmt.Println(a1, a2, a3, a4, a5)
}

الناتج:

TEXT 📖 للعرض فقط
[1 2 3 4 5] [1 2 3 4 5] [1 2 0 0 0] [1 2 3 4] [10 0 0 0 50]

(2) التكرار عبر المصفوفات

GO
package main

import "fmt"

func main() {
    nums := [4]string{"Alice", "Bob", "Charlie", "Dave"}

    // Method 1: for loop
    for i := 0; i < len(nums); i++ {
        fmt.Printf("%d: %s\n", i, nums[i])
    }

    // Method 2: for range
    for i, name := range nums {
        fmt.Printf("[%d] %s\n", i, name)
    }
}

(3) المصفوفات هي أنواع قيمية

GO
package main

import "fmt"

func modify(arr [3]int) {
    arr[0] = 999  // modifies the copy
    fmt.Printf("Inside function: %v\n", arr)
}

func main() {
    a := [3]int{1, 2, 3}
    modify(a)
    fmt.Printf("Outside function: %v\n", a)  // original array unchanged
}

الناتج:

TEXT 📖 للعرض فقط
Inside function: [999 2 3]
Outside function: [1 2 3]
🔥 خطأ شائع: المصفوفات في لغة Go هي أنواع قيمية، لذا فإن تمريرها كمعلمات يؤدي إلى نسخ المصفوفة بأكملها. تمرير مصفوفة تحتوي على مليون عنصر كمعلمة = نسخ مليون عنصر = كارثة في الأداء. استخدم الشرائح بدلاً من ذلك.

▶ مثال: مقارنة المصفوفات (فقط للمصفوفات من نفس النوع والطول)

GO
package main

import "fmt"

func main() {
    a := [3]int{1, 2, 3}
    b := [3]int{1, 2, 3}
    c := [3]int{1, 2, 4}

    fmt.Println(a == b)  // true
    fmt.Println(a == c)  // false

    // Items of different lengths or types cannot be compared.
    // d := [4]int{1, 2, 3, 4}
    // fmt.Println(a == d)  // ❌ mismatched types
}
▶ جرّب الكود

4. أربع طرق لإنشاء شرائح

(1) الشريحة = «عرض» لمصفوفة

100%
graph TB
    subgraph Slice["Slice (24 bytes)"]
        P["ptr<br/>points to the underlying array"]
        L["len<br/>length"]
        C["cap<br/>capacity"]
    end
    subgraph Array["Underlying array"]
        A0["[0]"]
        A1["[1]"]
        A2["[2]"]
        A3["[3]"]
        A4["[4]"]
        A5["[5]"]
    end
    P --> A1
    P -.-> A2
    P -.-> A3

الشريحة هي مرجع إلى المصفوفة الأساسية — وهي تتكون من 24 بايتًا فقط (ptr + len + cap). ويمكن لعدة شرائح أن تتشارك في نفس المصفوفة الأساسية.

(2) الطريقة الأولى للتعريف: الإعلان المباشر عن شريحة ذات قيمة nil

GO
var s []int  // nil slice, len=0, cap=0
// You can append, but you cannot access it using `s[0]`.

(3) طريقة الإنشاء 2: حرفية

GO
s := []int{1, 2, 3, 4, 5}  // create and initialize
// Base array = [1 2 3 4 5], len = cap = 5

(4) طريقة الإنشاء 3: make (تحديد الطول والحد الأقصى)

GO
// Syntax: make([]T, len, cap)
s := make([]int, 5)       // len=5, cap=5, all zero values [0 0 0 0 0]
s := make([]int, 3, 10)   // len=3, cap=10

▶ مثال: مقارنة بين 4 طرق لاستخدام الفعل "make"

GO
package main

import "fmt"

func main() {
    // Method 1: nil slice
    var s1 []int
    fmt.Printf("s1: len=%d cap=%d nil=%v\n", len(s1), cap(s1), s1 == nil)

    // Method 2: Literal
    s2 := []int{1, 2, 3}
    fmt.Printf("s2: len=%d cap=%d %v\n", len(s2), cap(s2), s2)

    // Method 3: Set the length
    s3 := make([]int, 5)
    fmt.Printf("s3: len=%d cap=%d %v\n", len(s3), cap(s3), s3)

    // Method 4: make (length + capacity)
    s4 := make([]int, 3, 10)
    fmt.Printf("s4: len=%d cap=%d %v\n", len(s4), cap(s4), s4)

    // Method 5: Slicing from an array
    arr := [5]int{10, 20, 30, 40, 50}
    s5 := arr[1:4]  // [20 30 40], len=3 cap=4 (4 remaining elements)
    fmt.Printf("s5: len=%d cap=%d %v\n", len(s5), cap(s5), s5)
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
s1: len=0 cap=0 nil=true
s2: len=3 cap=3 [1 2 3]
s3: len=5 cap=5 [0 0 0 0 0]
s4: len=3 cap=10 [0 0 0]
s5: len=3 cap=4 [20 30 40]

(6) مرجع سريع: 4 طرق للإنشاء

الطريقة الصيغة حالات الاستخدام
شريحة فارغة var s []T التهيئة المؤجلة
حرفي []T{1, 2, 3} جميع العناصر معروفة
make make([]T, len, cap) تخصيص السعة مسبقًا
تقطيع المصفوفات arr[low:high] عرض المصفوفة الفرعية


5. len() و cap()

(1) العلاقة بين len و cap

GO
package main

import "fmt"

func main() {
    s := make([]int, 3, 10)  // len=3, cap=10

    fmt.Printf("Initial: len=%d cap=%d\n", len(s), cap(s))
    // len=3: You can access s[0], s[1], and s[2]
    // cap=10: The underlying array has 10 elements, with room for 7 more to be appended
}

(2) معنى كلمة «cap»

cap - len = عدد العناصر التي لا يزال من الممكن إضافتها دون تغيير الحجم. إذا تجاوز هذا العدد cap، فسيقوم append بتخصيص مصفوفة أساسية جديدة (عادةً ما يضاعف الحجم).

▶ مثال: الفرق الفعلي بين len وcap

GO
package main

import "fmt"

func main() {
    s := make([]int, 0, 5)  // len=0, cap=5

    for i := 1; i <= 8; i++ {
        s = append(s, i)
        fmt.Printf("append %d: len=%d cap=%d %v\n", i, len(s), cap(s), s)
    }
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
append 1: len=1 cap=5 [1]
append 2: len=2 cap=5 [1 2]
append 3: len=3 cap=5 [1 2 3]
append 4: len=4 cap=5 [1 2 3 4]
append 5: len=5 cap=5 [1 2 3 4 5]
append 6: len=6 cap=10 [1 2 3 4 5 6]  ← cap doubled
append 7: len=7 cap=10 [1 2 3 4 5 6 7]
append 8: len=8 cap=10 [1 2 3 4 5 6 7 8]
💡 نصيحة: استراتيجية Go لتغيير الحجم: عند len == cap، يكون الحد الأقصى الجديد مساوياً للحد الأقصى القديم * 2 (للشرائح الصغيرة) أو للحد الأقصى القديم * 1.25 (للشرائح الكبيرة، التي يزيد عددها تقريباً عن 256).



6. تغيير الحجم الديناميكي القائم على الإضافة

(1) أساسيات append

GO
package main

import "fmt"

func main() {
    var s []int  // nil

    s = append(s, 1)       // [1]
    s = append(s, 2, 3, 4) // [1 2 3 4]

    // Batch append: Splitting a slice
    s2 := []int{5, 6, 7}
    s = append(s, s2...)   // [1 2 3 4 5 6 7]

    fmt.Println(s)
}

الناتج:

TEXT 📖 للعرض فقط
[1 2 3 4 5 6 7]

▶ مثال: حذف العناصر باستخدام append (بدون وظيفة delete المدمجة)

GO
package main

import "fmt"

// Delete the element at index i
func removeAt(s []int, i int) []int {
    // Concatenation: s[:i] + s[i+1:]
    return append(s[:i], s[i+1:]...)
}

func main() {
    s := []int{1, 2, 3, 4, 5}
    s = removeAt(s, 2)  // delete 3
    fmt.Println(s)       // [1 2 4 5]
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
[1 2 4 5]

(3) عيب append: قد يؤدي إلى تعديل المصفوفة الأصلية

GO
package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5}
    s2 := s[:3]  // [1 2 3], shares underlying array

    s2 = append(s2, 99)  // no resize, modified s's underlying array!
    fmt.Println("s:", s)    // [1 2 3 99 5]
    fmt.Println("s2:", s2)  // [1 2 3 99]
}

الناتج:

TEXT 📖 للعرض فقط
s: [1 2 3 99 5]
s2: [1 2 3 99]
🔥 خطأ شائع: هذا أحد أكثر المزالق الكلاسيكية في لغة Go — حيث تشترك الشرائح في المصفوفة الأساسية، لذا فإن استدعاء append قد يؤثر على الشرائح الأخرى. استخدم copy() أو فرض تغيير الحجم عندما يتطلب الأمر عزلًا صارمًا.



7. شريحة [الحد الأدنى:الحد الأعلى:الحد الأقصى]

(1) ثلاثة أنواع من تعبيرات الشرائح

GO
package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}

    // Form 1: [low:high] — Takes [low, high)
    s1 := arr[1:4]
    fmt.Printf("arr[1:4] = %v, len=%d cap=%d\n", s1, len(s1), cap(s1))

    // Format 2: [low:] — From low to the end
    s2 := arr[2:]
    fmt.Printf("arr[2:]  = %v, len=%d cap=%d\n", s2, len(s2), cap(s2))

    // Form 3: [:high] — From the beginning to high
    s3 := arr[:3]
    fmt.Printf("arr[:3]  = %v, len=%d cap=%d\n", s3, len(s3), cap(s3))

    // Format 4: [low:high:max] — Sets a cap (to prevent unexpected additions from affecting the original array)
    s4 := arr[1:3:4]
    fmt.Printf("arr[1:3:4] = %v, len=%d cap=%d\n", s4, len(s4), cap(s4))
}

الناتج:

TEXT 📖 للعرض فقط
arr[1:4] = [20 30 40], len=3 cap=4
arr[2:]  = [30 40 50], len=3 cap=3
arr[:3]  = [10 20 30], len=3 cap=5
arr[1:3:4] = [20 30], len=2 cap=3

(2) الغرض من [الحد الأدنى:الحد الأعلى:الحد الأقصى]

يضع max حدًا لمنع append من الكتابة فوق أجزاء أخرى من المصفوفة الأصلية:

GO
package main

import "fmt"

func main() {
    arr := [5]int{10, 20, 30, 40, 50}

    // No limit on max: cap=4; after an append operation, arr[3] will be overwritten
    s1 := arr[1:3]  // [20 30], cap=4
    s1 = append(s1, 999)
    fmt.Println("arr:", arr)  // [10 20 30 999 50] ← overwritten!

    // Limit max=3: cap=2; append creates a new array
    s2 := arr[1:3:3]  // [20 30], cap=2
    s2 = append(s2, 888)
    fmt.Println("arr:", arr)  // [10 20 30 999 50] ← unchanged
    fmt.Println("s2:", s2)    // [20 30 888]
}

الناتج:

TEXT 📖 للعرض فقط
arr: [10 20 30 999 50]
arr: [10 20 30 999 50]
s2: [20 30 888]


8. مخاطر مشاركة المصفوفة الأساسية عند التقطيع (نقطة أساسية)

(1) المأزق الأول: يؤدي تعديل عنصر شريحة إلى التأثير على جميع الشرائح

GO
package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[0:3]  // [1 2 3]
    s2 := arr[2:5]  // [3 4 5]

    s1[2] = 999  // same underlying array!
    fmt.Println("s1:", s1)  // [1 2 999]
    fmt.Println("s2:", s2)  // [999 4 5]
    fmt.Println("arr:", arr) // [1 2 999 4 5]
}

(2) المشكلة الثانية: يؤدي استخدام append مع for range إلى حدوث حلقة لا نهائية

GO
package main

import "fmt"

func main() {
    s := []int{1, 2, 3}
    for _, v := range s {
        s = append(s, v*10)  // dangerous: s is growing
    }
    fmt.Println(s)  // output is undefined
}
🔥 خطأ شائع: لا تقم أبدًا بتعديل الشريحة نفسها أثناء حلقة for range. قبل الإصدار 1.22 من لغة Go، كانت s في for i, v := range s تمثل لقطة لحجم الشريحة عند بداية الحلقة؛ وقد عالج الإصدار 1.22 من لغة Go هذه المشكلة في بعض الحالات، لكن لا يزال من المستحسن توخي الحذر.

(3) المأزق الثالث: يتم تعديل الشرائح التي يتم تمريرها كمعلمات للدالة بشكل غير متوقع

GO
package main

import "fmt"

// Slicing uses pass-by-reference, so changes made within the function affect the outside.
func addElement(s []int) {
    s = append(s, 100)  // may modify the original array (if no resize)
    fmt.Printf("Inside function: %v\n", s)
}

func main() {
    s := make([]int, 3, 10)
    s[0], s[1], s[2] = 1, 2, 3

    addElement(s)
    fmt.Printf("Outside function: %v\n", s)  // depends on whether resize occurred
}

(4) استخدم copy() لحل مشكلات المشاركة

GO
package main

import "fmt"

func main() {
    arr := [5]int{1, 2, 3, 4, 5}
    s1 := arr[0:3]

    // Create a separate copy
    s2 := make([]int, len(s1))
    copy(s2, s1)

    s1[0] = 999
    fmt.Println("s1:", s1)  // [999 2 3]
    fmt.Println("s2:", s2)  // [1 2 3] ← unaffected
}

▶ مثال: استخدام "نسخ + إلحاق" لإدراج شريحة

GO
package main

import "fmt"

// Insert element val at index i
func insert(s []int, i int, val int) []int {
    // 1. Add 1 position
    s = append(s, 0)

    // 2. Shift the element at index i and all subsequent elements one position to the right
    copy(s[i+1:], s[i:])

    // 3. Add a new element
    s[i] = val

    return s
}

func main() {
    s := []int{1, 2, 4, 5}
    s = insert(s, 2, 3)
    fmt.Println(s)  // [1 2 3 4 5]
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
[1 2 3 4 5]


9. مثال كامل: أداة جمع البيانات الديناميكية

اجمع بين جميع ميزات الشرائح لإنشاء أداة لـ جمع البيانات من مصادر متعددة + إزالة التكرار + التصفية:

GO
// collector.go
package main

import (
    "fmt"
    "strings"
)

// Data Source Simulation Functions
func fetchFromSource(sourceName string) []string {
    data := map[string][]string{
        "GitHub": {"repo:go", "repo:docker", "issue:bug", "repo:go"},  // last one is duplicate
        "NPM":    {"pkg:react", "pkg:vue", "pkg:react"},              // duplicate
        "PyPI":   {"pkg:requests", "pkg:flask"},
    }
    return data[sourceName]
}

// Removing Duplicates: Using `map` to Create a Hash Set
func deduplicate(data []string) []string {
    seen := make(map[string]bool)
    result := make([]string, 0, len(data))

    for _, item := range data {
        if !seen[item] {
            seen[item] = true
            result = append(result, item)
        }
    }
    return result
}

// Filter: Keep only those starting with "repo:"
func filterRepos(data []string) []string {
    result := make([]string, 0)
    for _, item := range data {
        if strings.HasPrefix(item, "repo:") {
            result = append(result, item)
        }
    }
    return result
}

// Pipeline: Combining Multiple Steps
func collectPipeline() (total, unique, repos int, sources []string) {
    defer func() {
        // Naming Return Values + Defer Collection Statistics
        fmt.Printf("\n[Collection Complete] total=%d unique=%d repos=%d sources=%v\n",
            total, unique, repos, sources)
    }()

    allSources := []string{"GitHub", "NPM", "PyPI"}
    var allData []string

    for _, src := range allSources {
        sources = append(sources, src)
        data := fetchFromSource(src)
        allData = append(allData, data...)
        total += len(data)
    }

    uniqueData := deduplicate(allData)
    unique = len(uniqueData)

    repoData := filterRepos(uniqueData)
    repos = len(repoData)

    fmt.Println("\n=== Deduped Data ===")
    for _, item := range uniqueData {
        fmt.Printf("  %s\n", item)
    }

    fmt.Println("\n=== Filtering Repo Data ===")
    for _, item := range repoData {
        fmt.Printf("  %s\n", item)
    }

    return total, unique, repos, sources
}

func main() {
    collectPipeline()
}

النتيجة المتوقعة:

TEXT 📖 للعرض فقط
=== Deduped Data ===
  repo:go
  repo:docker
  issue:bug
  pkg:react
  pkg:vue
  pkg:requests
  pkg:flask

=== Filtering Repo Data ===
  repo:go
  repo:docker

[Collection Complete] total=9 unique=7 repos=2 sources=[GitHub NPM PyPI]
🔥 خطأ شائع: استخدم make([]string, 0, len(data)) لتخصيص السعة مسبقًا وتجنب تغيير الحجم بشكل متكرر باستخدام append. هذه تقنية أساسية لتحسين الأداء.



❓ أسئلة شائعة

س ما هي العلاقة الأساسية بين المصفوفات والشرائح؟
ج الشريحة هي في الأساس عرض لمصفوفة. تتكون الشريحة من 24 بايت: مؤشر من 8 بايت (يشير إلى عنصر في المصفوفة) + طول من 8 بايت + سعة من 8 بايت. يمكن أن تشترك عدة شرائح في نفس المصفوفة الأساسية.
س هل تؤدي append دائمًا إلى تغيير حجم المصفوفة؟
ج ليس بالضرورة. لا يتغير حجم المصفوفة (حيث يتم تخصيص مصفوفة جديدة ونسخ المحتويات مرتين) إلا عند استخدام len == cap. أما إذا كانت هناك مساحة متوفرة، يتم كتابة العناصر مباشرةً إلى المصفوفة الأصلية.
س هل يتم تمرير الشريحة إلى الدالة بالقيمة أم بالإشارة؟
ج يتم تمرير الشريحة نفسها (24 بايت) بالقيمة، لكن المصفوفة الأساسية التي تشير إليها تكون مشتركة. ولذلك، فإن تعديل العناصر داخل الدالة يؤثر على الشريحة الخارجية، لكن ما إذا كان append يؤثر على الشريحة الخارجية أم لا يعتمد على ما إذا كان قد تم تغيير حجم الشريحة أم لا.
س متى يكون المعامل max بالصيغة [low:high:max] مطلوبًا؟
ج استخدمه عندما تريد تحديد حجم شريحة ما لمنع عمليات append من التأثير على العناصر الأخرى في المصفوفة الأصلية. سيناريو شائع: إنشاء شريحة فرعية من شريحة أكبر، ثم إجراء عملية append.
س هل توجد طريقة مدمجة لإزالة العناصر من شريحة؟
ج لا. على عكس لغة Java، لا تحتوي لغة Go على list.remove(i). بل تُنفذ لغة Go عملية الإزالة باستخدام append(s[:i], s[i+1:]...) (ولكن هذا لا يحافظ على الترتيب). إذا كنت ترغب في الحفاظ على الترتيب، فستحتاج إلى إعادة ترتيب العناصر يدويًّا.
س كيف يمكنني نسخ شريحة؟
ج هناك ثلاث طرق: (1) copy(dst, src) — تنسخ min(len(dst), len(src)) عنصرًا؛ (2) append([]T(nil), src...) — تنسخ جميع العناصر؛ (3) يدويًّا باستخدام for range.
س ما الفرق في الأداء بين الشرائح والمصفوفات عند استخدام range؟
ج يؤدي استخدام range على مصفوفة إلى نسخ المصفوفة بأكملها (أداء ضعيف)؛ بينما يؤدي استخدام range على شريحة إلى نسخ 24 بايت فقط (أداء جيد). ولهذا السبب تُعد الشرائح المعيار الفعلي لـ«المجموعات».

📖 ملخص


📝 تمارين

  1. المشكلة الأساسية (الصعوبة ⭐): استخدم الشرائح لتنفيذ دالة reverse([]int) []int تعمل على عكس ترتيب عناصر الشريحة. تلميح: استخدم حلقة for مع متغير مؤقت، أو قم بتبديل العناصر في مكانها. حالة الاختبار: reverse([]int{1,2,3,4,5})[5 4 3 2 1].

  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): قم بتنفيذ الدالة uniqueSorted(nums []int) []int: إزالة التكرارات والترتيب تصاعديًا. تلميح: قم أولاً بترتيب المصفوفة باستخدام sort.Ints، ثم قم بالتكرار عبرها لإزالة التكرارات. إذا كان المدخلات هي []int{3, 1, 4, 1, 5, 9, 2, 6, 5}، فيجب أن تكون المخرجات [1 2 3 4 5 6 9].

  3. مشكلة التحدي (درجة الصعوبة ⭐⭐⭐): قم بتنفيذ خوارزمية الحد الأقصى للنافذة المنزلقة: maxSlidingWindow(nums []int, k int) []int. على سبيل المثال، nums=[1,3,-1,-3,5,3,6,7], k=3 → الناتج [3,3,5,5,6,7]. المتطلبات: (1) التعقيد الزمني O(n)؛ (2) استخدم deque (الذي يمكن محاكاته باستخدام []int) للحفاظ على النافذة؛ (3) اشرح لماذا سيؤدي التمرير المباشر إلى انتهاء المهلة (O(n*k)).

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%