# Problem Description
- The task involves validating a 2D matrix (matrix) with specific rules (rules). The rules matrix contains two types of constraints:
    E: Denotes that the elements must be equal in the matrix.
    N: Denotes that the elements must be not equal in the matrix.
- The goal is to check whether the matrix adheres to these constraints as specified by the rules matrix.

Inputs:
    rules: A 2D grid containing 'E' (equal) or 'N' (not equal).
    matrix: A 2D grid of integers.
Output:
    A boolean indicating whether the matrix satisfies all the constraints in the rules.

# Visual Facts
From the image, we can observe the following explicit facts:
- Example 1:
    - rules = [[E], [N, N], [E]]
    - matrix = [[1, 1], [2, 2]]
    - The combined result satisfies the rules:
        First Row:
            1 and 1: Equal (corresponds to rule E, satisfied).
        First Column:
            1 and 2: Not equal (corresponds to rule N, satisfied).
        Second Column:
            1 and 2: Not equal (corresponds to rule N, satisfied).
        Second Row:
            2 and 2: Equal (corresponds to rule E, satisfied).
    - The result is Valid.

Example 2 (Valid):
    - rules = [[E, N], [N, N, E], [E, N]]
    - matrix = [[1, 1, 2], [3, 3, 2]]
    - The combined result satisfies the rules:
        First Row:
            1 and 1 are E (equal), satisfying the rule.
            1 and 2 are N (not equal), satisfying the rule.
        First Column:
            1 and 3 are N (not equal), satisfying the rule.
        Second Column:
            1 and 3 are N (not equal), satisfying the rule.
        Third Column:
            2 and 2 are E (equal), satisfying the rule.
        Second Row:
            3 and 3 are E (equal), satisfying the rule.
            3 and 2 are N (not equal), satisfying the rule.
    - The result is Valid.
Example 3 (Invalid):
    - rules = [[E, N], [N, N, E], [E, N]]
    - matrix = [[1, 2, 2], [3, 2, 2]]
    - The combined result violates the rules:
        First Row:
            1 and 2: Not equal (violate rule E).
            2 and 2: Equal (violate rule N).
        First Column:
            1 and 3: Not equal (corresponds to rule N, satisfied).
        Second Column:
            2 and 2: Equal, but the rule is N (not equal), violates the rule.
        Third Column:
            2 and 2: Equal (corresponds to rule E, satisfied).
        Second Row:
            3 and 2: Not equal (violate rule E).
            2 and 2: Equal (violate rule N).
    - The result is Invalid.

# Visual Patterns
1. Equality Rules (E):
    If a cell in rules is labeled E, the corresponding cells in matrix must contain the same value.

2. Inequality Rules (N):
    If a cell in rules is labeled N, the corresponding cells in matrix must contain different values.
3. Correspondence between rules and matrix:
    Horizontal Relationship: 
        - Rule: rules[2 * i][j]
        - Constraint: Applies to matrix[i][j] and matrix[i][j + 1].
    Vertical Relationship:
        - Rule: rules[2 * i + 1][j]
        - Constraint: Applies to matrix[i][j] and matrix[i + 1][j].
4. The rules and matrix matrices must have consistent dimensions for the validation process.
    Validation Process:
    - For each pair of neighboring cells in matrix, check the corresponding entry in rules to validate whether the values satisfy the E or N condition.
    - Iterate over the entire rules matrix and check all pairs of constrained cells.
5. Invalid Scenarios:
    - The result is invalid if any single constraint in rules is violated in the matrix.


# Solution
from typing import List

def solution(rules: List[List[str]], matrix: List[List[int]]) -> bool:
    """
    Validates whether a given matrix satisfies the specified rules.

    Args:
        rules: A 2D list of strings containing 'E' or 'N', representing equality/inequality constraints
        matrix: A 2D list of integers to be validated against the rules

    Returns:
        bool: True if the matrix satisfies all rules, False otherwise
    """
    rows = len(matrix)
    cols = len(matrix[0])
    for i in range(rows):
        for j in range(cols):
            if i < rows - 1:
                if rules[2 * i + 1][j] == 'E':
                    if matrix[i][j] != matrix[i + 1][j]:
                        return False
                elif rules[2 * i + 1][j] == 'N':
                    if matrix[i][j] == matrix[i + 1][j]:
                        return False
            if j < cols - 1:
                if rules[2 * i][j] == 'E':
                    if matrix[i][j] != matrix[i][j + 1]:
                        return False
                elif rules[2 * i][j] == 'N':
                    if matrix[i][j] == matrix[i][j + 1]:
                        return False
    return True


# Test Script
def test():
    # Test case 1
    relations = [
        ['E'],
        ['N', 'N'],
        ['E']
    ]
    filled_matrix = [
        [1, 1],
        [2, 2]
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n" 
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != True:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: True"
        )
        assert False, error_message

    # Test case 2    
    filled_matrix = [
        [1, 1],
        [1, 2]
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != False:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: False"
        )
        assert False, error_message
        
    # Test case 3
    relations = [
        ['E'],
        ['E', 'E'],
        ['E']
    ]
    filled_matrix = [
        [1, 1],
        [1, 2]
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != False:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: False"
        )
        assert False, error_message
        
    # Test case 4
    filled_matrix = [
        [1, 1],
        [1, 1]
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != True:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: True"
        )
        assert False, error_message
        
    # Test case 5
    relations = [
        ['E', 'N'],
        ['N', 'N', 'E'],
        ['E', 'N']
    ]
    filled_matrix = [
        [1, 1, 3],
        [2, 2, 3]
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != True:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: True"
        )
        assert False, error_message
        
    # Test case 6
    filled_matrix = [
        [1, 1, 3],
        [2, 2, 2]
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != False:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: False"
        )
        assert False, error_message
        
    # Test case 7
    relations = [
        ['E', 'N','E'],
        ['N', 'N', 'E', 'E'],
        ['N', 'E', 'E'],
        ['E', 'N', 'E', 'N'],
        ['E', 'N', 'N']
    ]
    filled_matrix = [
        [1, 1, 2, 2],
        [3, 2, 2, 2],
        [3, 3, 2, 1],
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != True:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: True"
        )
        assert False, error_message
        
    # Test case 8
    filled_matrix = [
        [1, 1, 2, 2],
        [3, 2, 2, 2],
        [3, 3, 2, 2],
    ]
    try:
        result = solution(relations, filled_matrix)
    except Exception as e:
        error_message = (
            f"An exception occurred while running the test case:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Exception: '{str(e)}'"
        )
        assert False, error_message
        
    if result != False:
        error_message = (
            f"The following Test Case failed:\n"
            f"`rules`: {relations}\n"
            f"`matrix`: {filled_matrix}\n\n"
            f"Actual Output: {result}\n"
            f"Expected Output: False"
        )
        assert False, error_message

test()