質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

2回答

1572閲覧

条件を満たす(n)個の要素を持つリストを全パターン生成したいです。

keiji_kc

総合スコア18

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

2クリップ

投稿2022/11/18 04:58

やりたいこと

  • pythonで 条件を満たす(n)個の要素を持つリストを全パターン生成したいです。

例えば、以下のような結果を取得したいです。

[[1, 1, 1, 1, 1, 1, 1, 2, 2, 2], [0, 1, 1, 1, 1, 1, 1, 2, 2, 2], [0, 0, 1, 1, 1, 1, 1, 2, 2, 2], .....]

生成するリストの条件

  • 要素数 n=10 (実際には数を50個以上に増やしたい)
  • 各要素は0,1,2からなります。
  • 0は3個以下しか含まれない。(nの数が大きくなると、この数字も増やします。例えば、int(n*0.1)個程度。)
  • 2も3個以下しか含まれない。(同上)

n=10の場合、3**10パターンから、0と2を一定数以上含むパターンのリストを削除するイメージです。

n=15程度なら、実現できる処理は以下のように書きました。

python

1from itertools import product 2 3pattern = [] 4n = 10 # 何列の結果を出すか(n=20くらいでメモリーエラーになる。) 5 6for _ in range(n): 7 pattern.append([0,1,2]) 8 9data = list(product(*pattern)) # この処理が重いです。 10 11# dataは、例えば、例: [[0, 0, 1, 1, 1, 1, 1, 2, 2, 2], [0, 1, 1, 1, 1, 1, 1, 2, 2, 2].....]のようなデータです。 12# dataから(ここでは例として)、"0が3個以上"、"2が3個以上"含むリストを、削除します。 13 14result = [] 15 16for row in data: 17 if row.count(0) >= 3 or row.count(2) >= 3: 18 continue 19 result.append(row) 20 21print(resutl)

この方法ですと、nが大きくなると計算しきれずメモリーエラーになってしまいます。n=10(310)ではすぐに計算できても、20では(320)で手元のPCではエラーを起こします。

ただ、例えば、n=10で計算した場合に、総当りで出したdataは59049個のなのに対して、必要なデータをピックアップしたresultは2181個と相当少なくなります。なので、総当たりせずに、resultを直接出せれば、nが大きくなってもある程度なら計算できるのでは?と考えています。

この処理の中で最も重いのは、

data = list(product(*pattern))

です。
ここで総当りしてしまっているのが無駄な作業で、0や2を含む数が規定より多い場合に、処理をしないようにスキップできればいいはずです。

仮説

例えば、

  • はじめに[1,1,1,1,1,1...]を作って、0をn個、2をm個、代入する、という発想もありなのかな?と考えはしたものの、コードに落とし込めません。。
  • ビット演算を使う??
  • numpyで処理すれば多少は早くなる?

ただ、正解にたどり着けません。。。

教えて頂けると幸いです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

勝たんしか深さ優先探索な問題ですね.len(cond)分木を探索するイメージで,次のように再帰を書けば余計な配列を生成せず全列挙できます.

Python

