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

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

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

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

Python

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

Q&A

解決済

2回答

4507閲覧

python 同じ値の要素を複数含む配列の並び替えパターン抽出

iMasa

総合スコア22

Python 3.x

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

Python

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

0グッド

1クリップ

投稿2019/02/03 13:32

編集2019/02/03 15:09

困っていること

下記のようなリストがあり、全ての並び替えパターンを抽出したいです。
ソースコードに記載している今の方法では要素が増える度に処理が遅くなり困っています。

元データ
配列には数値型のみが格納されます。
・最大40要素程度を想定しています
・例えば 1070 のような4桁の数値も入り得ます

# シンプルな例 data = [0,0,0,0,0,0,1,1] # 最も要素が多い場合の例 data = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,700,1010,1010]

期待する結果

(1, 0, 0, 0, 1, 0, 0, 0) (0, 0, 0, 0, 1, 0, 1, 0) (0, 0, 0, 0, 0, 0, 1, 1) (0, 0, 0, 0, 0, 1, 1, 0) (1, 0, 0, 0, 0, 0, 1, 0) (0, 1, 0, 0, 0, 1, 0, 0) (0, 0, 0, 0, 1, 0, 0, 1) (0, 0, 1, 0, 0, 1, 0, 0) (0, 1, 0, 0, 1, 0, 0, 0) (0, 0, 0, 0, 0, 1, 0, 1) (1, 0, 0, 0, 0, 0, 0, 1) (0, 0, 0, 1, 0, 1, 0, 0) (0, 0, 1, 0, 1, 0, 0, 0) (1, 0, 0, 1, 0, 0, 0, 0) (0, 0, 0, 1, 1, 0, 0, 0) (1, 0, 1, 0, 0, 0, 0, 0) (0, 1, 0, 0, 0, 0, 1, 0) (1, 1, 0, 0, 0, 0, 0, 0) (0, 0, 1, 0, 0, 0, 1, 0) (0, 0, 0, 1, 0, 0, 1, 0) (0, 1, 0, 0, 0, 0, 0, 1) (0, 1, 0, 1, 0, 0, 0, 0) (0, 0, 1, 0, 0, 0, 0, 1) (0, 0, 0, 0, 1, 1, 0, 0) (0, 0, 1, 1, 0, 0, 0, 0) (0, 1, 1, 0, 0, 0, 0, 0) (0, 0, 0, 1, 0, 0, 0, 1) (1, 0, 0, 0, 0, 1, 0, 0)

今の実装方法

下記の流れで処理していますが、効率が悪い気がしています。
解決策が見つけられず困っています。
他に良い方法(特に速度面で)があればご教授頂きたいです。

1.まずは配列の順列を取得する
2.取得した順列に対してSETを取り、重複を削除する

import itertools as ir data = [0,0,0,0,0,0,1,1] data_len = len(data) print(data_len) # 8要素 permutations = list(ir.permutations(data)) print(len(permutations)) # 40320通り permutations_set = list(set(permutations)) print(len(permutations)) # 28通り for i in permutations_set: print(i)

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

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

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

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

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

tiitoi

2019/02/03 13:48

問題の入力は質問のコードのようにビット列 (要素が0または1) 限定でしょうか?
iMasa

2019/02/03 14:01

ご質問ありがとうございます。 数値限定ですが 0 1 だけではありません。 最大4桁の数字が入り得ます。 要素の最大数は40程度を想定しています。
hayataka2049

2019/02/03 14:33

40!は無茶では? 重複があるとしてどれくらいに収まる見込みなのでしょうか
hayataka2049

2019/02/03 15:06 編集

ちなみに、サンプルとして40要素程度の適当な配列を示していただけるととても回答しやすいです。
iMasa

2019/02/03 15:07

やはりそもそも無茶なんですね。。。 仮に最大30要素程度として、その中でユニークな値は3種類程度です。 つまりほとんどが重複です。
hayataka2049

2019/02/03 15:14

示された例だと0がやたらに多いですが、だいたいこういう比率になりますか?
iMasa

2019/02/03 15:19

比率的にはサンプル通りですが、今のやり方では 0 とは限りません。 例えば 0 ではなく 120 とかになったりはします。
hayataka2049

2019/02/03 15:38

とりあえず、どれかしらの要素に偏るということですね。
iMasa

2019/02/03 15:49

その通りです。 質問そのものが上手くできず申し訳ありません。
wwbQzhMkhhgEmhU

2019/02/03 15:56

元の数値をsort | uniq -cしたもの入力にして、元の個数分順列に並べてけばいいのでは? ただし、各数の個数に達したら選べない的なルールで。 Pythonだとどっちが早いか分からないけど。。。
guest

回答2

0

ベストアンサー

