예제 #1
0
 def static(self, variables, *, order):
     variables = flatten(variables)
     checkType(variables, [Variable])
     order = flatten(order)
     checkType(order, [int])
     self.staticPart = (variables, order)
     return self
예제 #2
0
def NValues(term, *others, excepting=None, condition=None):
    terms = flatten(term, others)
    checkType(terms, [Variable])
    if excepting is not None:
        excepting = flatten(excepting)
        checkType(excepting, [int])
    return _wrapping_by_complete_or_partial_constraint(ConstraintNValues(terms, excepting, Condition.build_condition(condition)))
예제 #3
0
def Cumulative(*, origins, lengths, ends=None, heights, condition=None):
    origins = flatten(origins)
    checkType(origins, [Variable])
    lengths = flatten(lengths)
    checkType(lengths, ([Variable], [int]))
    heights = flatten(heights)
    checkType(heights, ([Variable], [int]))
    if ends is not None: ends = flatten(ends)
    checkType(ends, ([Variable], type(None)))
    return _wrapping_by_complete_or_partial_constraint(ConstraintCumulative(origins, lengths, ends, heights, Condition.build_condition(condition)))
예제 #4
0
파일: curser.py 프로젝트: cprudhom/pycsp3
 def Instantiation(*, variables, values):
     variables = flatten(variables)
     values = flatten(values) if not isinstance(values, range) else list(values)
     checkType(variables, [Variable])
     checkType(values, (int, [int]))
     if len(variables) == 0:
         return None
     if len(values) == 1 and len(variables) > 1:
         values = [values[0]] * len(variables)
     return ConstraintInstantiation(variables, values)
예제 #5
0
def Channel(list1, list2=None, *, start_index1=0, start_index2=0):
    list1 = flatten(list1)
    checkType(list1, [Variable])
    if list2:
        list2 = flatten(list2)
        checkType(list2, [Variable])
    checkType(start_index1, int)
    checkType(start_index2, int)
    assert start_index2 == 0 or list2 is not None, "start_index2 is defined while list2 is not specified"
    return ECtr(ConstraintChannel(list1, start_index1, list2, start_index2))
예제 #6
0
def Clause(term, *others, phases=None):
    literals = flatten(term, others)
    phases = [False] * len(literals) if phases is None else flatten(phases)
    assert len(literals) == len(phases)
    checkType(literals, ([Variable, NotVariable]))
    checkType(phases, [bool])
    for i, literal in enumerate(literals):
        if isinstance(literal, NotVariable):
            literals[i] = literal.variable
            phases[i] = True
    return ECtr(ConstraintClause(literals, phases))
예제 #7
0
def AllDifferent(term, *others, excepting=None, matrix=None):
    terms = flatten(term, others)
    if matrix is not None:
        assert excepting is None, "excepting values are currently not supported for AllDifferentMatrix"
        matrix = [flatten(row) for row in terms]
        assert all(len(row) == len(matrix[0]) for row in matrix), "The matrix id badly formed"
        assert all(checkType(l, [Variable]) for l in matrix)
        return ECtr(ConstraintAllDifferentMatrix(matrix))
    excepting = list(excepting) if isinstance(excepting, (tuple, set)) else [excepting] if isinstance(excepting, int) else excepting
    checkType(terms, ([Variable, Node]))
    checkType(excepting, ([int], type(None)))
    return ECtr(ConstraintAllDifferent(terms, excepting))
예제 #8
0
def _lex(term, others, operator, matrix):
    if len(others) == 0:
        assert is_matrix(term, Variable)
        lists = [flatten(l) for l in term]
    else:
        assert is_1d_list(term, Variable) and all(is_1d_list(l, Variable) for l in others)
        lists = [flatten(term)] + [flatten(l) for l in others]
    assert is_matrix(lists, Variable)  # new check because some null cells (variables) may have been discarded
    assert all(len(l) == len(lists[0]) for l in lists)
    checkType(lists, [Variable])
    checkType(operator, TypeOrderedOperator)
    return ECtr(ConstraintLexMatrix(lists, operator)) if matrix else ECtr(ConstraintLex(lists, operator))
