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 __heuristic_backtrack(
        constraint_problem: ConstraintProblem,
        primary_select_unassigned_vars:
    SelectUnassignedVariables = minimum_remaining_values,
        secondary_select_unassigned_vars:
    SelectUnassignedVariables = degree_heuristic,
        sort_domain: SortDomain = least_constraining_value,
        inference: Optional[Inference] = None,
        find_all_solutions: bool = False,
        with_history: bool = False) -> Optional[Dict[Variable, Any]]:
    selected_unassigned_vars = primary_select_unassigned_vars(
        constraint_problem, None)
    if secondary_select_unassigned_vars is not None and len(
            selected_unassigned_vars) > 1:
        selected_unassigned_vars = secondary_select_unassigned_vars(
            constraint_problem, selected_unassigned_vars)
    selected_variable, *_ = selected_unassigned_vars

    sorted_domain = sort_domain(constraint_problem, selected_variable)
    for value in sorted_domain:
        selected_variable.assign(value)
        if with_history:
            __actions_history.append((selected_variable, value))

        if inference is not None and not inference(constraint_problem,
                                                   selected_variable):
            selected_variable.unassign()
            if with_history:
                __actions_history.append((selected_variable, None))
            continue

        if constraint_problem.is_completely_assigned():
            if constraint_problem.is_consistently_assigned():
                if find_all_solutions:
                    yield constraint_problem.get_current_assignment()
                else:
                    yield None

            selected_variable.unassign()
            if with_history:
                __actions_history.append((selected_variable, None))
            continue

        if constraint_problem.is_consistently_assigned():
            for solution_assignment in __heuristic_backtrack(
                    constraint_problem, primary_select_unassigned_vars,
                    secondary_select_unassigned_vars, sort_domain, inference,
                    find_all_solutions, with_history):
                yield solution_assignment

        selected_variable.unassign()
        if with_history:
            __actions_history.append((selected_variable, None))
def __forward_checking_backtrack(
        constraint_problem: ConstraintProblem,
        find_all_solutions: bool = False,
        with_history: bool = False) -> Optional[Dict[Variable, Any]]:
    variable, *_ = constraint_problem.get_unassigned_variables()
    for value in variable.domain:
        variable.assign(value)
        if with_history:
            __actions_history.append((variable, value))

        unassigned_neighbors_frozenset = constraint_problem.get_unassigned_neighbors(
            variable)
        unsatisfiable_neighbors = filter(
            lambda unassigned_neighbor: not constraint_problem.
            get_consistent_domain(unassigned_neighbor),
            unassigned_neighbors_frozenset)
        if any(unsatisfiable_neighbors):
            variable.unassign()
            if with_history:
                __actions_history.append((variable, None))
            continue

        if constraint_problem.is_completely_assigned():
            if constraint_problem.is_consistently_assigned():
                if find_all_solutions:
                    yield constraint_problem.get_current_assignment()
                else:
                    yield None

            variable.unassign()
            if with_history:
                __actions_history.append((variable, None))
            continue

        if constraint_problem.is_consistently_assigned():
            for solution_assignment in __forward_checking_backtrack(
                    constraint_problem, find_all_solutions, with_history):
                yield solution_assignment

        variable.unassign()
        if with_history:
            __actions_history.append((variable, None))
def __backtrack(constraint_problem: ConstraintProblem,
                inference: Optional[Inference] = None,
                find_all_solutions: bool = False,
                with_history: bool = False) -> Optional[Dict[Variable, Any]]:
    variable, *_ = constraint_problem.get_unassigned_variables()
    for value in variable.domain:
        variable.assign(value)
        if with_history:
            __actions_history.append((variable, value))

        if inference is not None and not inference(constraint_problem,
                                                   variable):
            variable.unassign()
            if with_history:
                __actions_history.append((variable, None))
            continue

        if constraint_problem.is_completely_assigned():
            if constraint_problem.is_consistently_assigned():
                if find_all_solutions:
                    yield constraint_problem.get_current_assignment()
                else:
                    yield None

            variable.unassign()
            if with_history:
                __actions_history.append((variable, None))
            continue

        if constraint_problem.is_consistently_assigned():
            for solution_assignment in __backtrack(constraint_problem,
                                                   inference,
                                                   find_all_solutions,
                                                   with_history):
                yield solution_assignment

        variable.unassign()
        if with_history:
            __actions_history.append((variable, None))
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 __optimized_heuristic_backtrack(constraint_problem: ConstraintProblem,
                                    find_all_solutions: bool = False,
                                    with_history: bool = False):
    unassigned_variables = constraint_problem.get_unassigned_variables()
    min_variable = min(
        unassigned_variables,
        key=lambda var: len(constraint_problem.get_consistent_domain(var)))
    min_remaining_values = len(
        constraint_problem.get_consistent_domain(min_variable))
    min_variables = filter(
        lambda var: len(constraint_problem.get_consistent_domain(var)) ==
        min_remaining_values, unassigned_variables)
    selected_unassigned_vars = frozenset(min_variables)
    if len(selected_unassigned_vars) > 1:
        selected_variable = max(
            selected_unassigned_vars,
            key=lambda var: len(
                constraint_problem.get_unassigned_neighbors(var)))
    else:
        selected_variable, *_ = selected_unassigned_vars

    unassigned_neighbors = constraint_problem.get_unassigned_neighbors(
        selected_variable)

    def neighbors_consistent_domain_lengths(val) -> int:
        selected_variable.assign(val)
        consistent_domain_lengths = map(
            lambda neighbor: len(
                (constraint_problem.get_consistent_domain(neighbor))),
            unassigned_neighbors)
        selected_variable.unassign()
        return sum(consistent_domain_lengths)

    sorted_domain = sorted(
        constraint_problem.get_consistent_domain(selected_variable),
        key=neighbors_consistent_domain_lengths,
        reverse=True)

    for value in sorted_domain:
        selected_variable.assign(value)
        if with_history:
            __actions_history.append((selected_variable, value))

        if constraint_problem.is_completely_assigned():
            if constraint_problem.is_consistently_assigned():
                if find_all_solutions:
                    yield constraint_problem.get_current_assignment()
                else:
                    yield None

            selected_variable.unassign()
            if with_history:
                __actions_history.append((selected_variable, None))
            continue

        if constraint_problem.is_consistently_assigned():
            for solution_assignment in __optimized_heuristic_backtrack(
                    constraint_problem, find_all_solutions, with_history):
                yield solution_assignment

        selected_variable.unassign()
        if with_history:
            __actions_history.append((selected_variable, None))