def min_conflicts(
        constraint_problem: ConstraintProblem,
        max_steps: int,
        tabu_size: int = -1,
        with_history: bool = False) -> Optional[Deque[Tuple[Variable, Any]]]:
    __tabu_queue.clear()
    read_only_variables = constraint_problem.get_assigned_variables()

    if tabu_size == -1:
        tabu_size = 0
    assert tabu_size + len(read_only_variables) < len(constraint_problem.get_variables()), \
        "tabu_size + len(read_only_variables) is equal or bigger than constraint_problem's variables amount."
    if tabu_size == 0:
        tabu_size = -1

    actions_history = None
    if with_history:
        actions_history = deque()
    rand_assignmt_history = constraint_problem.assign_variables_with_random_values(
        read_only_variables, actions_history)
    if with_history:
        actions_history.extend(rand_assignmt_history)

    best_min_conflicts = len(constraint_problem.get_unsatisfied_constraints())
    best_min_conflicts_assignment = constraint_problem.get_current_assignment()
    for i in range(max_steps):
        if constraint_problem.is_completely_consistently_assigned():
            return actions_history

        conflicted_variable = __get_random_conflicted_variable(
            constraint_problem, read_only_variables, tabu_size)
        conflicted_variable.unassign()
        if with_history:
            actions_history.append((conflicted_variable, None))
        min_conflicts_value = __get_min_conflicts_value(
            constraint_problem, conflicted_variable)
        conflicted_variable.assign(min_conflicts_value)
        if with_history:
            actions_history.append((conflicted_variable, min_conflicts_value))

        if len(__tabu_queue) == tabu_size:
            __tabu_queue.popleft()
        if __tabu_queue:
            __tabu_queue.append(conflicted_variable)

        curr_conflicts_count = len(
            constraint_problem.get_unsatisfied_constraints())
        if curr_conflicts_count < best_min_conflicts:
            best_min_conflicts = curr_conflicts_count
            best_min_conflicts_assignment = constraint_problem.get_current_assignment(
            )

    constraint_problem.unassign_all_variables()
    constraint_problem.assign_variables_from_assignment(
        best_min_conflicts_assignment)
    return actions_history
def constraints_weighting(constraint_problem: ConstraintProblem, max_tries: int, with_history: bool = False) \
        -> Optional[Deque[Tuple[Variable, Any]]]:
    actions_history = None
    if with_history:
        actions_history = deque()
    constraints_weights = {
        constraint: 1
        for constraint in constraint_problem.get_constraints()
    }
    read_only_variables = constraint_problem.get_assigned_variables()

    for i in range(max_tries):
        constraint_problem.assign_variables_with_random_values(
            read_only_variables)
        last_reduction = float("inf")
        while 0 < last_reduction:
            if constraint_problem.is_completely_consistently_assigned():
                return actions_history

            reduction, variable, value = __get_best_reduction_variable_value(
                constraint_problem, constraints_weights, read_only_variables)
            variable.unassign()
            if with_history:
                actions_history.append((variable, None))
            variable.assign(value)
            if with_history:
                actions_history.append((variable, value))
            last_reduction = reduction

            for unsatisfied_constraint in constraint_problem.get_unsatisfied_constraints(
            ):
                constraints_weights[unsatisfied_constraint] += 1

        if i != max_tries - 1:
            constraint_problem.unassign_all_variables(read_only_variables)

    return actions_history