framework icon indicating copy to clipboard operation
framework copied to clipboard

feat: laravel collection

Open ahmed3mar opened this issue 7 months ago • 3 comments

📑 Description

Closes https://github.com/goravel/goravel/issues/566

Basic Usage

Creating Collections

// Create from variadic arguments
numbers := collect.New(1, 2, 3, 4, 5)

// Create from slice
items := []string{"apple", "banana", "cherry"}
fruits := collect.Of(items)

Basic Operations

numbers := collect.New(1, 2, 3, 4, 5)

// Get count
fmt.Println(numbers.Count()) // 5

// Get first/last
fmt.Println(*numbers.First()) // 1
fmt.Println(*numbers.Last())  // 5

// Check if empty
fmt.Println(numbers.IsEmpty()) // false

// Get all items
fmt.Println(numbers.All()) // [1 2 3 4 5]

Filtering and Mapping

numbers := collect.New(1, 2, 3, 4, 5, 6)

// Filter even numbers
evens := numbers.Filter(func(n int, _ int) bool {
    return n%2 == 0
})
fmt.Println(evens.All()) // [2 4 6]

// Map to double values (Laravel-style method)
doubled := numbers.Map(func(n int, _ int) interface{} {
    return n * 2
})
fmt.Println(doubled.All()) // [2 4 6 8 10 12]

// Map to string representation
strings := numbers.Map(func(n int, i int) interface{} {
    return fmt.Sprintf("item_%d_%d", i, n)
})
fmt.Println(strings.All()) // [item_0_1 item_1_2 item_2_3 item_3_4 item_4_5 item_5_6]

### Working with Structs

