在动态订单中尝试所有可能的组合

时间:2016-07-12 08:34:05

标签: python combinations

基本上我正在寻找itertools.product的实现,它允许我更改生成组合的顺序。

示例:如果我使用itertools.product('AB', 'xy'),则会按照以下确切顺序生成组合:

[('A', 'x'), ('A', 'y'), ('B', 'x'), ('B', 'y')]

我需要一个响应请求的实现,例如"请将A更改为B next",例如:

>>> generator = DynamicOrderProduct({'var1': 'AB', 'var2': 'xy'})
>>> generator.first()
{'var1': 'A', 'var2': 'x'}
>>> generator.change('var1')
{'var1': 'B', 'var2': 'x'}
>>> generator.change('var2')
{'var1': 'B', 'var2':, 'y'}
>>> generator.change('var2') # here it can't generate a new combination by
                             # changing var2, so it changes var1 instead
{'var1': 'A', 'var2': 'y'}
>>> generator.change('var2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

理想情况下,生成器会接受这样的变量列表:

generator.change(['var1', 'var2'])

然后应该尝试更改var1的值,如果不可能,请改为更改var2的值,依此类推。

我将如何实施此功能?标准库中有什么东西可以帮助我吗?

1 个答案:

答案 0 :(得分:0)

好吧,我已经成功编写了一个可以实现我想要的迭代器。这是我写过的最丑陋的代码,但它完成了工作。

我仍然希望有一个更好的解决方案 - 这个实现保留了一组所有返回的组合,这些组合可以增长到使用相当多的内存。

class DynamicOrderProduct:
    """
    Given a dict of {variable: [value1,value2,value3,...]}, allows iterating
      over the cartesian product of all variable values.
    Each step in the iteration returns a mapping of {variable: value}.
    To start the iteration, retrieve the first mapping by calling .first().
    To retrieve subsequent mappings, call
      .next(order_in_which_to_change_variable_values). This function's
      parameter should be a list of variables sorted by which variable's value
      should change next. If possible, the first variable in the list will
      change value. If not, the 2nd variable in the list will change value
      instead, and so on. Raises StopIteration if all combinations are
      exhausted.

    Example:

        possible_values = {'B': [0,1], # B can take the value 0 or the value 1
                           'L': [1,2,3]}
        iterator = DynamicOrderProduct(possible_values)
        print(iterator.first())

        import random
        variables = list(possible_values.keys())
        while True:
            order = random.sample(variables, len(variables))
            print('advancing variables in this order:', order)
            try:
                print(iterator.next(order))
            except StopIteration:
                break

    You may also pass an incomplete list of variables to the .next function.
    If no new combination of the given variables is possible, StopIteration is
      raised. For example:

        iterator = DynamicOrderProduct({var1: [1],
                                        var2: [1,2]})
        iterator.first() # this returns {var1: 1, var2: 1}
        iterator.next([var1]) # raises StopIteration

    Also, you may pass multiple lists to .next in order to change the value of
      multiple variables. StopIteration will be raised only if no variable can
      change value.

        iterator = DynamicOrderProduct({var1: [1,2],
                                        var2: [1,2]})
        iterator.first() # this returns {var1: 1, var2: 1}
        iterator.next([var1], [var2]) # returns {var1: 2, var2: 2}
    """
    def __init__(self, possible_variable_values):
        self.possible_variable_values = {k:tuple(v) for k,v in \
                                        possible_variable_values.items()}
        self.variable_order = list(possible_variable_values)
        self.exhausted_combinations = set()

    def first(self):
        self.mapping = {var:vals[0] for var,vals in \
                       self.possible_variable_values.items()}
        t = tuple(self.mapping[var] for var in self.variable_order)
        self.exhausted_combinations.add(t)
        return self.mapping

    def next(self, *orders):
        def advance(order, index, maxindex=2147483648):
            while True: # loop to reduce recursion
                try:
                    variable = order[index]
                except IndexError:
                    raise StopIteration

                value = self.mapping[variable]
                valindex = self.possible_variable_values[variable].index(value)
                start_index = valindex
                while True: # change the value until we find a new combination
                    valindex += 1
                    try:
                        possible_values = self.possible_variable_values
                        value = possible_values[variable][valindex]
                    except IndexError:
                        valindex = 0
                        value = self.possible_variable_values[variable][0]
                    self.mapping[variable] = value

                    # if we've tried all values but none of them
                    # worked, try to change the next variable's
                    # value instead
                    if valindex == start_index:
                        if index+1 >= maxindex:
                            raise StopIteration
                        # instead of recursing, update our own parameters and
                        # start a new iteration
                        index += 1
                        break

                    t = tuple(self.mapping[var] for var in self.variable_order)
                    # if this combination isn't new, try
                    # changing the previous variables' values
                    if t in self.exhausted_combinations:
                        if index == 0:
                            continue
                        try:
                            return advance(order, 0, index)
                        except StopIteration:
                            continue
                    return t

        total_order = []
        fail = True
        for order in orders:
            # each iteration may also change the previous
            # iterations' variables
            total_order = order + total_order
            try:
                t = advance(total_order, 0)
            except StopIteration:
                fail = True
            else:
                fail = False
        if fail:
            raise StopIteration

        self.exhausted_combinations.add(t)
        return self.mapping