Search code examples
pythonmatrixprintingcolorscolorama

I can't get a letter matrix to print partially in color (highlighting specific characters)


I've been trying to create a program using python that will take a text, submitted by the user, and scramble its letters into a matrix (count the letter characters, exclude symbols and spaces and generate the matrix in question, filling it in with the letters in a random order). A second part of the program looks within this matrix and try to find words in the same way one would in a 'word search' puzzle. The program searches for specific words stored in an external .json file. These two parts seem to work properly. The last thing I need the program to do – and the thing that is giving me trouble – is print out the matrix highlighting the words found by the program (either in a different color, or bold, or by making them uppercase, anything). I am not very well-versed in python so everything else I've done has been by piecing together advice from forums and, in some instances, AI, so I don't know where the issue with the program comes in. I've been using Visual Studio Code as an environment, just in case that matters.

Here is the code in question:


from colorama import init, Fore, Back, Style

init()  # Initialize Colorama
import random
import math
import json

def generate_matrix(text):
    # Remove spaces, commas, periods, numbers, and other symbols from the text
    allowed_characters = ''.join(c for c in text if c.isalpha())
    
    # Convert to lowercase
    allowed_characters = allowed_characters.lower()

    # Get the length of the text and calculate the size of the square matrix
    length = len(allowed_characters)
    matrix_size = int(round(math.sqrt(length)))

    # Fill the text with white spaces to complete the square matrix
    allowed_characters = allowed_characters + ' ' * (matrix_size ** 2 - length)

    # Create a list with the allowed characters
    characters = list(allowed_characters)

    # Randomly shuffle the characters
    random.shuffle(characters)

    # Create the square matrix
    matrix = [characters[i:i+matrix_size] for i in range(0, matrix_size**2, matrix_size)]

    return matrix

def search_words(matrix, words):
    found = []
    for word in words:
        for i, row in enumerate(matrix):
            if word in row:
                # Highlight the letters of the found word in red
                highlighted_word = ''.join(
                    [Fore.RED + letter + Style.RESET_ALL if letter in word else letter for letter in row])
                found.append(highlighted_word)
                break
        else:
            for j in range(len(matrix[0])):
                if word in ''.join(row[j] for row in matrix):
                    # Highlight the letters of the found word in red
                    highlighted_word = ''.join(
                        [Fore.RED + matrix[i][j] + Style.RESET_ALL if matrix[i][j] in word else matrix[i][j] for i in
                         range(len(matrix))])
                    found.append(highlighted_word)
                    break
            else:
                diagonal = ''.join(matrix[i][i] for i in range(len(matrix)))
                if word in diagonal:
                    # Highlight the letters of the found word in red
                    highlighted_word = ''.join(
                        [Fore.RED + matrix[i][i] + Style.RESET_ALL if matrix[i][i] in word else matrix[i][i] for i in
                         range(len(matrix))])
                    found.append(highlighted_word)
                else:
                    diagonal_inverted = ''.join(matrix[i][len(matrix) - 1 - i] for i in range(len(matrix)))
                    if word in diagonal_inverted:
                        # Highlight the letters of the found word in red
                        highlighted_word = ''.join(
                            [Fore.RED + matrix[i][len(matrix) - 1 - i] + Style.RESET_ALL if
                             matrix[i][len(matrix) - 1 - i] in word else matrix[i][len(matrix) - 1 - i] for i in
                             range(len(matrix))])
                        found.append(highlighted_word)

    return found

# Read words from the JSON file
with open('words_dictionary.json', 'r') as file:
    words_to_search = json.load(file)

# Filter out words that are less than four characters long
words_to_search = [word for word in words_to_search if len(word) >= 4]

# Ask the user to input a text
text_input = input("Enter a text: ")

# Generate the square matrix
generated_matrix = generate_matrix(text_input)

# Print the generated matrix
print("Generated matrix:")
for row in generated_matrix:
    print(' '.join(row))

# Search for words in the matrix
found_words = search_words(generated_matrix, words_to_search)

# Print the found words
if found_words:
    print("\nFound words:")
    for word in found_words:
        print(word)
else:
    print("\nNo words found.")

Here is an image of the output:

Text, its matrix & the words found

This was my attempt at highlighting the words in red, but I've also tried both turning them uppercase and bold, with similar results. The idea would be to get the same generated matrix printed out and have the words be red (or whatever) within it. Thank you all so much for your help, let me know if there is any other info you need from me!


Solution

  • Your logic for finding and highlighting words has gotten a little convoluted. To some degree that's inevitable, but you'll find you have a much easier time if you break it down into bite-sized chunks. I recommend starting fresh on that part.
    You know you need to highlight specific letters within your matrix. Make a function to do that, agnostic of specific words. The arguments will be the matrix and a list of coordinate pairs specifying which letters should be highlighted. The return value is a copy of the matrix with the necessary highlighting.

    def highlight_chars(matrix, indices):
        result = matrix.copy()
        for i, j in indices:
            result[i][j] = Fore.RED + matrix[i][j] + Style.RESET_ALL
        return result
    

    Once you have that, you can start writing functions that find words and spit out coordinates for the letters in those words. I would write a separate function for each search direction. Here's a couple to get you started:

    def find_horizontal(matrix, words):
        indices = []
        for i, row in enumerate(matrix):
            row_str = ''.join(row)
            for word in words:
                start_index = row_str.find(word)
                if start_index >= 0:
                    for k in range(len(word)):
                        indices.append((i, start_index + k))
        return indices
    
    def find_vertical(matrix, words):
        indices = []
        for j in range(len(matrix[0])):
            column_str = ''.join(row[j] for row in matrix)
            for word in words:
                start_index = column_str.find(word)
                if start_index >= 0:
                    for k in range(len(word)):
                        indices.append((start_index + k, j))
        return indices
    

    If you want to add features like diagonals and searching backwards, you should be able to implement functions for those without too much trouble using similar methods.
    Finally, tie it all together with your search_words() function:

    def search_words(matrix, words):
        indices = find_horizontal(matrix, words)
        indices += find_vertical(matrix, words)
        
        found = highlight_chars(matrix, indices)
        return found
    

    Note that this will return a matrix, so you'll want to print it the same way you print it row-by-row the same way you printed the first one.
    Unfortunately, if you use a large word dictionary (like this one with over 370,000 words) you may end up with more highlighted than not, making the output difficult to parse.