ozdemir/subset-finder

A simple package to find the subset of a given set.

2.1.2 2025-08-16 10:33 UTC

This package is auto-updated.

Last update: 2025-08-19 02:06:52 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub

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.

Features

  • 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

Installation

composer require ozdemir/subset-finder

Laravel Integration

The package automatically registers with Laravel. If you need to publish the configuration:

php artisan vendor:publish --tag=subset-finder-config

Quick Start

Basic Usage

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();

Using Configuration Profiles

// 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()
);

Using the Facade

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);

Using the Trait

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
}

Use Cases

E-commerce Bundle 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 Management

$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();

Configuration

Environment Variables

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

Configuration File

// 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,
        ],
    ],
];

Performance Monitoring

$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%

Error Handling

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());
}

Testing

# Run tests
composer test

# Run tests with coverage
composer test-coverage

# Run static analysis
composer analyse

Performance Tips

  1. Use appropriate configuration profiles for your dataset size
  2. Enable lazy evaluation for large collections to reduce memory usage
  3. Monitor memory usage and adjust max_memory_usage accordingly
  4. Use meaningful sort fields to optimize subset selection
  5. Consider batch processing for very large datasets

Advanced Usage

Custom Logging

use Psr\Log\LoggerInterface;

class CustomLogger implements LoggerInterface
{
    // Implement logger methods
}

$subsetFinder = new SubsetFinder(
    $collection, 
    $subsetCollection, 
    $config, 
    new CustomLogger()
);

Memory Management

// 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
}

Contributing

Contributions are welcome! Please see our Contributing Guide for details.

License

This package is open-sourced software licensed under the MIT License.

Support

Roadmap

Redis Caching Support

  • 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

Parallel Processing

  • 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

Weighted Subset Selection

  • 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)

🔄 In Development

  • Machine learning-based optimization
  • GraphQL integration
  • Performance benchmarking tools
  • More configuration profiles