python

exercises

exercises.py🐍
"""
07 - File Handling: Exercises
Complete these exercises to practice file operations!
"""

import os
import json
import csv
import tempfile
from pathlib import Path
from typing import Optional


# =============================================================================
# EXERCISE 1: Basic File Reading
# =============================================================================
def read_entire_file(filepath: str) -> str:
    """
    Read and return the entire contents of a file.
    
    Args:
        filepath: Path to the file to read
        
    Returns:
        The complete file contents as a string
        
    Raises:
        FileNotFoundError: If file doesn't exist
    """
    # YOUR CODE HERE
    with open(filepath, 'r') as f:
        return f.read()


def read_lines_as_list(filepath: str) -> list[str]:
    """
    Read a file and return lines as a list (without newline characters).
    
    Args:
        filepath: Path to the file to read
        
    Returns:
        List of lines without trailing newlines
    """
    # YOUR CODE HERE
    with open(filepath, 'r') as f:
        return f.read().splitlines()


def count_lines(filepath: str) -> int:
    """
    Count the total number of lines in a file.
    
    Args:
        filepath: Path to the file
        
    Returns:
        Number of lines
    """
    # YOUR CODE HERE
    with open(filepath, 'r') as f:
        return sum(1 for _ in f)


# =============================================================================
# EXERCISE 2: Basic File Writing
# =============================================================================
def write_lines(filepath: str, lines: list[str]) -> None:
    """
    Write a list of strings to a file, one per line.
    
    Args:
        filepath: Path to write to
        lines: List of strings to write
    """
    # YOUR CODE HERE
    with open(filepath, 'w') as f:
        for line in lines:
            f.write(line + '\n')


def append_to_file(filepath: str, text: str) -> None:
    """
    Append text to the end of a file.
    
    Args:
        filepath: Path to the file
        text: Text to append (add a newline at the end)
    """
    # YOUR CODE HERE
    with open(filepath, 'a') as f:
        f.write(text + '\n')


# =============================================================================
# EXERCISE 3: Find and Replace
# =============================================================================
def find_and_replace(filepath: str, old_text: str, new_text: str) -> int:
    """
    Replace all occurrences of old_text with new_text in a file.
    
    Args:
        filepath: Path to the file
        old_text: Text to find
        new_text: Text to replace with
        
    Returns:
        Number of replacements made
    """
    # YOUR CODE HERE
    with open(filepath, 'r') as f:
        content = f.read()
    
    count = content.count(old_text)
    new_content = content.replace(old_text, new_text)
    
    with open(filepath, 'w') as f:
        f.write(new_content)
    
    return count


# =============================================================================
# EXERCISE 4: Word Counter
# =============================================================================
def count_words(filepath: str) -> dict[str, int]:
    """
    Count occurrences of each word in a file.
    Words should be lowercase and stripped of punctuation.
    
    Args:
        filepath: Path to the file
        
    Returns:
        Dictionary mapping words to counts
    """
    # YOUR CODE HERE
    import string
    
    with open(filepath, 'r') as f:
        content = f.read().lower()
    
    # Remove punctuation
    translator = str.maketrans('', '', string.punctuation)
    content = content.translate(translator)
    
    words = content.split()
    word_counts: dict[str, int] = {}
    
    for word in words:
        word_counts[word] = word_counts.get(word, 0) + 1
    
    return word_counts


def most_common_words(filepath: str, n: int = 5) -> list[tuple[str, int]]:
    """
    Find the n most common words in a file.
    
    Args:
        filepath: Path to the file
        n: Number of top words to return
        
    Returns:
        List of (word, count) tuples sorted by count descending
    """
    # YOUR CODE HERE
    counts = count_words(filepath)
    sorted_words = sorted(counts.items(), key=lambda x: x[1], reverse=True)
    return sorted_words[:n]


# =============================================================================
# EXERCISE 5: JSON Operations
# =============================================================================
def read_json_file(filepath: str) -> dict:
    """
    Read and parse a JSON file.
    
    Args:
        filepath: Path to the JSON file
        
    Returns:
        Parsed JSON data as dictionary
    """
    # YOUR CODE HERE
    with open(filepath, 'r') as f:
        return json.load(f)


