【问题标题】:Python cartesian product of n lists with n unknown at coding time编码时 n 个列表的 Python 笛卡尔积,其中 n 未知
【发布时间】:2012-09-30 01:45:24
【问题描述】:

问题


  • what is the best way to generate a cartesian product of some lists, not knowing in advance how many lists there are?

如果你喜欢,你可以停止阅读这里


背景

我没有钱上学,所以我想自学一些编程 在高速公路收费站上夜班时使用互联网。我有 决定尝试解决一些“编程挑战”问题作为练习。

编程作业

这是我要解决的问题,TopCoder 的属性:

http://community.topcoder.com/stat?c=problem_statement&pm=3496

我不会复制和粘贴完整的描述以尊重他们的版权声明 但我假设我可以总结它,前提是我不逐字使用它 (虽然是 IANAL)。

总结

如果历史股票价格的“加权总和”是获得的附录的总和 通过将这些价格的一个子集乘以相等数量的“权重” 因子,前提是后者加起来为 1.0 并从给定集合中选择 的有效值 [-1.0, -0.9, ..., 0.9, 1.0],请在所有 作为函数参数提供的历史数据,检查 5 个价格 一次,预测下一个价格并返回“加权排列” 因素”,产生最低的平均预测误差。至少会有 每次运行 6 个股票价格,因此至少可以保证一个预测,最终 结果应在 1E-9 范围内准确。

测试数据

格式:

  • 一行输入数据,list 格式
  • 预期结果一行
  • 一个空行作为分隔符

下载自:

我的解决方案


import itertools

# For a permutation of factors to be used in a weighted sum, it should be chosen
# such than the sum of all factors is 1.
WEIGHTED_SUM_TOTAL = 1.0
FACTORS_CAN_BE_USED_IN_WEIGHTED_SUM = lambda x: sum(x) == WEIGHTED_SUM_TOTAL

# Historical stock price data should be examined using a sliding window of width
# 5 when making predictions about the next price.
N_RECENT_PRICES = 5

# Valid values for weighting factors are: [-1.0, -0.9, ..., 0.9, 1.0]
VALID_WEIGHTS = [x / 10. for x in range(-10, 11)]

# A pre-calculated list of valid weightings to consider. This is the cartesiant
# product of the set of valid weigths considering only the combinations which
# are valid as components of a weighted sum.
CARTESIAN_PRODUCT_FACTORS = [VALID_WEIGHTS] * N_RECENT_PRICES
ALL_PERMUTATIONS_OF_WEIGHTS = itertools.product(*CARTESIAN_PRODUCT_FACTORS)
WEIGHTED_SUM_WEIGHTS = filter(FACTORS_CAN_BE_USED_IN_WEIGHTED_SUM,
                              ALL_PERMUTATIONS_OF_WEIGHTS)

# Generator function to get sliding windows of a given width from a data set
def sliding_windows(data, window_width):

  for i in range(len(data) - window_width):
    yield data[i:i + window_width], data[i + window_width]

def avg_error(data):

  # The supplied data will guarantee at least one iteration
  n_iterations = len(data) - 5

  best_average_error = None

  # Consider each valid weighting (e.g. permutation of weights)
  for weighting in WEIGHTED_SUM_WEIGHTS:

    # Keep track of the prediction errors for this weighting
    errors_for_this_weighting = []

    for historical_data, next_to_predict in sliding_windows(data,
                                                            N_RECENT_PRICES):

      prediction = sum([a * b for a, b in zip(weighting, historical_data)])
      errors_for_this_weighting.append(abs(next_to_predict - prediction))

    average_error = sum(errors_for_this_weighting) / n_iterations

    if average_error == 0: return average_error

    best_average_error = (average_error if not best_average_error else
      min(average_error, best_average_error))

  return best_average_error

def main():
  with open('data.txt') as input_file:
    while True:
        data = eval(input_file.readline())
        expected_result = eval(input_file.readline())
        spacer = input_file.readline()
        if not spacer:
          break
        result = avg_error(data)
        print expected_result, result, (expected_result - result) < 1e-9

if __name__ == '__main__':
    main()

我的问题

我不是要求对我的解决方案进行代码审查,因为这将是错误的 StackExchange 论坛。在这种情况下,我会将我的解决方案发布到“代码审查”。

相反,我的问题很小、准确且明确,符合本网站的格式(希望如此)。

