main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
package main

import (	"fmt"
	"sort")

// Filter returns a new slice containing only elements that satisfy the predicate
func Filter[T any](slice []T, predicate func(T) bool) []T {
	result := make([]T, 0, len(slice))
	for _, item := range slice {
		if predicate(item) {
			result = append(result, item)
		}
	}
	return result
}

// Map applies a transformation function to each element and returns a new slice
func Map[T, U any](slice []T, transform func(T) U) []U {
	result := make([]U, 0, len(slice))
	for _, item := range slice {
		result = append(result, transform(item))
	}
	return result
}

// Reduce reduces a slice to a single value using a reducer function
func Reduce[T, U any](slice []T, reducer func(U, T) U, initial U) U {
	accumulator := initial
	for _, item := range slice {
		accumulator = reducer(accumulator, item)
	}
	return accumulator
}

// Find returns the first element that satisfies the predicate and a boolean indicating if found
func Find[T any](slice []T, predicate func(T) bool) (T, bool) {
	for _, item := range slice {
		if predicate(item) {
			return item, true
		}
	}
	var zero T
	return zero, false
}

// RemoveDuplicates returns a new slice with duplicates removed (uses slices.Compare for equality)
func RemoveDuplicates[T comparable](slice []T) []T {
	seen := make(map[T]bool)
	result := make([]T, 0, len(slice))
	for _, item := range slice {
		if !seen[item] {
			seen[item] = true
			result = append(result, item)
		}
	}
	return result
}

// GroupBy groups slice elements into a map based on a key function
func GroupBy[T any, K comparable](slice []T, keyFunc func(T) K) map[K][]T {
	groups := make(map[K][]T)
	for _, item := range slice {
		key := keyFunc(item)
		groups[key] = append(groups[key], item)
	}
	return groups
}

// Keys returns a slice of all keys in a map
func Keys[K comparable, V any](m map[K]V) []K {
	keys := make([]K, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

// Values returns a slice of all values in a map
func Values[K comparable, V any](m map[K]V) []V {
	values := make([]V, 0, len(m))
	for _, v := range m {
		values = append(values, v)
	}
	return values
}

// Chunk splits a slice into chunks of the specified size
func Chunk[T any](slice []T, chunkSize int) [][]T {
	if chunkSize <= 0 {
		panic("chunkSize must be greater than 0")
	}

	var chunks [][]T
	for i := 0; i < len(slice); i += chunkSize {
		end := i + chunkSize
		if end > len(slice) {
			end = len(slice)
		}
		chunks = append(chunks, slice[i:end])
	}
	return chunks
}

func main() {
	// Example 1: Filter slice
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	evenNumbers := Filter(numbers, func(n int) bool {
		return n%2 == 0
	})
	fmt.Println("Even numbers:", evenNumbers)

	// Example 2: Map slice
	strings := Map(numbers, func(n int) string {
		return fmt.Sprintf("Number: %d", n)
	})
	fmt.Println("Mapped strings:", strings)

	// Example 3: Reduce slice (sum)
	sum := Reduce(numbers, func(acc, n int) int {
		return acc + n
	}, 0)
	fmt.Println("Sum of numbers:", sum)

	// Example 4: Find element
	found, exists := Find(numbers, func(n int) bool {
		return n == 7
	})
	fmt.Printf("Found 7: %v (exists: %t)\n", found, exists)

	// Example 5: Remove duplicates
	duplicates := []int{1, 2, 2, 3, 3, 3, 4, 5, 5}
	unique := RemoveDuplicates(duplicates)
	fmt.Println("Unique numbers:", unique)

	// Example 6: Group by
	type Person struct {
		Name  string
		Age   int
		City  string
	}

	people := []Person{
		{Name: "John", Age: 30, City: "New York"},
		{Name: "Jane", Age: 28, City: "London"},
		{Name: "Bob", Age: 35, City: "New York"},
		{Name: "Alice", Age: 25, City: "London"},
	}

	// Group people by city
	peopleByCity := GroupBy(people, func(p Person) string {
		return p.City
	})
	fmt.Println("People by city:")
	for city, folks := range peopleByCity {
		fmt.Printf("  %s: %v\n", city, folks)
	}

	// Example 7: Map keys and values
	personAges := map[string]int{
		"John":  30,
		"Jane":  28,
		"Bob":   35,
		"Alice": 25,
	}
	fmt.Println("Map keys:", Keys(personAges))
	fmt.Println("Map values:", Values(personAges))

	// Example 8: Chunk slice
	largeSlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
	chunks := Chunk(largeSlice, 3)
	fmt.Println("Chunks (size 3):", chunks)

	// Example 9: Combined operations
	// Filter adults, map to names, sort
	adultNames := Map(
		Filter(people, func(p Person) bool { return p.Age >= 30 }),
		func(p Person) string { return p.Name },
	)
	sort.Strings(adultNames)
	fmt.Println("Adult names (sorted):", adultNames)

	// Example 10: Reduce to concatenate strings
	words := []string{"Hello", " ", "Go", " ", "Utilities!"}
	sentence := Reduce(words, func(acc, s string) string {
		return acc + s
	}, "")
	fmt.Println("Concatenated sentence:", sentence)
}

How It Works

Provides functional-style utilities for slices and maps without sacrificing allocations or compile-time type checking.

Generic functions iterate slices with predicates or transformers, preallocate output where possible, use maps to de-duplicate or group values, and expose reduce and find helpers for concise data processing.

Key Concepts

  • 1Uses type parameters so helpers stay strongly typed.
  • 2Preallocates result slices and maps to reduce garbage.
  • 3GroupBy and Dedup leverage maps for O(n) lookups.
  • 4Includes Find and Reduce helpers for expressive pipelines.

When to Use This Pattern

  • Transforming API responses before rendering.
  • Cleaning and grouping analytics events in memory.
  • Building small data processing utilities without heavier libraries.
  • Sharing a common helper set across services to avoid duplication.

Best Practices

  • Keep predicate and mapper functions pure to avoid side effects.
  • Pre-size outputs when input length is known.
  • Avoid modifying input slices while iterating.
  • Benchmark critical paths because allocations vary by predicate.
Go Version1.18+
Difficultyintermediate
Production ReadyYes
Lines of Code188