예제 #9
0
 def __init__(self, variables, coefficients):
     variables = list(variables) if isinstance(variables,
                                               tuple) else variables
     coefficients = list(coefficients) if isinstance(
         coefficients, tuple) else coefficients
     assert isinstance(variables, list) and isinstance(
         coefficients, (int, list, range)), variables
     self.variables = flatten(
         variables)  # for example, in order to remove None occurrences
     self.coeffs = flatten(
         [coefficients] *
         len(variables) if isinstance(coefficients, int) else coefficients)
     assert len(self.variables) == len(
         self.coeffs), str(self.variables) + " " + str(self.coeffs)
예제 #10
0
def Extension(*, scope, table, positive=True):
    scope = flatten(scope)
    checkType(scope, [Variable])
    assert isinstance(table, list)
    if any(isinstance(v, ConditionValue) for t in table for v in t):  # if smart table
        table = sorted(list(to_ordinary_table(table, [x.dom for x in scope], keep_any=True)))

    checkType(table, [str, int, float])
    checkType(positive, bool)
    assert isinstance(table, list) and len(table) > 0, "A table must be a non-empty list of tuples or integers (or symbols)"
    assert isinstance(table[0], (tuple, int, str)), "Elements of tables are tuples or integers (or symbols)"
    #print(table)
    assert isinstance(table[0], (int, str)) or len(scope) == len(table[0]), (
         "The length of each tuple must be the same as the arity." + "Maybe a problem with slicing: you must for example write x[i:i+3,0] instead of x[i:i+3][0]")
    # TODO: this ckecking don't pass on Waterbucket.py, but the xml file is the same that the java version !
    # if options.checker:
    #    if not hasattr(Extension, "checked_tables"):
    #        Extension.checked_tables = set()
    #    if id(table) not in checked_tables:
    #        for t in table:
    #            for i, v in enumerate(t):
    #                if v not in variables[i].dom:
    #                    raise ValueError(
    #                        "Problem: Constraint extension : a value of table is not represented in a domain of a variable : " + str(domainElement))
    #        checked_tables.add(id(table))
    return ECtr(ConstraintExtension(scope, table, positive))
예제 #11
0
def _expand(compact_form):
    assert " " not in compact_form, "The specified string must correspond to a single token; bad form : " + compact_form
    if compact_form[-1] == ")":
        return compact_form  # // this means that we have an expression (predicate) here
    if "[" not in compact_form:
        return compact_form
    pos = compact_form.index("[")
    prefix, suffix = compact_form[:pos], compact_form[pos:]
    tokens = [int(v) if v.isdigit() else v for v in re.split("\]\[", suffix[1:-1])]
    var_array = VarEntities.prefixToEVarArray[prefix]
    assert var_array, "Pb with " + compact_form
    assert len(var_array.size) == len(tokens)
    mins, maxs, sizes = [0] * len(tokens), [0] * len(tokens), [0] * len(tokens)
    for i, value in enumerate(tokens):
        if isinstance(value, int):
            mins[i] = maxs[i] = value
        elif value == "":
            mins[i] = 0
            maxs[i] = var_array.size[i] - 1
        else:
            spl = value.split("..")
            mins[i], maxs[i] = int(spl[0]), int(spl[1])
        sizes[i] = maxs[i] - mins[i] + 1

    var_names = flatten(Variable.build_names_array(prefix, sizes, mins))
    return " ".join(s for s in var_names)
예제 #12
0
 def build(type, *args):
     type = TypeNode.value_of(
         type
     )  # for handling the cases where type is of type str or TypeConditionOperator
     if type is TypeNode.SET:
         assert len(args) == 1
         elements = list(args[0])
         sorted_sons = sorted(
             elements,
             key=lambda v: str(v)) if len(elements) > 0 and not isinstance(
                 elements[0], int) else sorted(elements)
         return Node(type,
                     Node._create_sons(*sorted_sons))  # *sorted(args[0])))
     args = flatten(
         Node.build(TypeNode.SET, arg
                    ) if isinstance(arg, (set, range, frozenset)) else arg
         for arg in args)
     assert type.is_valid_arity(
         len(args)
     ), "Problem: Bad arity for node " + type.name + ". It is " + str(
         len(args)) + " but it should be between " + str(
             type.min_arity) + " and " + str(type.max_arity)
     # Do we activate these simple modifications below?
     # if len(args) == 2 and isinstance(args[0], Variable) and isinstance(args[1], int):
     #     if (args[1] == 1 and type in (TypeNode.MUL, TypeNode.DIV)) or (args[1] == 0 and type in (TypeNode.ADD, TypeNode.SUB)):
     #         return Node(TypeNode.VAR,args[0])
     node = Node(type, Node._create_sons(*args))
     if type == TypeNode.EQ and all(son.type.is_predicate_operator()
                                    for son in node.sons):
         node = Node(TypeNode.IFF, node.sons)
     # Reducing the node
     for t in {TypeNode.ADD, TypeNode.MUL, TypeNode.OR, TypeNode.AND}:
         node.flatten_by_associativity(t)
     node.reduce_integers()
     return node