def write_json_file(filepath: str, data: dict, pretty: bool = True) -> None:
    """
    Write data to a JSON file.
    
    Args:
        filepath: Path to write to
        data: Data to write
        pretty: If True, format with indentation
    """
    # YOUR CODE HERE
    with open(filepath, 'w') as f:
        if pretty:
            json.dump(data, f, indent=2)
        else:
            json.dump(data, f)


def update_json_value(filepath: str, key: str, value) -> None:
    """
    Update a specific key in a JSON file.
    
    Args:
        filepath: Path to the JSON file
        key: Key to update
        value: New value
    """
    # YOUR CODE HERE
    data = read_json_file(filepath)
    data[key] = value
    write_json_file(filepath, data)


# =============================================================================
# EXERCISE 6: CSV Operations
# =============================================================================
def read_csv_as_dicts(filepath: str) -> list[dict]:
    """
    Read a CSV file and return as list of dictionaries.
    
    Args:
        filepath: Path to CSV file
        
    Returns:
        List of dictionaries (one per row)
    """
    # YOUR CODE HERE
    with open(filepath, 'r') as f:
        reader = csv.DictReader(f)
        return list(reader)


def write_dicts_to_csv(filepath: str, data: list[dict]) -> None:
    """
    Write a list of dictionaries to a CSV file.
    
    Args:
        filepath: Path to write to
        data: List of dictionaries with same keys
    """
    # YOUR CODE HERE
    if not data:
        return
    
    fieldnames = list(data[0].keys())
    with open(filepath, 'w', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(data)


def filter_csv_rows(filepath: str, column: str, value: str) -> list[dict]:
    """
    Filter CSV rows where column equals value.
    
    Args:
        filepath: Path to CSV file
        column: Column name to filter on
        value: Value to match
        
    Returns:
        List of matching rows as dictionaries
    """
    # YOUR CODE HERE
    data = read_csv_as_dicts(filepath)
    return [row for row in data if row.get(column) == value]


# =============================================================================
# EXERCISE 7: Pathlib Operations
# =============================================================================
def list_files_by_extension(directory: str, extension: str) -> list[Path]:
    """
    List all files in directory with given extension.
    
    Args:
        directory: Directory to search
        extension: File extension (e.g., '.txt')
        
    Returns:
        List of Path objects for matching files
    """
    # YOUR CODE HERE
    if not extension.startswith('.'):
        extension = '.' + extension
    
    dir_path = Path(directory)
    return list(dir_path.glob(f'*{extension}'))


def find_files_recursive(directory: str, pattern: str) -> list[Path]:
    """
    Recursively find all files matching a glob pattern.
    
    Args:
        directory: Root directory to search
        pattern: Glob pattern (e.g., '*.py')
        
    Returns:
        List of matching Path objects
    """
    # YOUR CODE HERE
    dir_path = Path(directory)
    return list(dir_path.rglob(pattern))


def get_file_info(filepath: str) -> dict:
    """
    Get information about a file.
    
    Args:
        filepath: Path to the file
        
    Returns:
        Dictionary with keys: 'name', 'size', 'extension', 'exists'
    """
    # YOUR CODE HERE
    p = Path(filepath)
    return {
        'name': p.name,
        'size': p.stat().st_size if p.exists() else 0,
        'extension': p.suffix,
        'exists': p.exists()
    }


# =============================================================================
# EXERCISE 8: Log File Parser
# =============================================================================
def parse_log_line(line: str) -> Optional[dict]:
    """
    Parse a log line in format: [YYYY-MM-DD HH:MM:SS] LEVEL: message
    
    Args:
        line: Log line string
        
    Returns:
        Dictionary with 'timestamp', 'level', 'message' or None if invalid
    """
    # YOUR CODE HERE
    import re
    
    pattern = r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.+)'
    match = re.match(pattern, line.strip())
    
    if match:
        return {
            'timestamp': match.group(1),
            'level': match.group(2),
            'message': match.group(3)
        }
    return None