```go
type User struct {
    ID   int
    Name string
    Age  int
}

users := collect.Of([]User{
    {ID: 1, Name: "Alice", Age: 25},
    {ID: 2, Name: "Bob", Age: 30},
    {ID: 3, Name: "Charlie", Age: 25},
})

// Filter by struct field using different Where patterns
youngUsers := users.Where("Age", "=", 25)        // 3 parameters
youngUsers = users.Where("Age", 25)              // 2 parameters (implies '=')
fmt.Println(youngUsers.Count()) // 2

// Using callback function
adultUsers := users.Where(func(u User) bool {
    return u.Age >= 18
})

// Using different operators
olderUsers := users.Where("Age", ">", 25)
notCharlie := users.Where("Name", "!=", "Charlie")

// Handling null values
activeUsers := users.Where("DeletedAt", "=", nil)    // Find non-deleted users
deletedUsers := users.Where("DeletedAt", "!=", nil)  // Find deleted users

// Group by field
grouped := users.GroupBy(func(u User) string {
    return fmt.Sprintf("%d", u.Age)
})
fmt.Println(len(grouped)) // 2 groups

// Pluck field values
names := users.Pluck("Name")
fmt.Println(names.Count()) // 3

// Sort by field
sorted := users.SortBy(func(u User) string {
    return u.Name
})
fmt.Println(sorted.First().Name) // "Alice"

Aggregation Methods

numbers := collect.New(1, 2, 3, 4, 5)

// Sum
sum := numbers.Sum(func(n int) float64 {
    return float64(n)
})
fmt.Println(sum) // 15.0

// Average
avg := numbers.Avg(func(n int) float64 {
    return float64(n)
})
fmt.Println(avg) // 3.0

// Min/Max
min := numbers.Min(func(n int) float64 { return float64(n) })
max := numbers.Max(func(n int) float64 { return float64(n) })
fmt.Println(min, max) // 1.0 5.0

Conditional Operations

numbers := collect.New(1, 2, 3, 4, 5)

// Conditional transformations
result := numbers.
    When(true, func(c *collect.Collection[int]) *collect.Collection[int] {
        return c.Filter(func(n int, _ int) bool { return n > 2 })
    }).
    Unless(false, func(c *collect.Collection[int]) *collect.Collection[int] {
        return c.Take(2)
    })

fmt.Println(result.All()) // [3 4]

// Tap for side effects
numbers.Tap(func(c *collect.Collection[int]) {
    fmt.Println("Processing", c.Count(), "items")
})

Available Methods

Core Methods (Alphabetical)

  • After(value) - Get item after given value
  • All() - Get all items as slice
  • Average(keyFunc) - Calculate average using key function
  • Before(value) - Get item before given value
  • Chunk(size) - Split into chunks of given size
  • Clone() - Create a copy of the collection
  • Collapse() - Collapse nested arrays into single array
  • Combine(keys) - Combine with keys to create map
  • Contains(value) - Check if collection contains value
  • Count() - Get item count
  • Diff(other) - Get difference with another collection
  • Each(func) - Iterate over each item
  • Every(predicate) - Check if all items match predicate
  • Filter(predicate) - Filter items by predicate
  • First() - Get first item
  • Flatten() - Flatten nested structures
  • GroupBy(keyFunc) - Group items by key function
  • Intersect(other) - Get intersection with another collection
  • IsEmpty() - Check if collection is empty
  • IsNotEmpty() - Check if collection is not empty
  • Join(separator) - Join items with separator
  • Last() - Get last item
  • Map(func) - Transform each item with a function (returns Collection[interface{}])
  • Merge(other) - Merge with another collection
  • Partition(predicate) - Split into two collections by predicate
  • Pluck(field) - Extract field values
  • Push(items...) - Add items to end
  • Reverse() - Reverse order
  • Search(value) - Find index of value
  • Slice(start, length) - Get slice of items
  • Sort(lessFunc) - Sort by comparison function
  • SortBy(keyFunc) - Sort by key function
  • Sum(keyFunc) - Calculate sum using key function
  • Take(n) - Take first n items
  • Unique() - Get unique items
  • Where(field, operator, value) - Filter by field comparison
  • Zip(other) - Zip with another collection

Where Method - Laravel-style Filtering

The Where method supports multiple patterns for flexible filtering:

type User struct {
    ID        int
    Name      string
    Age       int
    Country   string
    Balance   float64
    DeletedAt *time.Time
}

users := collect.Of([]User{...})

// 1. Two parameters (field, value) - implies '=' operator
frenchUsers := users.Where("Country", "FR")
youngUsers := users.Where("Age", 25)

// 2. Three parameters (field, operator, value)
richUsers := users.Where("Balance", ">", 100.0)
nonFrenchUsers := users.Where("Country", "!=", "FR")
seniorUsers := users.Where("Age", ">=", 65)

// 3. Single parameter (callback function)
customFilter := users.Where(func(u User) bool {
    return u.Age > 18 && u.Country == "US"
})

// 4. Null comparisons
activeUsers := users.Where("DeletedAt", "=", nil)
deletedUsers := users.Where("DeletedAt", "!=", nil)

// 5. String operations
nameContains := users.Where("Name", "like", "john")
excludePattern := users.Where("Name", "not like", "test")

Supported Operators:

  • =, == - Equality
  • != - Inequality
  • >, >= - Greater than, Greater than or equal
  • <, <= - Less than, Less than or equal
  • like - Case-insensitive substring match
  • not like - Case-insensitive substring exclusion

Utility Methods

  • Debug() - Print collection contents
  • Dump() - Print collection contents
  • Tap(func) - Execute function and return collection
  • ToJSON() - Convert to JSON string
  • When(condition, func) - Execute function if condition is true
  • Unless(condition, func) - Execute function if condition is false

Map Method - Laravel-style Transformation

The Map method provides Laravel-style transformation capabilities:

type User struct {
    ID   int
    Name string
    Age  int
}

users := collect.Of([]User{
    {ID: 1, Name: "Alice", Age: 25},
    {ID: 2, Name: "Bob", Age: 30},
})

// Transform to different types
names := users.Map(func(u User, i int) interface{} {
    return u.Name
})

ages := users.Map(func(u User, i int) interface{} {
    return u.Age
})

// Complex transformations
summaries := users.Map(func(u User, i int) interface{} {
    return map[string]interface{}{
        "id":      u.ID,
        "summary": fmt.Sprintf("%s (%d years)", u.Name, u.Age),
        "index":   i,
    }
})

// Chain with other operations
result := users.
    Map(func(u User, i int) interface{} {
        return u.Name
    }).
    Filter(func(name interface{}, _ int) bool {
        return len(name.(string)) > 3
    })

Generic Functions

Some operations require type transformation and are provided as generic functions for type safety:

// Type-safe Map to different type
strings := collect.New("1", "2", "3")
numbers := collect.Map(strings, func(s string, _ int) int {
    n, _ := strconv.Atoi(s)
    return n
})

// Reduce to single value
sum := collect.Reduce(numbers, func(acc int, n int, _ int) int {
    return acc + n
}, 0)

// Pluck with type conversion
users := collect.Of([]User{...})
userIDs := collect.Pluck[User, int](users, "ID")

Testing

go test -v

Examples

See example_test.go for comprehensive usage examples.

LazyCollection

LazyCollection provides lazy evaluation for efficient processing of large datasets. Operations are not executed until a terminal operation is called.

Creating LazyCollections

// From slice
lazy := collect.LazyCollect([]int{1, 2, 3, 4, 5})

// From range
lazy := collect.LazyRange(1, 1000000)

// From generator function
lazy := collect.LazyGenerate(func(i int) int {
    return i * 2
}, 100)

// From channel
ch := make(chan int, 5)
// ... populate channel
lazy := collect.LazyFromChannel(ch)

Lazy Operations

// Chain operations - these don't execute until consumed
result := collect.LazyRange(1, 1000000).
    Filter(func(n int, _ int) bool { return n%2 == 0 }).
    Take(5).
    All() // This triggers execution

fmt.Println(result) // [2 4 6 8 10]

Performance Benefits

// Efficient for large datasets when only a portion is needed
result := collect.LazyRange(1, 1000000).
    Filter(func(n int, _ int) bool { return n%100 == 0 }).
    Take(10).
    All()

// Only processes what's needed, not all 1 million items

Lazy vs Eager

// Lazy - processes only what's needed
lazyResult := collect.LazyRange(1, 1000000).
    Filter(func(n int, _ int) bool { return n%2 == 0 }).
    Take(5).
    All()

// Eager - processes all items first
eagerResult := collect.New(/* large slice */).
    Filter(func(n int, _ int) bool { return n%2 == 0 }).
    Take(5).
    All()

Converting Between Collections

// Lazy to eager
lazy := collectioncollectLazyRange(1, 11)
eager := lazy.Collect()

// Eager to lazy
eager := collect.New(1, 2, 3, 4, 5)
lazy := collect.LazyCollect(eager.All())

LazyCollection Methods

  • All() - Materialize all items
  • Count() - Count items
  • Filter(predicate) - Filter items
  • Map(func) - Transform each item with a function (returns LazyCollection[interface{}])
  • Where(params...) - Laravel-style filtering (supports all same patterns as Collection)
  • Take(n) - Take first n items
  • Skip(n) - Skip first n items
  • TakeWhile(predicate) - Take while condition is true
  • DropWhile(predicate) - Drop while condition is true
  • Unique() - Get unique items
  • Sort(lessFunc) - Sort items
  • Reverse() - Reverse order
  • Sum(keyFunc) - Calculate sum
  • Average(keyFunc) - Calculate average
  • Min(keyFunc) - Find minimum
  • Max(keyFunc) - Find maximum
  • GroupBy(keyFunc) - Group items
  • Partition(predicate) - Split into two collections
  • FlatMap(func) - Flat map transformation
  • Each(func) - Execute function for each item
  • ForEach(func) - Execute function for each item (consumes collection)
  • Iterator() - Get iterator for manual control
  • Collect() - Convert to eager Collection

License

MIT License

✅ Checks

  • [ ] Added test cases for my code

ahmed3mar avatar Jul 19 '25 13:07 ahmed3mar