예제 #13
0
 def _record_solution(roots, i):
     variables = []
     for token in roots[i][0].text.split():
         r = VarEntities.get_item_with_name(token)
         if isinstance(r, EVar):
             variables.append(r.variable)
         elif isinstance(r, Variable):
             variables.append(r)
         else:
             for x in flatten(r.variables, keep_none=True):
                 variables.append(x)
     if i == 0:  # reset the history in that case
         for x in variables:
             if x:
                 x.values = []
     values = []
     for tok in roots[i][1].text.split():
         if 'x' in tok:  # in order to handle compact forms in solutions
             vk = tok.split('x')
             assert len(vk) == 2
             for _ in range(int(vk[1])):
                 values.append(vk[0])
         else:
             values.append(tok)
     # values is a list with all values given as strings (possibly '*')
     assert len(variables) == len(values)
     for i, _ in enumerate(values):
         if variables[i]:
             if isinstance(variables[i], VariableInteger):
                 values[i] = int(values[i]) if values[i] != "*" else ANY
             variables[i].value = values[i]  # we record the last found solution value
             variables[i].values.append(values[i])  # we record it in the history
     return variables, values
예제 #14
0
파일: entities.py 프로젝트: cprudhom/pycsp3
 def build(type, *args):
     if type is TypeNode.SET:
         assert len(args) == 1
         elements = list(args[0])
         sorted_sons = sorted(
             elements,
             key=lambda v: str(v)) if len(elements) > 0 and not isinstance(
                 elements[0], int) else sorted(elements)
         return Node(type,
                     Node._create_sons(*sorted_sons))  # *sorted(args[0])))
     args = flatten(
         Node.build(TypeNode.SET, arg
                    ) if isinstance(arg, (set, range, frozenset)) else arg
         for arg in args)
     assert type.is_valid_arity(
         len(args)
     ), "Problem: Bad arity for node " + type.name + ". It is " + str(
         len(args)) + " but it should be between " + str(
             type.arityMin) + " and " + str(type.arityMax)
     node = Node(type, Node._create_sons(*args))
     if type == TypeNode.EQ and all(son.type.is_predicate_operator()
                                    for son in node.sons):
         node = Node(TypeNode.IFF, node.sons)
     # Reducing the node
     for t in {
             TypeNode.ADD, TypeNode.MUL, TypeNode.OR, TypeNode.AND,
             TypeNode.EQ, TypeNode.IFF
     }:
         node.flatten_by_associativity(t)
     node.reduce_integers()
     if options.debug:
         print("New node:", node)
     return node
예제 #15
0
def Cardinality(term, *others, occurrences, closed=False):
    terms = flatten(term, others)
    checkType(terms, [Variable])
    assert isinstance(occurrences, dict)
    values = list(occurrences.keys())
    assert all(isinstance(value, (int, Variable)) for value in values)
    occurs = list(occurrences.values())
    checkType(closed, (bool, type(None)))
    for i, occ in enumerate(occurs):
        if isinstance(occ, range):
            occurs[i] = str(min(occ)) + ".." + str(max(occ))
        if isinstance(occ, list):
            flat = flatten(occ)
            if all([isinstance(e, int) for e in flat]) and flat == list(range(min(flat), max(flat) + 1)):
                flat = str(min(flat)) + ".." + str(max(flat))
            occurs[i] = flat
    return ECtr(ConstraintCardinality(terms, values, occurs, closed))
예제 #16
0
def _ordered(term, others, operator, lengths):
    terms = flatten(term, others)
    checkType(terms, [Variable])
    checkType(operator, TypeOrderedOperator)
    checkType(lengths, ([int, Variable], type(None)))
    if lengths is not None:
        if len(terms) == len(lengths):
            lengths = lengths[:-1]  # we assume that the last value is useless
        assert len(terms) == len(lengths) + 1
    return ECtr(ConstraintOrdered(terms, operator, lengths))
