n个列表的Python笛卡尔积,编码时n未知

时间:2012-09-30 01:45:25

标签: python

问题


  • 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?

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

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

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

5 个答案:

答案 0 :(得分:4)

考虑如何编写数字(在十进制系统中,或在任何其他系统中)。即使您不需要,也包括零:

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)

答案 1 :(得分:3)

首先,考虑一个n-list笛卡尔积。让我们取第一个列表,我们将其称为L.然后我们将采用剩余的列表,我们将其称为R.然后,对于L中的每个项目,前置这是由R的笛卡尔积产生的每个元组的开头。

有了它,你可以通过实现无列表的笛卡尔积来解决问题。

这是一个Haskell实现,以防它帮助您理解我所说的内容:

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

答案 2 :(得分:1)

这是我最喜欢的(以及教学上体面的,我希望的)实现笛卡尔积的方法reduce,翻译自我前段时间写的Perl version

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

它类似于hayden的答案,除了它使用reduce而不是显式递归,我认为这使基本情况更加清晰。 我们在这里减少的是一个元组列表(累计输出,accum)对项目列表(list)。对于项目列表中的每个项目,我们将其连接到所有累积元组的末尾,并为尽可能多的列表(X)重复此过程。 reduce初始值设定项为[()],这是一个包含一个空元组的列表,可确保X[0][1, 2, 3]时,累加器将在第一步后变为[(1), (2), (3)]一个元组,因为我们希望X[0] 中的每个项目,以及零元组,因为我们希望它连接到 nothing )。这对应于发送者在对icktoofay的回答的评论中提到的“nullary产品”。

鉴于此功能定义,如果您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个元组。

答案 3 :(得分:0)

经典地,笛卡尔坐标在平面中为(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 :(得分:0)

Itertools救援。以下将逐个创建组合:

import itertools
combs=itertools.product(*lists)

电子。 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')