在我的代码中,我使用 itertools 生成列表的笛卡尔积。本质上,我并没有自己解决问题的症结,而是将解决方案委托给为我做这件事的图书馆。如果我想从这些练习中学习,我认为这是错误的做法。我应该自己做最难的部分,否则为什么要锻炼呢?所以我想问你:


  • what is the best way to generate a cartesian product of some lists, not knowing in advance how many lists there are?

这就是我想知道的全部内容,如果您愿意,可以批评我的代码。这很受欢迎,即使它通过了所有测试(总是有更好的做事方式,特别是如果你是像我这样的初学者)但是对于这个问题对于 SO来说“恰到好处”,我只关注一个方面代码,我遇到的一个具体问题和我不满意的事情。让我告诉你更多,我也会分享规范的“你已经尝试过什么”......

很明显,如果我知道列表的数量,我可以输入一些嵌套的 for 循环,就像本练习的顶级求解器在比赛中所做的那样。我尝试编写一个函数来为 unknown 数量的列表执行此操作,但我不确定采用哪种方法。第一种方法是编写递归函数。从列表 1 中取出元素 1 并将其与列表 2 的元素 1 和列表 3 的元素 1 组合,等等。我会将每个“层”中的元素推入堆栈,并在达到所需深度时弹出它们。我想我不会担心“堆栈溢出”,因为可达到的深度是合理的。然后,我努力选择一种数据结构,以尽可能最有效(内存/空间)的方式执行此操作,而不向递归调用传递太多参数。数据结构是否应该存在于调用之外?在通话中被传递?我能达到任何水平的并行性吗?如何?有这么多的问题和这么少的答案,我意识到我需要知道更多来解决这个问题,我可以在正确的方向上轻推。你可以提供一个代码 sn-p 我会研究它。或者只是向我解释处理此类问题的正确“计算机科学”方式是什么。我确信有些事情我没有考虑。

最后,我在上面的解决方案中考虑的是,谢天谢地 filter 过滤了一个生成器,因此完整的笛卡尔积永远不会保存在内存中(就像我做了一个 list(ALL_PERMUTATIONS_OF_WEIGHTS ) 在代码中的任何时间) 所以我只为那些实际上可以用作加权和的组合占用内存空间。如果应用于任何允许我生成笛卡尔积的系统而不使用 itertools,类似的警告会很好。

【问题讨论】:

  • 你需要递归。查看​DFS。
  • 您所说的“最佳”最简单、最快是什么意思?

标签: python


【解决方案1】:

想想数字是如何写的(在十进制系统中,或在任何其他系统中)。包括零,即使您不需要它们:

00000
00001
00002
...
00009
00010
00011
00012
...
99998
99999

您可以看到这看起来像是 5 个列表 list(range(10)) 的笛卡尔积(在这种特殊情况下)。您可以通过递增“最低”数字非常轻松地生成此输出,当它到达列表中的最后一个时,将其设置为第一个元素并递增“下一个最高”数字。当然,您仍然需要for 循环,但数量非常少。当您处理任意数量的任意列表时,请使用类似的方法。

例如,如果您有 3 个列表:['a', 'b', 'c']['x', 'y']['1', '2'],您将获得:

ax1
ax2
ay1
ay2
bx1
bx2
by1
by2
cy1
cy2
cx1
cx2

祝你好运!

编辑:

如果您愿意,这里有一个示例代码来执行此操作。我不递归只是为了展示这有多简单。当然,递归也是一种很好的方法。

def lex_gen(bounds):
    elem = [0] * len(bounds)
    while True:
        yield elem
        i = 0
        while elem[i] == bounds[i] - 1:
            elem[i] = 0
            i += 1
            if i == len(bounds):
                raise StopIteration
        elem[i] += 1

def cart_product(lists):
    bounds = [len(lst) for lst in lists]
    for elem in lex_gen(bounds):
        yield [lists[i][elem[i]] for i in range(len(lists))]


for k in cart_product([['1', '2'], ['x', 'y'], ['a', 'b', 'c']]):
    print(k)