예제 #17
0
 def _opt(self, variables, type):
     if variables:
         variables = flatten(variables)
         checkType(variables, [Variable])
     types = TypeVarHeuristic if isinstance(
         self, VarHeuristic) else TypeValHeuristic
     assert isinstance(type, str) and all(
         p in [t.name for t in types]
         for p in re.split(r'/|\+', type)), "Bad value for " + type
     return variables, type
예제 #18
0
def Count(term, *others, value=None, values=None, condition=None):
    terms = flatten(term, others)
    assert len(terms) > 0, "A count with an empty scope"
    checkType(terms, ([Variable], [Node]))
    if value is None and values is None:
        value = 1
    assert value is None or values is None, str(value) + " " + str(values)
    values = list(values) if isinstance(values, (tuple, set)) else [value] if isinstance(value, (int, Variable)) else values
    checkType(values, ([int], [Variable]))
    return _wrapping_by_complete_or_partial_constraint(ConstraintCount(terms, values, Condition.build_condition(condition)))
예제 #19
0
def _extremum(term, others, index, start_index, type_rank, condition, maximum):
    terms = list(term) if isinstance(term, types.GeneratorType) else flatten(term, others)
    terms = [Sum(t) if isinstance(t, ScalarProduct) else t for t in terms]  # to have PartialConstraints
    checkType(terms, ([Variable], [Node], [PartialConstraint]))
    auxiliary().replace_partial_constraints(terms)
    checkType(index, (Variable, type(None)))
    checkType(start_index, int)
    checkType(type_rank, TypeRank)
    assert index is not None or (start_index == 0 and type_rank is TypeRank.ANY)
    return ConstraintMaximum(terms, index, start_index, type_rank, condition) if maximum else ConstraintMinimum(terms, index, start_index, type_rank, condition)
예제 #20
0
def AllDifferentList(lists, *others, excepting=None):
    if isinstance(lists, types.GeneratorType):
        lists = [l for l in lists]
    elif len(others) > 0:
        lists = list((lists,) + others)
    lists = [flatten(l) for l in lists]
    assert all(checkType(l, [Variable]) for l in lists)
    excepting = list(excepting) if isinstance(excepting, (tuple, set)) else excepting
    checkType(excepting, ([int], type(None)))
    assert all(len(l) == len(lists[0]) for l in lists) and (excepting is None or len(excepting) == len(list[0]))
    return ECtr(ConstraintAllDifferentList(lists, excepting))
예제 #21
0
def Sum(term, *others, condition=None):
    def _get_terms_coeffs(terms):
        if len(terms) == 1 and isinstance(terms[0], ScalarProduct):
            return flatten(terms[0].variables), flatten(terms[0].coeffs)
        if all(isinstance(x, (Variable, PartialConstraint)) for x in terms):
            return terms, None
        t1, t2 = [], []
        for tree in terms:
            if isinstance(tree, Variable):
                t1.append(tree)
                t2.append(1)
            else:
                assert isinstance(tree, (Node, NegVariable))
                pair = (tree.variable, -1) if isinstance(tree, NegVariable) else tree.tree_val_if_binary_type(TypeNode.MUL)
                if pair is None:
                    break
                t1.append(pair[0])
                t2.append(pair[1])
        if len(t1) == len(terms):
            for tree in terms:
                if isinstance(tree, Node):
                    tree.mark_as_used()
            return t1, t2
        return terms, None

    def _manage_coeffs(terms, coeffs):
        if coeffs:
            OpOverrider.disable()
            if len(coeffs) == 1 and isinstance(coeffs[0], (tuple, set, range)):
                coeffs = list(coeffs[0])
            elif isinstance(coeffs, (tuple, set, range)):
                coeffs = list(coeffs)
            elif isinstance(coeffs, (int, Variable)):
                coeffs = [coeffs]
            assert len(terms) == len(coeffs), "Lists (vars and coeffs) should have the same length. Here, we have " + str(len(terms)) + "!=" + str(len(coeffs))
            # if 0 in coeffs:
            #    terms = [term for i, term in enumerate(terms) if coeffs[i] != 0]
            #    coeffs = [coeff for coeff in coeffs if coeff != 0]
            # if all(c == 1 for c in coeffs): coeffs = None
            checkType(coeffs, ([Variable, int], type(None)))
            OpOverrider.enable()
        return terms, coeffs

    terms = list(term) if isinstance(term, types.GeneratorType) else flatten(term, others)
    checkType(terms, ([Variable], [Node], [PartialConstraint], [ScalarProduct]))
    auxiliary().replace_partial_constraints(terms)

    terms, coeffs = _get_terms_coeffs(terms)
    terms, coeffs = _manage_coeffs(terms, coeffs)
    # TODO control here some assumptions
    return _wrapping_by_complete_or_partial_constraint(ConstraintSum(terms, coeffs, Condition.build_condition(condition)))
