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 __get_best_reduction_variable_value(
        constraint_problem: ConstraintProblem,
        constraints_weights: Dict[Constraint, int],
        read_only_variables: FrozenSet[Variable]) -> Tuple[int, Variable, Any]:
    pairs_to_weight_reduction = dict()
    weight = __calculate_weight(constraint_problem, constraints_weights)
    original_assignment = constraint_problem.get_current_assignment()
    constraint_problem.unassign_all_variables()
    for variable in constraint_problem.get_variables() - read_only_variables:
        for value in variable.domain:
            variable.assign(value)
            curr_weight = __calculate_weight(constraint_problem,
                                             constraints_weights)
            pairs_to_weight_reduction[(variable, value)] = weight - curr_weight
            variable.unassign()

    constraint_problem.unassign_all_variables()
    constraint_problem.assign_variables_from_assignment(original_assignment)
    max_variable, max_value = max(pairs_to_weight_reduction,
                                  key=pairs_to_weight_reduction.get)
    return pairs_to_weight_reduction[(max_variable,
                                      max_value)], max_variable, max_value
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
def generate_start_state_randomly(
        constraint_problem: ConstraintProblem) -> None:
    constraint_problem.unassign_all_variables()
    for var in constraint_problem.get_variables():
        var.assign(choice(var.domain))