Python Algorithms: Mastermind Solver, Text Frequency, and Sorting Techniques

Mastermind Game Logic Implementation in Python

This section provides Python functions for implementing the core logic of the Mastermind game, including calculating clues and finding compatible combinations. Note that muertos refers to Black Pegs (exact matches) and heridos refers to White Pegs (correct color, wrong position).

1. Counting Color Occurrences

def occurrences(color: str, comb: str) -> int:
   ""Counts how many times a specific color appears in a combination""
    oc = 0
    for l in comb:
        if l == color:
            oc += 1
    return oc

2. Calculating Mastermind Clues (Black and White Pegs)

def get_clues(secret: str, guess: str) -> tuple[int, int]:
    assert len(secret) == len(guess)
    
    # Black Pegs (Muertos): Exact matches
    muertos = 0
    for i in range(len(secret)): 
        if secret[i] == guess[i]:
            muertos += 1
            
    # White Pegs (Heridos): Correct colors in wrong positions
    heridos = 0
    # Use a set to iterate over unique colors in the secret code
    for l in set(secret): 
        # Calculate total matches for this color in both codes
        total_matches = min(occurrences(l, secret), occurrences(l, guess))
        heridos += total_matches
        
    # Subtract exact matches (muertos) from total color matches
    heridos -= muertos
    
    # Returns (White Pegs, Black Pegs)
    return heridos, muertos

3. Checking Combination Compatibility

def is_compatible_history(comb: str, history: dict) -> bool: 
   ""Checks if a combination is compatible with the entire game history""
    i = 0 
    res = True
    while i < len(history["guesses"]) and res:
        # Compare the clues generated by 'comb' against the recorded clues
        res = history["clues"][i] == get_clues(comb, history["guesses"][i]) 
        i += 1
    return res

4. Generating the Next Combination

This function iterates through possible combinations lexicographically, assuming colors are represented by characters starting from ‘a’, up to NCOLORS.

def next_comb(comb: str) -> str:
    i = len(comb) - 1
    res = ""
    
    # Iterate backwards, simulating addition with carry
    while i >= 0:
        # Check if the current color is the maximum allowed color (NCOLORS is assumed)
        if ord(comb[i]) - ord('a') + 1 == NCOLORS:
            res = 'a' + res # Reset to 'a' (carry over)
        else:
            # Increment the color character
            aux = chr(ord(comb[i]) + 1)
            res = aux + res
            break 
        i -= 1 
        
    # Handle the prefix (characters that didn't carry)
    res = comb[:i+1] + res
    return res

5. Finding the Next Compatible Combination

def next_compatible_comb(current: str, history: dict) -> str:
   ""Finds the next combination that is consistent with the game history""
    
    current = next_comb(current)
    while not is_compatible_history(current, history):
        current = next_comb(current)
        
    return current

6. Mastermind Game Simulation

def game(secret: str) -> dict:
   ""Simulates a Mastermind game using an iterative solving strategy""
    # Start with the initial guess (e.g., "aaaa", assuming SIZE is defined)
    current = "a" * SIZE 
    clue = get_clues(secret, current)
    
    hist = {
        'guesses': [current],
        'clues': [clue]
    }
    
    # Loop until the number of Black Pegs (index 1 of clue) equals the code size
    while hist["clues"][-1][1] != SIZE: 
        current = next_compatible_comb(current, hist)
        clue = get_clues(secret, current)
        hist["guesses"].append(current)
        hist["clues"].append(clue)
        
    return hist

Text Frequency Analysis Functions

These functions are designed for basic text processing, including word extraction, custom lowercasing (handling accented characters), and calculating word frequencies.

1. Utility Functions for Character Checking

def is_in(s: str, ltr: str) -> bool:
   ""Checks if a letter (ltr) is present in a string (s)""
    i = 0
    while i < len(s) and s[i] != ltr:
        i = i + 1
    return i < len(s)