def filter_log_by_level(filepath: str, level: str) -> list[dict]:
    """
    Filter log entries by level (e.g., 'ERROR', 'WARNING').
    
    Args:
        filepath: Path to log file
        level: Log level to filter
        
    Returns:
        List of matching log entries as dictionaries
    """
    # YOUR CODE HERE
    results = []
    with open(filepath, 'r') as f:
        for line in f:
            parsed = parse_log_line(line)
            if parsed and parsed['level'] == level:
                results.append(parsed)
    return results


# =============================================================================
# EXERCISE 9: File Backup
# =============================================================================
def create_backup(filepath: str) -> str:
    """
    Create a backup of a file by copying to filename.bak
    
    Args:
        filepath: Path to the file to backup
        
    Returns:
        Path to the backup file
    """
    # YOUR CODE HERE
    import shutil
    
    backup_path = filepath + '.bak'
    shutil.copy2(filepath, backup_path)
    return backup_path


def create_numbered_backup(filepath: str) -> str:
    """
    Create numbered backups (file.bak.1, file.bak.2, etc.)
    Find the next available number.
    
    Args:
        filepath: Path to the file to backup
        
    Returns:
        Path to the backup file
    """
    # YOUR CODE HERE
    import shutil
    
    n = 1
    while True:
        backup_path = f"{filepath}.bak.{n}"
        if not os.path.exists(backup_path):
            break
        n += 1
    
    shutil.copy2(filepath, backup_path)
    return backup_path


# =============================================================================
# EXERCISE 10: Configuration Manager
# =============================================================================
class ConfigManager:
    """Manage application configuration stored in JSON file."""
    
    def __init__(self, config_path: str):
        """
        Initialize with path to config file.
        Create with empty dict if doesn't exist.
        """
        # YOUR CODE HERE
        self.config_path = config_path
        if os.path.exists(config_path):
            with open(config_path, 'r') as f:
                self.config = json.load(f)
        else:
            self.config = {}
            self._save()
    
    def _save(self) -> None:
        """Save config to file."""
        with open(self.config_path, 'w') as f:
            json.dump(self.config, f, indent=2)
    
    def get(self, key: str, default=None):
        """
        Get a configuration value.
        
        Args:
            key: Configuration key
            default: Default value if key not found
            
        Returns:
            Configuration value or default
        """
        # YOUR CODE HERE
        return self.config.get(key, default)
    
    def set(self, key: str, value) -> None:
        """
        Set a configuration value and save.
        
        Args:
            key: Configuration key
            value: Value to set
        """
        # YOUR CODE HERE
        self.config[key] = value
        self._save()
    
    def delete(self, key: str) -> bool:
        """
        Delete a configuration key.
        
        Args:
            key: Key to delete
            
        Returns:
            True if key existed, False otherwise
        """
        # YOUR CODE HERE
        if key in self.config:
            del self.config[key]
            self._save()
            return True
        return False