예제 #22
0
 def _get_terms_coeffs(terms):
     if len(terms) == 1 and isinstance(terms[0], ScalarProduct):
         return flatten(terms[0].variables), flatten(terms[0].coeffs)
     if all(isinstance(x, (Variable, PartialConstraint)) for x in terms):
         return terms, None
     t1, t2 = [], []
     for tree in terms:
         if isinstance(tree, Variable):
             t1.append(tree)
             t2.append(1)
         else:
             assert isinstance(tree, (Node, NegVariable))
             pair = (tree.variable, -1) if isinstance(tree, NegVariable) else tree.tree_val_if_binary_type(TypeNode.MUL)
             if pair is None:
                 break
             t1.append(pair[0])
             t2.append(pair[1])
     if len(t1) == len(terms):
         for tree in terms:
             if isinstance(tree, Node):
                 tree.mark_as_used()
         return t1, t2
     return terms, None
예제 #23
0
 def __eq__(self, other):
     other = self._simplify_with_auxiliary_variables(other)
     if isinstance(
             self.constraint,
         (ConstraintElement, ConstraintElementMatrix)) and isinstance(
             other, (int, Variable)):
         if isinstance(self.constraint, ConstraintElement):
             arg = self.constraint.arguments[TypeCtrArg.LIST]
             arg.content = flatten(
                 arg.content
             )  # we need to flatten now because it has not been done before
         return ECtr(self.constraint.set_value(
             other))  # only value must be replaced for these constraints
     return self.add_condition(TypeConditionOperator.EQ, other)
예제 #24
0
파일: entities.py 프로젝트: cprudhom/pycsp3
 def __init__(self, X, name, comment=None, tags=[]):
     super().__init__(name, comment, tags)
     self.name = name
     self.variables = X
     self.flatVars = flatten(X)
     assert len(self.flatVars) != 0, "Array of variable empty !"
     self.size = []
     curr = self.variables
     while isinstance(curr, list):
         self.size.append(len(curr))
         curr = curr[0]
     VarEntities.items.append(self)
     for x in self.flatVars:
         VarEntities.varToEVarArray[x] = self
     VarEntities.prefixToEVarArray[name] = self
     if options.debug:
         print("New VarArray entity: ", self)
예제 #25
0
def Var(term=None, *others, dom=None):
    assert (term is None) != (dom is None)
    if term is not None:
        dom = flatten(term, others)
    if not isinstance(dom, Domain):
        dom = Domain(dom)
    name = extract_declaration_for("Var")
    comment, tags = comment_and_tags_of(function_name="Var")

    assert isinstance(comment, (str, type(None))), "A comment must be a string (or None). Usually, they are given on plain lines preceding the declaration"
    assert name not in Variable.name2obj, "The identifier " + name + " is used twice. This is not possible"
    assert dom.get_type() in {TypeVar.INTEGER, TypeVar.SYMBOLIC}, "Currently, only integer and symbolic variables are supported. Problem with " + str(dom)
    assert str(name) not in Variable.name2obj, "The identifier " + name + " is used twice. This is not possible"

    var_object = VariableInteger(name, dom) if dom.get_type() == TypeVar.INTEGER else VariableSymbolic(name, dom)
    Variable.name2obj[name] = var_object
    EVar(var_object, comment, tags)  # object wrapping the variable x
    return var_object
