A powerful and flexible PHP package for efficiently finding subsets within collections based on quantity criteria. Built with Laravel collections and optimized for performance, memory efficiency, and developer experience.
- High Performance: Optimized algorithms with configurable memory limits
- Flexible Configuration: Multiple configuration profiles for different use cases
- Performance Monitoring: Built-in metrics and logging capabilities
- Robust Error Handling: Comprehensive validation and meaningful error messages
- Type Safety: Full PHP 8.1+ type support with strict validation
- Comprehensive Testing: 100% test coverage with Pest PHP
- Laravel Integration: Service provider, facade, and trait support
- Memory Efficient: Optional lazy evaluation for large datasets
composer require ozdemir/subset-finder
The package automatically registers with Laravel. If you need to publish the configuration:
php artisan vendor:publish --tag=subset-finder-config
use Ozdemir\SubsetFinder\Subset;
use Ozdemir\SubsetFinder\SubsetCollection;
use Ozdemir\SubsetFinder\SubsetFinder;
use Ozdemir\SubsetFinder\SubsetFinderConfig;
// Define your collection and subset criteria
$collection = collect([
new Product(id: 1, quantity: 11, price: 15),
new Product(id: 2, quantity: 6, price: 5),
new Product(id: 3, quantity: 6, price: 5),
]);
$subsetCollection = new SubsetCollection([
Subset::of([1, 2])->take(5), // Find 5 items from products 1 and 2
Subset::of([3])->take(2), // Find 2 items from product 3
]);
// Create and configure SubsetFinder
$config = new SubsetFinderConfig(
idField: 'id',
quantityField: 'quantity',
sortField: 'price', // Sort by price (ascending)
sortDescending: false
);
$subsetFinder = new SubsetFinder($collection, $subsetCollection, $config);
$subsetFinder->solve();
// Get results
$foundSubsets = $subsetFinder->getFoundSubsets();
$remaining = $subsetFinder->getRemaining();
$maxSubsets = $subsetFinder->getSubsetQuantity();
// For large datasets (512MB memory, lazy evaluation enabled)
$subsetFinder = new SubsetFinder(
$collection,
$subsetCollection,
SubsetFinderConfig::forLargeDatasets()
);
// For performance (64MB memory, lazy evaluation disabled)
$subsetFinder = new SubsetFinder(
$collection,
$subsetCollection,
SubsetFinderConfig::forPerformance()
);
// For balanced approach (256MB memory, lazy evaluation enabled)
$subsetFinder = new SubsetFinder(
$collection,
$subsetCollection,
SubsetFinderConfig::forBalanced()
);
use Ozdemir\SubsetFinder\Facades\SubsetFinder;
// Create with default configuration
$subsetFinder = SubsetFinder::create($collection, $subsetCollection);
// Create with specific profile
$subsetFinder = SubsetFinder::forLargeDatasets($collection, $subsetCollection);
$subsetFinder = SubsetFinder::forPerformance($collection, $subsetCollection);
use Ozdemir\SubsetFinder\Traits\HasSubsetOperations;
class ProductCollection extends Collection
{
use HasSubsetOperations;
}
$products = new ProductCollection([...]);
// Find subsets directly on the collection
$subsetFinder = $products->findSubsets($subsetCollection);
// Use profiles
$subsetFinder = $products->findSubsetsWithProfile($subsetCollection, 'large_datasets');
// Check feasibility
if ($products->canSatisfySubsets($subsetCollection)) {
// Proceed with subset creation
}
$products = collect([
new Product(id: 1, quantity: 100, price: 10), // T-shirt
new Product(id: 2, quantity: 50, price: 5), // Socks
new Product(id: 3, quantity: 25, price: 20), // Hat
]);
$bundles = new SubsetCollection([
Subset::of([1, 2])->take(2), // T-shirt + Socks bundle
Subset::of([1, 3])->take(1), // T-shirt + Hat bundle
]);
$subsetFinder = new SubsetFinder($products, $bundles);
$subsetFinder->solve();
// Create 25 T-shirt + Socks bundles
// Create 25 T-shirt + Hat bundles
// Remaining: 25 T-shirts, 0 socks, 0 hats
$inventory = collect([
new Item(id: 'A', quantity: 100, category: 'electronics'),
new Item(id: 'B', quantity: 200, category: 'clothing'),
new Item(id: 'C', quantity: 150, category: 'books'),
]);
$orders = new SubsetCollection([
Subset::of(['A', 'B'])->take(10), // Electronics + Clothing order
Subset::of(['C'])->take(5), // Books order
]);
$subsetFinder = new SubsetFinder($inventory, $orders);
$subsetFinder->solve();
SUBSET_FINDER_MAX_MEMORY=256M
SUBSET_FINDER_LAZY_EVALUATION=true
SUBSET_FINDER_LOGGING=true
SUBSET_FINDER_LOG_CHANNEL=subset-finder
SUBSET_FINDER_LOG_LEVEL=info
// config/subset-finder.php
return [
'defaults' => [
'id_field' => 'id',
'quantity_field' => 'quantity',
'sort_field' => 'id',
'sort_descending' => false,
'max_memory_usage' => env('SUBSET_FINDER_MAX_MEMORY', 128 * 1024 * 1024),
'enable_lazy_evaluation' => env('SUBSET_FINDER_LAZY_EVALUATION', true),
'enable_logging' => env('SUBSET_FINDER_LOGGING', false),
],
'profiles' => [
'large_datasets' => [
'max_memory_usage' => 512 * 1024 * 1024,
'enable_lazy_evaluation' => true,
'enable_logging' => true,
],
'performance' => [
'max_memory_usage' => 64 * 1024 * 1024,
'enable_lazy_evaluation' => false,
'enable_logging' => false,
],
],
];
$subsetFinder = new SubsetFinder($collection, $subsetCollection);
$subsetFinder->solve();
// Get performance metrics
$metrics = $subsetFinder->getPerformanceMetrics();
// [
// 'execution_time_ms' => 45.23,
// 'memory_peak_mb' => 12.5,
// 'memory_increase_mb' => 8.2,
// 'collection_size' => 1000,
// 'subset_count' => 5,
// 'found_subsets_count' => 5,
// 'remaining_items_count' => 50
// ]
// Check solution quality
$isOptimal = $subsetFinder->isOptimal(); // true if no remaining items
$efficiency = $subsetFinder->getEfficiencyPercentage(); // 95.2%
use Ozdemir\SubsetFinder\Exceptions\InvalidArgumentException;
use Ozdemir\SubsetFinder\Exceptions\InsufficientQuantityException;
try {
$subsetFinder = new SubsetFinder($collection, $subsetCollection);
$subsetFinder->solve();
} catch (InvalidArgumentException $e) {
// Handle invalid input (empty collection, invalid items, etc.)
Log::error('Invalid subset finder input: ' . $e->getMessage());
} catch (InsufficientQuantityException $e) {
// Handle insufficient quantities
Log::warning('Cannot create subsets: ' . $e->getMessage());
}
# Run tests
composer test
# Run tests with coverage
composer test-coverage
# Run static analysis
composer analyse
- Use appropriate configuration profiles for your dataset size
- Enable lazy evaluation for large collections to reduce memory usage
- Monitor memory usage and adjust
max_memory_usage
accordingly - Use meaningful sort fields to optimize subset selection
- Consider batch processing for very large datasets
use Psr\Log\LoggerInterface;
class CustomLogger implements LoggerInterface
{
// Implement logger methods
}
$subsetFinder = new SubsetFinder(
$collection,
$subsetCollection,
$config,
new CustomLogger()
);
// Check memory before processing
if (memory_get_usage(true) > $config->maxMemoryUsage) {
throw new \Exception('Insufficient memory for processing');
}
// Process in batches for very large datasets
$batchSize = 1000;
foreach ($collection->chunk($batchSize) as $batch) {
// Process batch
}
Contributions are welcome! Please see our Contributing Guide for details.
This package is open-sourced software licensed under the MIT License.
- Documentation: GitHub Wiki
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- High-performance caching with Redis integration
- Memory-based fallback when Redis is unavailable
- Smart cache key generation based on input data
- Configurable TTL and cache management
- Automatic fallback to memory cache on Redis failure
- Multi-process subset finding for large datasets
- Intelligent chunking with optimal size calculation
- System-aware process limits based on CPU and memory
- Simulated parallel processing for development environments
- Performance metrics for parallel operations
- Multi-criteria optimization with configurable weights
- Advanced constraint handling (ranges, custom functions)
- Efficiency scoring and ranking algorithms
- Statistical analysis with quartiles and distributions
- Real-world optimization scenarios (e-commerce, inventory)
- Machine learning-based optimization
- GraphQL integration
- Performance benchmarking tools
- More configuration profiles