こんな感じかなぁ、正しさの保証(実装がバグってないことの保証。考え方はこれでいいはず)はありませんし効率も微妙(もっとうまい実装の仕方があるはず)ですが、参考にしてください。

アイデアとしては、たとえばユニークな要素が3つなら1ノードごとに3つに分岐する木をつくり根から葉の方に伸ばしていく、途中で各要素の個数の制約を満たさなくなったらその下はやる必要なし、という単純なものです。つまり、最大葉数ユニークな要素数^長さの木を探索しながら、各要素の出現回数の制約を満たすものだけ残すという条件で枝刈りする訳です。

python

1import time 2from itertools import permutations 3from collections import Counter 4 5def f1(data): 6 p = list(permutations(data)) 7 return list(set(p)) 8 9def f2(data): 10 n = len(data) 11 result = [] 12 cnt = Counter(data) 13 keys = cnt.keys() 14 15 def isinvalid(x): 16 cnt2 = Counter(x) 17 for k, v in cnt.items(): 18 if cnt2.get(k, 0) > v: 19 return True 20 else: 21 return False 22 23 def r(x, i=0): 24 if isinvalid(x): 25 return 26 if i == n: 27 result.append(x) 28 else: 29 for k in keys: 30 r([*x, k], i=i+1) 31 r([]) 32 return result 33 34data = [0,0,0,0,0,0,1,1] 35print(set(f1(data)) == set(map(tuple, f2(data)))) 36 37data = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,700,1010,1010] 38t1 = time.time() 39res = f2(data) 40t2 = time.time() 41print(len(res), t2-t1) 42""" => 43True 449828 1.785611867904663 45"""

投稿2019/02/03 18:25

編集2019/02/03 21:47
hayataka2049

総合スコア30933

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

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

wwbQzhMkhhgEmhU

2019/02/03 20:50

あんな適当なコメントから回答までして頂きありがとうございます。 スッキリしたいい回答だと思います。 >>> d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,700,1010,1010] >>> Counter(d) Counter({0: 25, 1010: 2, 700: 1}) >>> factorial(28)/factorial(25)/factorial(2)/factorial(1) 9828.0 >>> パターン数は理論どおりで合っていることだけは確認しました。 元のコードがメモリに乗らなくなる頃からは圧倒的な速度になると思います。
hayataka2049

2019/02/03 22:49

コメントありがとうございます。実はパターン数の理論値の出し方が浮かばないまま回答していたので、助かります。それが一致すれば大丈夫そうですね。 いかに列挙しないで済ますかというと、枝狩りするしかないでしょうね。アルゴリズムはこれで行くとして、あとはデータ構造で工夫すれば軽く2桁くらいは速度の改善余地がありそうです。
iMasa

2019/02/04 20:59

いつもありがとうございます。 関数内の関数や再帰的な呼び出し等は使ったことがなく、それを読み解くだけで物凄い時間をかけましたが何とか理解できそうです。 もっと勉強します。
guest

0

hayataka2049さんの回答を参考に改良してみました。
現在の残り個数cntを直接加減算操作することで一桁程度速くなりました。

Python

1 2import time 3from itertools import permutations 4from collections import Counter 5 6def f2(data): 7 n = len(data) 8 result = [] 9 cnt = Counter(data) 10 keys = cnt.keys() 11 12 def isinvalid(x): 13 cnt2 = Counter(x) 14 for k, v in cnt.items(): 15 if cnt2.get(k, 0) > v: 16 return True 17 else: 18 return False 19 20 def r(x, i=0): 21 if isinvalid(x): 22 return 23 if i == n: 24 result.append(x) 25 else: 26 for k in keys: 27 r([*x, k], i=i+1) 28 r([]) 29 return result 30 31def f3(data): 32 n = len(data) 33 result = [] 34 cnt = Counter(data) 35 keys = cnt.keys() 36 37 def r(x,i=0): 38 if i == n: 39 result.append(x) 40 else: 41 for k in keys: 42 if cnt[k] >= 1: # 残あり 43 cnt[k] -= 1 44 r([*x, k], i=i+1) 45 cnt[k] += 1 46 r([]) 47 return result 48 49 50data = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,700,1010,1010] 51 52t1 = time.time() 53res = f2(data) 54t2 = time.time() 55print(len(res), t2-t1)# 9828 0.8919732570648193 56 57t1 = time.time() 58res = f3(data) 59t2 = time.time() 60print(len(res), t2-t1)# 9828 0.08100700378417969

投稿2019/02/04 02:18

can110

総合スコア38266

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

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

hayataka2049

2019/02/04 02:44

なるほど、深さ優先だからこれで良いわけですね
iMasa

2019/02/04 21:02

回答ありがとうございます。 改良版ということで迷ったのですが、最初に回答を出してくださったhayataka2049さんをベストアンサーにさせて頂きました。 こちらの回答も非常に勉強になります。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問