Design an object-oriented file search system that mimics the functionality of the Unix find command. Your system should allow users to search through a collection of files using various filtering criteria such as file extension, size thresholds, and name patterns. The design must be extensible, allowing new filter types to be added without modifying existing code.
The system should support:
FileFilter abstract base class that defines the interface for all filtersFileSearchSystem class that can accept multiple filters and apply them to a collection of filesExample 1: Single Extension Filter
Files: [ \{name: "document.txt", size: 1024, path: "/home/user"\}, \{name: "image.png", size: 2048, path: "/home/user"\}, \{name: "notes.txt", size: 512, path: "/home/user"\} ] Filter: ExtensionFilter(".txt") Output: ["document.txt", "notes.txt"] Explanation: Only files with .txt extension are returned
Example 2: Size Filter
Files: [ \{name: "small.log", size: 500, path: "/var/log"\}, \{name: "large.log", size: 6000000, path: "/var/log"\}, \{name: "medium.log", size: 3000000, path: "/var/log"\} ] Filter: SizeFilter(5000000) // 5MB minimum Output: ["large.log"] Explanation: Only files larger than 5MB are returned
Example 3: Combined Filters
Files: [ \{name: "app.log", size: 6000000, path: "/var/log"\}, \{name: "app.txt", size: 7000000, path: "/var/log"\}, \{name: "debug.log", size: 3000000, path: "/var/log"\} ] Filters: [ExtensionFilter(".log"), SizeFilter(5000000)] Output: ["app.log"] Explanation: File must be .log AND larger than 5MB
Hint 1: Design Pattern Selection Consider using the Strategy Pattern for different filter implementations and the Composite Pattern for combining multiple filters. Each filter should be a separate class implementing a common interface, making it easy to add new filter types without changing existing code.
Hint 2: Filter Interface Design The key is defining a simple, powerful interface for the
FileFilterbase class. A singlematches(file_info)method that returns a boolean is sufficient. This allows any filter implementation to evaluate whether a file meets its criteria independently.
Hint 3: Combining Filters For composite filters (combining multiple criteria), create a
CompositeFilterclass that also implements theFileFilterinterface. It should store a list of child filters and return true only if ALL child filters return true (AND logic). This maintains consistency with the Strategy Pattern.
Full Solution ``
Solution Explanation
Design Patterns Used:
- Strategy Pattern: Each filter type (ExtensionFilter, SizeFilter, etc.) implements the same
FileFilterinterface with amatches()method. This allows filters to be swapped and extended easily.- Composite Pattern: The
CompositeFilterclass combines multiple filters while maintaining the same interface, allowing filters to be nested and combined arbitrarily.Key Design Principles:
- Open/Closed Principle: New filter types can be added by creating new classes that extend
FileFilterwithout modifying existing code.- Single Responsibility: Each filter class has one job - evaluate one specific criterion.
- Dependency Inversion: The
FileSearchSystemdepends on the abstractFileFilterinterface, not concrete implementations.Time Complexity: O(n × f) where n is the number of files and f is the number of filters. Each file is checked against each filter once.
Space Complexity: O(m) where m is the number of matching files returned.
The design is easily extensible - you can add new filter types (like modification date, permissions, file content) by simply creating a new class that extends
FileFilter.
from abc import ABC, abstractmethod
from typing import List, Dict, Optional
# Abstract base class defining the filter interface
class FileFilter(ABC):
"""
Base class for all file filters using the Strategy Pattern.
Each concrete filter implements its own matching logic.
"""
@abstractmethod
def matches(self, file_info: Dict) -> bool:
"""
Determine if a file matches the filter criteria.
Args:
file_info: Dictionary with 'name', 'size', and 'path' keys
Returns:
True if file matches criteria, False otherwise
"""
pass
class ExtensionFilter(FileFilter):
"""Filter files by their extension."""
def __init__(self, extension: str):
"""
Args:
extension: File extension including the dot (e.g., '.txt')
"""
self.extension = extension.lower()
def matches(self, file_info: Dict) -> bool:
"""Check if file has the specified extension."""
return file_info['name'].lower().endswith(self.extension)
class SizeFilter(FileFilter):
"""Filter files by minimum size threshold."""
def __init__(self, min_size: int, max_size: Optional[int] = None):
"""
Args:
min_size: Minimum file size in bytes
max_size: Optional maximum file size in bytes
"""
self.min_size = min_size
self.max_size = max_size
def matches(self, file_info: Dict) -> bool:
"""Check if file size is within specified range."""
size = file_info['size']
meets_min = size >= self.min_size
meets_max = self.max_size is None or size <= self.max_size
return meets_min and meets_max
class NamePatternFilter(FileFilter):
"""Filter files by name patterns (prefix, suffix, or contains)."""
def __init__(self, pattern: str, match_type: str = 'contains'):
"""
Args:
pattern: String pattern to match
match_type: One of 'prefix', 'suffix', 'contains', or 'exact'
"""
self.pattern = pattern.lower()
self.match_type = match_type
def matches(self, file_info: Dict) -> bool:
"""Check if file name matches the pattern."""
name = file_info['name'].lower()
if self.match_type == 'prefix':
return name.startswith(self.pattern)
elif self.match_type == 'suffix':
return name.endswith(self.pattern)
elif self.match_type == 'contains':
return self.pattern in name
elif self.match_type == 'exact':
return name == self.pattern
return False
class CompositeFilter(FileFilter):
"""
Combine multiple filters using AND logic (Composite Pattern).
A file must pass ALL child filters to match.
"""
def __init__(self, filters: List[FileFilter]):
"""
Args:
filters: List of FileFilter instances to combine
"""
self.filters = filters
def matches(self, file_info: Dict) -> bool:
"""Check if file passes all child filters."""
return all(f.matches(file_info) for f in self.filters)
def add_filter(self, filter: FileFilter):
"""Add another filter to the composite."""
self.filters.append(filter)
class OrCompositeFilter(FileFilter):
"""
Combine multiple filters using OR logic.
A file must pass AT LEAST ONE child filter to match.
"""
def __init__(self, filters: List[FileFilter]):
self.filters = filters
def matches(self, file_info: Dict) -> bool:
"""Check if file passes any child filter."""
return any(f.matches(file_info) for f in self.filters)
class FileSearchSystem:
"""
Main search system that applies filters to a collection of files.
Supports adding multiple filters and executing searches.
"""
def __init__(self):
self.filters: List[FileFilter] = []
def add_filter(self, filter: FileFilter):
"""
Add a filter to the search system.
Args:
filter: A FileFilter instance
"""
self.filters.append(filter)
def clear_filters(self):
"""Remove all filters from the system."""
self.filters.clear()
def search(self, files: List[Dict]) -> List[str]:
"""
Search through files and return names of matching files.
Args:
files: List of file dictionaries with 'name', 'size', 'path'
Returns:
List of file names that match all filters
"""
if not files:
return []
# If no filters are added, return all files
if not self.filters:
return [f['name'] for f in files]
# Apply all filters (AND logic across filters in the system)
matching_files = []
for file_info in files:
if all(filter.matches(file_info) for filter in self.filters):
matching_files.append(file_info['name'])
return matching_files
def search_with_details(self, files: List[Dict]) -> List[Dict]:
"""
Search and return full file information instead of just names.
Returns:
List of file dictionaries that match all filters
"""
if not files:
return []
if not self.filters:
return files
return [f for f in files if all(filter.matches(f) for filter in self.filters)]
# Example usage demonstrating the system
def example_usage():
# Sample file data
files = [
{'name': 'document.txt', 'size': 1024, 'path': '/home/user'},
{'name': 'large_log.log', 'size': 6000000, 'path': '/var/log'},
{'name': 'image.png', 'size': 2048, 'path': '/home/user'},
{'name': 'test_data.txt', 'size': 7000000, 'path': '/data'},
{'name': 'app.log', 'size': 3000, 'path': '/var/log'},
]
# Example 1: Find all .txt files
system = FileSearchSystem()
system.add_filter(ExtensionFilter('.txt'))
result = system.search(files)
print(f"Text files: {result}")
# Output: ['document.txt', 'test_data.txt']
# Example 2: Find large .log files (>5MB)
system.clear_filters()
system.add_filter(ExtensionFilter('.log'))
system.add_filter(SizeFilter(5000000))
result = system.search(files)
print(f"Large log files: {result}")
# Output: ['large_log.log']
# Example 3: Using composite filter
composite = CompositeFilter([
ExtensionFilter('.txt'),
SizeFilter(5000000)
])
system.clear_filters()
system.add_filter(composite)
result = system.search(files)
print(f"Large text files: {result}")
# Output: ['test_data.txt']
# Example 4: Using OR logic - find .txt OR .log files
or_filter = OrCompositeFilter([
ExtensionFilter('.txt'),
ExtensionFilter('.log')
])
system.clear_filters()
system.add_filter(or_filter)
result = system.search(files)
print(f"Text or log files: {result}")
# Output: ['document.txt', 'large_log.log', 'test_data.txt', 'app.log']
if __name__ == "__main__":
example_usage()