[英]What's the most memory efficient way to generate the combinations of a set in python?
[英]Python - most efficient way to generate combinations of large sets subject to criteria?
我試圖在受邊界條件限制的投資組合內生成所有可能的金融工具組合。
例如,假設我有一組列表,這些列表代表投資組合的分配,受每個工具總投資組合規模的最小和最大百分比的影響:
"US Bonds" = {0.10,0.15,0.20,0.25,0.30}
"US Equities" = {0.25, 0.30, 0.35, 0.40, 0.45, 0.50}
"European Bonds" = {0.10, 0.15, 0.20}
"European Equities = {0.20,0.25,0.30,0.35,0.40,0.45,0.50}
...
"Cash" = {0.0, 0.05, 0.10, 0.15,...0.95}
我的列表,資產因此看起來像:
[In]
Asset
[Out]
[[0.1, 0.15, 0.2, 0.25, 0.30],
[0.25, 0.30,0.35, 0.40, 0.45, 0.50],
[0.1, 0.15, 0.2],
[0.20, 0.25, 0.30,0.35, 0.40, 0.45, 0.50]
...
[0.0, 0.05, 0.1, 0.15, 0.2, 0.25,...0.95]]
生成所有可能的投資組合的最有效方法是什么,其條件是每種工具組合的總和必須= 1?
現在,我正在創建一個列表'投資組合'如下:
portfolios = [item for item in itertools.product(*asset) if np.isclose(sum(item),1)]
(nb,'np.isclose'來處理時髦的fp算術)。
我已將資產類別和可能的分配表示為列表集合,但想知道是否存在更快的不同數據表示(例如,NumPY數組)。
關於各種組合的最佳執行存在一些問題,但我沒有看到任何邊界條件。
(注:代碼見: http : //lpaste.net/145213 )
首先,我將百分比表示為整數值,以避免浮點舍入錯誤。
其次,最有效的方法是使用邊界來避免查看不可能滿足== 1約束的投資組合。
您要編寫的循環將如下操作:
def portfolios():
for us_bonds in [ 10, 15, 20, 25, 30 ]:
if us_bonds > 100: break
for us_equaties in [ 25, 30, 35, 40, 45, 50 ]:
if us_bonds + us_equaties > 100: break
for euro_bonds in [ 10, 15, 20 ]:
if us_bonds + us_equaties + euro_bonds > 100: break
for euro_equaties in [ 20, 25, 30, 35, 40, 45, 50 ]:
if us_bonds + us_equaties + euro_bonds + euro_equaties > 100: break
cash = 100 - (us_bonds + us_equaties + euro_bonds + euro_equaties)
yield [us_bonds, us_equaties, euro_bonds, euro_equaties, cash]
這定義了一個可以在for
循環中使用的生成器,如下所示:
for x in portfolios(): print x
這種方法是有效的,因為它避免構建超過== 100約束的投資組合。
還要注意,我們已經利用了“現金”百分比基本上可以是任何事實的事實 - 因此它只占用了100%與其他投資類別總數之間的差異。
以下函數將此循環概括為任意數量的投資類別:
def gen_portfolio(categories):
n = len(categories)
tarr = [0] * (n+1)
parr = [0] * (n+1)
karr = [0] * (n+1)
marr = [ len(c) for c in categories ]
i = 0
while True:
while True:
if i < n:
p = categories[i][ karr[i] ]
t = tarr[i] + p
if t <= 100:
parr[i] = p
tarr[i+1] = t
i += 1
karr[i] = 0
continue
else:
break # backup
else:
parr[n] = 100 - tarr[n] # set the Cash percentage
yield parr[:] # yield a copy of the array parr
break
# backup
while True:
if i > 0:
i -= 1
karr[i] += 1
if karr[i] < marr[i]: break
else:
return # done!
def portfolios2():
cats = [ [ 10, 15, 20, 25, 30 ], [ 25, 30, 35, 40, 45, 50 ], [ 10, 15, 20 ], [ 20, 25, 30, 35, 40, 45, 50 ] ]
return gen_portfolio(cats)
這是一個測試,表明它們產生相同的結果:
def compareTest():
ports1 = [ x for x in portfolios() ]
ports2 = [ x for x in portfolios2() ]
print "ports1 length:", len(ports1)
print "ports2 length:", len(ports2)
for x in ports1:
if x not in ports2: print "not in ports2:", x
for x in ports2:
if x not in ports1: print "not in ports1:", x
更新
這是一個示例,演示了此方法與itertools.product之間的區別。
假設有10個投資類別,每個類別的百分比為[90,91,...,99]。 帶有break語句的嵌套循環將按如下方式進行:
start the loop: for p1 in [90,91,..,99]
set p1 = 90
p1 < 100 so continue
start the loop: for p2 in [90,91,..,99]
set p2 = 90
p1 + p2 > 100, so break out of the p2 loop
set p1 = 91
p1 < 100 so continue
start the loop: for p2 in [90,91,..,99]
set p2 = 90
p1 + p2 > 100, so break out of the p2 loop
set p1 = 92
...
所以帶有break語句的嵌套循環只查看10個案例 - p1 = 90,91,..,99和p2 = 90. p2永遠不會超過90,它永遠不會嘗試為p3,p4,...分配任何東西,p10。
另一方面,itertools.product將生成所有100個案例,然后您必須過濾掉總和> 100的組合。
對於某些輸入,itertools.product可能更快,因為它是用C語言編寫的,但它不會根據當前選擇的總和對案例進行任何修剪。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.