def get_word(txt: str, pos: int) -> tuple[str, int]:
   ""Extracts a word starting from position 'pos', skipping punctuation (PUNCTUATION is assumed)""
    i = pos
    # Skip leading punctuation
    while i < len(txt) and is_in(PUNCTUATION, txt[i]):
        i += 1
    
    word = ''
    # Collect characters until punctuation is found
    while i < len(txt) and not is_in(PUNCTUATION, txt[i]):
        word += txt[i]
        i += 1
    return word, i

2. Custom Lowercasing Function

The lower function ensures proper conversion to lowercase, specifically handling common Spanish accented uppercase letters.

def lower(word: str) -> str:
    new = ''
    for ltr in word:
        if 'A' <= ltr <= 'Z':
            # Standard ASCII uppercase to lowercase conversion
            ltr = chr(ord('a') + ord(ltr) - ord('A'))
        # Handling specific accented characters
        elif ltr == 'Á':
            ltr = 'á'
        elif ltr == 'É':
            ltr = 'é'
        elif ltr == 'Í':
            ltr = 'í'
        elif ltr == 'Ó':
            ltr = 'ó'
        elif ltr == 'Ú':
            ltr = 'ú'
        elif ltr == 'Ü':
            ltr = 'ü'
        elif ltr == 'Ñ':
            ltr = 'ñ'
        new = new + ltr
    return new

3. Calculating Word Frequencies

def frequency(text: str) -> dict:
   ""Calculates the raw frequency count of words in a text""
    fr = {}
    i = 0
    while i < len(text):
        word, end_pos = get_word(text, i) 
        word = lower(word) 
        
        if word in fr:
            fr[word] += 1
        else:
            fr[word] = 1
        i = end_pos 
    return fr

def frequency_rel(freq: dict) -> None:
   ""Converts raw frequencies into relative frequencies (proportions)""
    total = 0
    # Calculate the total number of words
    for count in freq.values(): 
        total += count
        
    # Divide each count by the total
    for word in freq.keys():
        freq[word] /= total

Sorting Algorithms: Merge and Partition

This section provides implementations for fundamental components of common comparison-based sorting algorithms.

1. Merge Function (Merge Sort Component)

The merge function combines two sorted sub-arrays (from low to mid and mid+1 to high) into a single sorted array using an auxiliary list.

def merge(lst: list, low: int, mid: int, high: int, aux: list) -> None:
    i1 = low
    i2 = mid + 1
    i3 = low
    
    # Merge elements from both halves into aux
    while i1 <= mid and i2 <= high:
        if lst[i1] < lst[i2]:
            aux[i3] = lst[i1]
            i1 += 1
        else:
            aux[i3] = lst[i2]
            i2 += 1
        i3 += 1
            
    # Copy remaining elements from the first half
    while i1 <= mid:
        aux[i3] = lst[i1]
        i1 += 1
        i3 += 1
        
    # Copy remaining elements from the second half
    while i2 <= high:        
        aux[i3] = lst[i2]
        i2 += 1
        i3 += 1
        
    # Copy sorted elements back to the original list
    lst[low:high+1] = aux[low:high+1]

2. Three-Way Partition Function (Quicksort Component)

The partition function rearranges elements around a given pivot, resulting in three sections: elements less than the pivot, elements equal to the pivot, and elements greater than the pivot.

def partition(lst: list, pivot, low: int, high: int) -> tuple[int, int]:
    lw = low  # Boundary for elements < pivot
    bg = high # Boundary for elements > pivot
    i = low
    
    while i <= bg:
        if lst[i] < pivot:
            # Swap current element with element at lw
            lst[i], lst[lw] = lst[lw], lst[i]
            lw += 1
            i += 1
        elif lst[i] > pivot:
            # Swap current element with element at bg
            lst[i], lst[bg] = lst[bg], lst[i]
            bg -= 1
            # i is not incremented because the swapped element needs checking
        else:
            # Element equals pivot
            i += 1
            
        print(lst) 
            
    # Returns the start index of the equal section (lw) and the end index + 1 (bg+1)
    return lw, bg + 1