【讨论】:

    【解决方案2】:

    首先,考虑一个 n 列表笛卡尔积。让我们取第一个列表,我们将其称为 L。然后我们将取其余列表,我们将其称为 R。然后,对于 L 中的每个项目,将其添加到由生成的每个元组的开头R的笛卡尔积。

    这样,您只需实现无列表的笛卡尔积即可解决问题。

    这是一个 Haskell 实现,以防它帮助你理解我在说什么:

    cartesian :: [[a]] -> [[a]]
    cartesian [] = [[]]
    cartesian (xs:yss) = [x : ys | x <- xs, ys <- cartesian yss]
    

    【讨论】:

    • 单行版本,例如Try Haskell:let { cartesian [] = []; cartesian [xs] = [[x] | x &lt;- xs]; cartesian [xs, ys] = [[x, y] | x &lt;- xs, y &lt;- ys]; cartesian (xs:yss) = [x : ys | x &lt;- xs, ys &lt;- cartesian yss] } in cartesian $ replicate 2 ['0'..'9']
    • 实际上,nullary product 并不是您在此处定义的空列表,而是一个包含 空列表的列表。这一事实使这个递归定义变得更加简单。定义 cartesian [] = [[]] 允许您删除除最后一个一般情况之外的所有其他情况。
    • @senderle:谢谢。这确实使定义更简单。
    【解决方案3】:

    传统上,笛卡尔坐标是平面中的(x,y) 或3D 空间中的(x,y,z)(对于实数中的x、y 和z):

    [ (x,y) for x in reals for y in reals ]
    

    更一般地说,它们是元组(作为 Python 列表理解):

    [ (x1, x2, x3, ...) for x1 in X1 for x2 in X2 for x3 in X3 ...]
    

    对于对象(在我们的例子中是可迭代的)X1, X2, X3,...,我们想要的是一个函数:

    def cartesian_product(X1,X2,X3,...):
         return # the above list
    

    一种方法是使用递归,注意始终返回元组:

    def cartesian_product(*X):
        if len(X) == 1: #special case, only X1
            return [ (x0,) for x0 in X[0] ]
        else:
            return [ (x0,)+t1 for x0 in X[0] for t1 in cartesian_product(*X[1:]) ]
    
    cartesian_product([1,2],[3,4],[5,6])
    # [(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)]
    

    【讨论】:

      【解决方案4】:

      这是一种最喜欢(我希望在教学上也不错)以reduce 形式实现笛卡尔积的方法,翻译自我前段时间写的Perl version

      def cartesian_product(*X):
        return reduce(
          lambda accum, lst: 
            [ tup + (item,) for tup in accum for item in lst ],
          X,
          [()]
        )
      

      它类似于 hayden 的回答,只是它使用 reduce 而不是显式递归,我认为这使基本情况更加清晰。 我们在这里减少的是元组列表(累积输出,accum)与项目列表(lst)的对比。对于项目列表中的每个项目,我们将其连接到所有累积元组的末尾,并对尽可能多的列表 (X) 重复此过程。 reduce初始化器是[()],一个包含一个空元组的列表,它确保如果X[0][1, 2, 3],那么在第一步之后累加器将变为[(1), (2), (3)]一个元组,因为我们想要X[0] 中的每个项目一次,以及一个零元组,因为我们希望它被连接到 nothing)。这对应于 senderle 在对 icktoofay 的回答的评论中提到的“无效产品”。

      给定这个函数定义,如果你 print cartesian_product([1,2], [3,4], [5,6]) 它将打印:

      [(1, 3, 5), (1, 3, 6), (1, 4, 5), (1, 4, 6), (2, 3, 5), (2, 3, 6), (2, 4, 5), (2, 4, 6)]
      

      这是我们预期的 8 个元组。

      【讨论】:

        【解决方案5】:

        Itertools 来救援。以下将创建组合,因为它们被一一使用:

        import itertools
        combs=itertools.product(*lists)
        

        E. G。使用命令行 Python,并假设您有一个可变长度列表:

        >>> c=[['3', '5', '7'], ['100'], ['1', '2', '3']]
        >>> z=itertools.product(*c)
        >>> for ii in z:
        ...     print ii
        ... 
        ('3', '100', '1')
        ('3', '100', '2')
        ('3', '100', '3')
        ('5', '100', '1')
        ('5', '100', '2')
        ('5', '100', '3')
        ('7', '100', '1')
        ('7', '100', '2')
        ('7', '100', '3')
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-10-06
          • 2011-01-31
          • 2022-06-25
          • 1970-01-01
          • 2011-03-24
          • 1970-01-01
          相关资源
          最近更新 更多