7

質問


  • 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つの株価が存在するため、少なくとも1つの予測が保証され、最終結果は1E-9以内で正確である必要があります。

テストデータ

フォーマット:

  • 入力データ用の1行(list形式)
  • 期待される結果の1行
  • スペーサーとしての1つの空の行

ダウンロード元:

私の解決策


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にとって「ちょうどいい」ものであるために、私は1つの側面だけに焦点を当てています。コードの、私が持っている具体的な問題と私が満足していない何か。もっとお話ししましょう、私はまた、標準的な「あなたはすでに何を試しましたか」を共有します...

明らかに、リストの数を知っていれば、この演習のトップソルバーが競争で行ったように、ネストされたforループを入力するだけで済みます。未知のものに対してこれを行う関数を書いてみましたリストの数ですが、どのアプローチを取るべきかわかりませんでした。最初のアプローチは、再帰関数を作成することでした。リスト1から、要素1を取得し、リスト2の要素1、次にリスト3の要素1などと組み合わせます。各「レイヤー」の要素をスタックにプッシュし、目的の深さに達したらポップします。到達可能な深度が妥当であるため、「スタックオーバーフロー」を恐れることはないと思います。次に、再帰呼び出しにあまりにも多くのパラメーターを渡さずに、可能な限り最も効率的な(メモリ/スペース)方法でこれを行うためのデータ構造を選択するのに苦労しました。データ構造は呼び出しの外部に存在する必要がありますか?呼び出しで渡されますか?任意のレベルの並列処理を実現できますか?どのように?非常に多くの質問と非常に少ない回答で、私はこの問題を解決するためにもっと知る必要があり、正しい方向にナッジを使用できることに気づきました。コードスニペットを提供していただければ、調査します。または、この種の問題を処理する正しい「コンピュータサイエンス」の方法を説明してください。私が考えていないことがあると確信しています。

最後に、上記のソリューションで検討したこと、ありがたいことに、ジェネレーターをフィルターフィルター処理することで、完全なデカルト積がメモリに保持されないようにすることです(コード内でいつでもリスト(ALL_PERMUTATIONS_OF_WEIGHTS)を実行した場合のように)。加重和として実際に使用できる組み合わせに対してのみ、メモリ内のスペースを占有しています。itertoolsを使用せずにデカルト積を生成できるシステムに適用すると、同様の注意が役立ちます。

4

5 に答える 5

4

数字がどのように書かれているかを考えてみてください (10 進法またはその他のシステムで)。必要がない場合でも、ゼロを含めます。

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)
于 2012-09-30T02:03:31.697 に答える
3

まず、n リストのデカルト積を考えます。最初のリストを取りましょう。これを L と呼びます。次に、残りのリストを取り、これを R と呼びます。次に、L の各項目について、それをR のデカルト積。

これで、リストのないデカルト積を実装するだけで問題を解決できます。

私が言っていることを理解するのに役立つ場合は、Haskell の実装を次に示します。

cartesian :: [[a]] -> [[a]]
cartesian [] = [[]]
cartesian (xs:yss) = [x : ys | x <- xs, ys <- cartesian yss]
于 2012-09-30T02:05:14.683 に答える
1

古典的に、デカルト座標は(x,y)平面または(x,y,z)3D空間(実数の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

これを行う1つの方法は、再帰を使用し、常にタプルを返すように注意することです。

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)]
于 2012-09-30T02:25:59.827 に答える
1

これは、私が少し前に書いたPerl バージョンreduceから翻訳された、の観点からデカルト積を実装するお気に入りの (そして教育的にまともな方法だと思います) 方法です。

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

明示的な再帰の代わりに使用することを除いて、ヘイデンの答えに似ていreduceます。これにより、基本ケースがより明確になると思います。ここで削減しているのは、accumアイテムのリスト ( ) に対するタプルのリスト (累積された出力lst) です。アイテムのリスト内のすべてのアイテムについて、蓄積されたすべてのタプルの末尾に連結し、このプロセスをリスト ( X) の数だけ繰り返します。reduce イニシャライザ is は[()]、1 つの空のタプルを含むリストです。これにより、アキュムレータがX[0]最初のステップの後に確実に ( 1 つのタプルは各項目を1 回に入れたいため、1 つのタプルは何も連結しないようにしたいため)[1, 2, 3][(1), (2), (3)]X[0] )。これは、icktoofayの回答へのコメントでsenderleが言及した「nullary product」に対応しています。

この関数定義を指定すると、次の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 つのタプルです。

于 2012-09-30T16:49:27.133 に答える
0

救助へのItertools。以下は、1 つずつ使用されるため、組み合わせが作成されます。

import itertools
combs=itertools.product(*lists)

例)コマンドライン 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')
于 2014-04-17T05:03:28.233 に答える