# =============================================================================
# TEST YOUR SOLUTIONS
# =============================================================================
if __name__ == "__main__":
    import tempfile
    import shutil
    
    # Create temporary directory for tests
    temp_dir = tempfile.mkdtemp()
    
    try:
        print("Testing file handling exercises...")
        print("=" * 60)
        
        # Test 1: Basic reading
        test_file = os.path.join(temp_dir, "test.txt")
        with open(test_file, 'w') as f:
            f.write("Line 1\nLine 2\nLine 3\n")
        
        content = read_entire_file(test_file)
        assert "Line 1" in content, "read_entire_file failed"
        print("✓ read_entire_file")
        
        lines = read_lines_as_list(test_file)
        assert lines == ["Line 1", "Line 2", "Line 3"], "read_lines_as_list failed"
        print("✓ read_lines_as_list")
        
        assert count_lines(test_file) == 3, "count_lines failed"
        print("✓ count_lines")
        
        # Test 2: Writing
        write_file = os.path.join(temp_dir, "write_test.txt")
        write_lines(write_file, ["Hello", "World"])
        assert os.path.exists(write_file), "write_lines failed"
        print("✓ write_lines")
        
        append_to_file(write_file, "Appended")
        with open(write_file, 'r') as f:
            assert "Appended" in f.read(), "append_to_file failed"
        print("✓ append_to_file")
        
        # Test 3: Find and replace
        replace_file = os.path.join(temp_dir, "replace.txt")
        with open(replace_file, 'w') as f:
            f.write("hello world, hello python")
        count = find_and_replace(replace_file, "hello", "hi")
        assert count == 2, "find_and_replace count failed"
        print("✓ find_and_replace")
        
        # Test 4: Word counter
        word_file = os.path.join(temp_dir, "words.txt")
        with open(word_file, 'w') as f:
            f.write("the quick brown fox the quick dog")
        counts = count_words(word_file)
        assert counts.get('the') == 2, "count_words failed"
        print("✓ count_words")
        
        top = most_common_words(word_file, 2)
        assert len(top) == 2, "most_common_words failed"
        print("✓ most_common_words")
        
        # Test 5: JSON
        json_file = os.path.join(temp_dir, "test.json")
        write_json_file(json_file, {"name": "test", "value": 42})
        data = read_json_file(json_file)
        assert data["name"] == "test", "JSON read/write failed"
        print("✓ JSON operations")
        
        update_json_value(json_file, "value", 100)
        data = read_json_file(json_file)
        assert data["value"] == 100, "update_json_value failed"
        print("✓ update_json_value")
        
        # Test 6: CSV
        csv_file = os.path.join(temp_dir, "test.csv")
        csv_data = [
            {"name": "Alice", "age": "30"},
            {"name": "Bob", "age": "25"}
        ]
        write_dicts_to_csv(csv_file, csv_data)
        read_data = read_csv_as_dicts(csv_file)
        assert len(read_data) == 2, "CSV operations failed"
        print("✓ CSV operations")
        
        filtered = filter_csv_rows(csv_file, "name", "Alice")
        assert len(filtered) == 1, "filter_csv_rows failed"
        print("✓ filter_csv_rows")
        
        # Test 7: Pathlib
        txt_files = list_files_by_extension(temp_dir, '.txt')
        assert len(txt_files) >= 1, "list_files_by_extension failed"
        print("✓ list_files_by_extension")
        
        info = get_file_info(test_file)
        assert info['exists'] is True, "get_file_info failed"
        print("✓ get_file_info")
        
        # Test 8: Log parser
        log_file = os.path.join(temp_dir, "app.log")
        with open(log_file, 'w') as f:
            f.write("[2024-01-15 10:30:00] INFO: Started\n")
            f.write("[2024-01-15 10:30:01] ERROR: Something failed\n")
        
        parsed = parse_log_line("[2024-01-15 10:30:00] INFO: Test message")
        assert parsed['level'] == 'INFO', "parse_log_line failed"
        print("✓ parse_log_line")
        
        errors = filter_log_by_level(log_file, 'ERROR')
        assert len(errors) == 1, "filter_log_by_level failed"
        print("✓ filter_log_by_level")
        
        # Test 9: Backup
        backup_path = create_backup(test_file)
        assert os.path.exists(backup_path), "create_backup failed"
        print("✓ create_backup")
        
        numbered = create_numbered_backup(test_file)
        assert '.bak.' in numbered, "create_numbered_backup failed"
        print("✓ create_numbered_backup")
        
        # Test 10: Config manager
        config_file = os.path.join(temp_dir, "config.json")
        config = ConfigManager(config_file)
        config.set("theme", "dark")
        assert config.get("theme") == "dark", "ConfigManager set/get failed"
        print("✓ ConfigManager")
        
        print("\n" + "=" * 60)
        print("All tests passed! ✓")
        
    finally:
        # Cleanup
        shutil.rmtree(temp_dir)
Exercises - Python Tutorial | DeepML