1n = 10 2 3cond = { 4 0: 3, # 3個未満 5 1: 1e9, # 10^9未満(実質無制限) 6 2: 3, # 3個未満 7} 8 9result = list() 10 11def dfs(x): 12 if len(x) == n: 13 result.append(x.copy()) 14 return 15 16 for k, v in cond.items(): 17 if x.count(k) < v - 1: 18 x.append(k) 19 dfs(x) 20 x.pop() 21 22dfs(list()) 23print(result) 24print(len(result))

n = 10のとき,同様に2181個観測され,n = 30で190591個,n = 50は1504401個の配列が生成されました.適宜,適用したい条件condを追加して実行することができます.

ちなみに生成する条件で「0,2は3個以下」と言っていましたが,3個未満でないと結果の個数が一致しません.どちらが望んだ結果なのか不明でしたので,とりあえず得られる配列の個数に合わせましたが,もし3個以下の条件が正しいなら,if x.count(k) < v - 1:if x.count(k) < v:に変更してください.

can110さんの回答との相違点は,「グローバル変数を使いまくっているので行儀が悪い」「ファイル書き込みではなくresultに答えを格納しまくるのでメモリ圧迫する」という点です.ファイル書き込みによるメモリ節約に関しては弊回答にも適用しやすいものかと思われます.

投稿2022/11/18 06:38

編集2022/11/18 07:33
PondVillege

総合スコア1579

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

keiji_kc

2022/11/18 07:18

素晴らしい、、、感動しました。 これが深さ優先探索というものでしょうか?大変興味を持ちました。。 お恥ずかしながら、初見で理解できないので、じっくり勉強したいと思います! 手始めにこのあたりを勉強してみようと思います。。 https://qiita.com/drken/items/4a7869c5e304883f539b
PondVillege

2022/11/18 07:21

そうですね,けんちょんさんの資料は大変読みやすいので良い選択だと思います. 深さ優先探索の中でも簡単なものなので,理解できれば非常に大きな一歩となるでしょう.
keiji_kc

2022/11/18 08:28

追記も頂いてありがとうございます。たしかに、一定の数を超えるとメモリエラー出ました。 ファイルに書き出せば解消できるかもしれませんね!ありがとうございます。
guest

0

max0, max2 = 0および2の最大個数
cnt_total = 合計個数
とすると
0の個数は0~max0
2の個数は0~max2
の範囲をとり、
1の数はcnt_total - cnt0 - cnt2
なので 0および2の個数をそれぞれ0~maxまで変えて
それぞれの個数での組み合わせを列挙していけばよいです。

以下ではとりあえず個数の組み合わせの列挙を再帰で行っていますが、30以上は厳しいと思います。
もっと効率の良い方法あるいはライブラリがあるかもしれません。

Python

1import itertools 2 3# 各値の組み合わせを列挙 4def count( f, counts, ret=[]): 5 6 # すべて使い切った 7 if not any(counts): 8 f.write(f'{ret}\n') 9 return 10 11 for i in range(len(counts)): 12 if counts[i] > 0: 13 counts_next = counts.copy() 14 counts_next[i] -= 1 # この数(i)を使った 15 count( f, counts_next, ret + [i]) 16 17cnt_total = 4 18max0, max2 = 1,1 19 20with open('ret.txt', 'w') as f: 21 for cnt0, cnt2 in itertools.product(range(max0+1), range(max2+1)): 22 cnt1 = cnt_total - cnt0 - cnt2 23 print(cnt0,cnt1,cnt2) # 0,1,2の個数 24 count( f, [cnt0,cnt1,cnt2]) 25 26""" 27cnt_total = 4 28max0, max2 = 1,1 29----- 30[1, 1, 1, 1] 31[1, 1, 1, 2] 32[1, 1, 2, 1] 33[1, 2, 1, 1] 34[2, 1, 1, 1] 35[0, 1, 1, 1] 36[1, 0, 1, 1] 37[1, 1, 0, 1] 38[1, 1, 1, 0] 39[0, 1, 1, 2] 40[0, 1, 2, 1] 41[0, 2, 1, 1] 42[1, 0, 1, 2] 43[1, 0, 2, 1] 44[1, 1, 0, 2] 45[1, 1, 2, 0] 46[1, 2, 0, 1] 47[1, 2, 1, 0] 48[2, 0, 1, 1] 49[2, 1, 0, 1] 50[2, 1, 1, 0] 51----- 5221行 53""" 54 55""" 56cnt_total = 30 57max0, max2 = 3,3 58----- 59[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 60[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] 6162[2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0] 63[2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0] 64----- 6515143571行 66"""

追記

C++で総数だけ数える雑なコード書いてみましたが、40程度でも数分かかって6901580871通りとはじき出されたので、50個ものパターンをじっさいに生成するのは現実的ではないと思います。

C++

1#include <stdio.h> 2#include <string.h> 3#include <stdlib.h> 4#include <vector> 5 6using namespace std; 7 8const int TOTAL = 40; 9const int MAX0 = 4; 10const int MAX2 = 4; 11 12long long nCount = 0; 13 14void count( int aCount[], int aRet[]) 15{ 16 if( aCount[0] <= 0 && aCount[1] <= 0 && aCount[2] <= 0){ 17 nCount++; 18 return; 19 } 20 21 int pos = TOTAL - (aCount[0] + aCount[1] + aCount[2]); 22 for( int i = 0; i < 3; i++){ 23 if( aCount[i] > 0){ 24 int aCount_next[3]; 25 memcpy( aCount_next, aCount, sizeof(aCount_next)); 26 aCount_next[i]--; 27 aRet[pos] = i; 28 count(aCount_next, aRet); 29 } 30 } 31} 32 33int main( int argc, char *argv[]) 34{ 35 int aRet[TOTAL]; 36 for( int n0 = 0; n0 <= MAX0; n0++){ 37 for( int n2 = 0; n2 <= MAX2; n2++){ 38 int n1 = TOTAL - n0 - n2; 39 int aCount[3] = {n0, n1, n2}; 40 printf("%d,%d,%d\n", aCount[0], aCount[1], aCount[2]); 41 count( aCount, aRet); 42 printf("%lld\n", nCount); 43 } 44 } 45 46 return 0; 47}

投稿2022/11/18 06:45

編集2022/11/18 09:22
can110

総合スコア38341

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

keiji_kc

2022/11/18 08:27

ありがとうございます。 ファイルに書き出すアイデア、なるほどと思いました! 大変参考になります!
can110

2022/11/18 09:23

ためしにC++で総数を数えるコード書いてみましたが、パターン数が膨大になるのでファイルに書き出すのも厳しいかと思います。
keiji_kc

2022/11/18 09:39

ご丁寧にありがとうございます! 私も教えて頂いて色々やってますが、一定の数を超えると、どうしても限界が来ることはわかりました。 とはいえ、元のコードよりかなり改善できそうでしたので、テストを分割するなど、少し工夫して実現しようと思います。本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問