예제 #26
0
 def __init__(self, X, name, comment=None, tags=[]):
     super().__init__(name, comment, tags)
     self.name = name
     self.variables = X
     self.flatVars = flatten(X)
     if len(self.flatVars) == 0:
         return
     # assert len(self.flatVars) != 0, "Array of variable empty !"
     self.containing_hole = None  # undefined until we ask  #flatVarsKeepingNone = flatten(X, keep_none=True)
     self.size = []
     curr = self.variables
     while isinstance(curr, list):
         self.size.append(len(curr))
         curr = curr[0]
     VarEntities.items.append(self)
     for x in self.flatVars:
         VarEntities.varToEVarArray[x] = self
     VarEntities.prefixToEVarArray[name] = self
예제 #27
0
파일: curser.py 프로젝트: cprudhom/pycsp3
 def _set_contains(self, other):  # for being able to use 'in' when expressing intension/extension constraints
     if not OpOverrider.activated:
         return self.__contains__(other)
     if isinstance(other, types.GeneratorType):
         other = list(other)
     tself = unique_type_in(self)
     # if isinstance(other, Variable) and len(self) > 0 and is_containing(self, int):  # unary table constraint
     if isinstance(other, Variable) and tself in {int, str}:  # unary table constraint
         queue_in.append((list(self), other))
         return True
     # if isinstance(other, (Variable, PartialConstraint)) or isinstance(other, (int, str)) and is_containing(self, Variable):  # intension constraint
     if isinstance(other, (Variable, PartialConstraint)) or isinstance(other, (int, str)) and tself and issubclass(tself, Variable):  # intension constraint
         queue_in.append((self, other))
         return True
     # if is_1d_tuple(other, Variable) or is_1d_list(other, Variable):  # non-unary table constraint
     #     queue_in.append((list(self), other))
     #     return True
     if is_containing(other, Variable):  # non-unary table constraint
         queue_in.append((list(self), flatten(other)))
         return True
     return self.__contains__(other)
예제 #28
0
def checkType(obj, allowed_types, message=""):
    if options.checker == "none":
        return True
    if options.checker == "fast" and isinstance(
            obj, (list, tuple, set, frozenset)) and len(obj) > 100:
        obj = obj[:1]  # TODO problem here: sets cannot be indexed
    allowed_types = (allowed_types, ) if not isinstance(
        allowed_types, tuple) else allowed_types
    for allowedType in allowed_types:
        if not isinstance(allowedType, list):
            if isinstance(obj, allowedType):
                return True
        elif isinstance(obj, (list, tuple, set)):
            for p in flatten(obj):
                if not any(isinstance(p, typ) for typ in allowedType):
                    break  # raise TypeMCSPError(inspector.getCalling(), p, allowedTypes, message, position)
            else:
                return True
    if message == "":
        message = "Wrong type for " + str(obj) + " (allowable types: " + str(
            allowed_types) + ")\n"
    raise TypeError(message)
예제 #29
0
def _compact_constraint_arguments(arguments):
    for arg in list(arguments.values()):
        if isinstance(arg.content, list) and len(arg.content) > 0 and arg.content_compressible:
            if not isinstance(arg.content[0], list):  # It is only one list
                arg.content = compact(arg.content, preserve_order=arg.content_ordered)
            elif arg.lifted is True:
                arg.content = [compact(l, preserve_order=arg.content_ordered) for l in arg.content]
        elif arg.name == TypeCtrArg.MATRIX:  # Special case for matrix
            sc = None if is_containing(arg.content_compressible, int) else _simple_compact(flatten(arg.content_compressible))
            arg.content = sc if sc is not None else arg.content
예제 #30
0
def _compact_constraint_group(group):
    preserve_order = False
    cnt = 0
    if isinstance(group.abstraction, dict):
        for key, value in group.abstraction.items():
            if "%" in str(value):
                cnt += 1
            argument = group.entities[0].constraint.arguments[key]
            if argument.content_compressible:
                if "%" not in str(value):
                    if key == TypeCtrArg.MATRIX:  # Special case for matrix
                        sc = None if is_containing(argument.content_compressible, int) else _simple_compact(flatten(argument.content_compressible))
                        group.abstraction[key] = sc if sc is not None else group.abstraction[key]
                    else:
                        group.abstraction[key] = compact(value, preserve_order=argument.content_ordered)
                elif argument.content_ordered is True:
                    preserve_order = True
    else:
        preserve_order = True
    if cnt > 1:
        preserve_order = True
    for i in range(len(group.all_args)):
        group.all_args[i] = compact(group.all_args[i], preserve_order=preserve_order, group_args=True)