Hasilkan rangkaian leksikografis secara efisien dengan Python

Saya ingin membuat rangkaian angka leksikografis sedemikian rupa sehingga untuk setiap angka jumlah digitnya adalah konstanta tertentu. Hal ini agak mirip dengan 'masalah jumlah subset'. Misalnya jika saya ingin menghasilkan angka 4 digit dengan jumlah = 3 maka saya memiliki rangkaian seperti:

[3 0 0 0]

[2 1 0 0]

[2 0 1 0]

[2 0 0 1]

[1 2 0 0] ... dan seterusnya.

Saya berhasil melakukannya dengan Python dengan kode berikut:

import numpy as np

M = 4 # No. of digits
N = 3 # Target sum

a = np.zeros((1,M), int)
b = np.zeros((1,M), int)

a[0][0] = N
jj = 0

while a[jj][M-1] != N:
    ii = M-2
    while a[jj][ii] == 0:
          ii = ii-1
    kk = ii
    if kk > 0:
       b[0][0:kk-1] = a[jj][0:kk-1]
    b[0][kk] = a[jj][kk]-1
    b[0][kk+1] = N - sum(b[0][0:kk+1])
    b[0][kk+2:] = 0
    a = np.concatenate((a,b), axis=0)
    jj += 1

for ii in range(0,len(a)):
    print a[ii]

print len(a)

Menurut saya ini bukan cara yang efisien (karena saya seorang pemula Python). Ini berfungsi dengan baik untuk nilai M dan N yang kecil (‹10) tetapi sangat lambat setelah itu. Saya ingin menggunakannya untuk M ~ 100 dan N ~ 6. Bagaimana cara membuat kode saya lebih efisien atau adakah cara yang lebih baik untuk mengkodekannya?


person Aman_X    schedule 20.03.2019    source sumber
comment
Lihat komposisi bilangan bulat   -  person Joseph Wood    schedule 20.03.2019
comment
Tautan yang diberikan memiliki informasi yang sangat berguna, namun tidak membantu menyempurnakan algoritme.   -  person Aman_X    schedule 21.03.2019


Jawaban (4)


Algoritma yang sangat efektif diadaptasi dari buku Jorg Arndt "Matters Computational"
(Bab 7.2 Co-lexicographic order for compositions into exactly k parts)

n = 4
k = 3

x = [0] * n
x[0] = k

while True:
    print(x)
    v = x[-1]
    if (k==v ):
        break
    x[-1] = 0
    j = -2
    while (0==x[j]):
        j -= 1
    x[j] -= 1
    x[j+1] = 1 + v

[3, 0, 0, 0]
[2, 1, 0, 0]
[2, 0, 1, 0]
[2, 0, 0, 1]
[1, 2, 0, 0]
[1, 1, 1, 0]
[1, 1, 0, 1]
[1, 0, 2, 0]
[1, 0, 1, 1]
[1, 0, 0, 2]
[0, 3, 0, 0]
[0, 2, 1, 0]
[0, 2, 0, 1]
[0, 1, 2, 0]
[0, 1, 1, 1]
[0, 1, 0, 2]
[0, 0, 3, 0]
[0, 0, 2, 1]
[0, 0, 1, 2]
[0, 0, 0, 3]

Jumlah komposisi dan waktu dalam hitungan detik untuk Python biasa (mungkin array numpy lebih cepat) untuk n=100, dan k = 2,3,4,5 (2,8 ghz Cel-1840)

2  5050 0.040000200271606445
3  171700 0.9900014400482178
4  4421275 20.02204465866089
5  91962520 372.03577995300293
I expect time  2 hours for 100/6 generation

Sama dengan array numpy (x = np.zeros((n,), dtype=int)) memberikan hasil yang lebih buruk - tetapi mungkin karena saya tidak tahu cara menggunakannya dengan benar

2  5050 0.07999992370605469
3  171700 2.390003204345703
4  4421275 54.74532389640808

Kode asli (ini Delphi, kompiler C/C++ mungkin mengoptimalkan lebih baik) menghasilkan 100/6 dalam 21 detik

3  171700  0.012
4  4421275  0.125
5  91962520  1.544
6  1609344100 20.748

Tidak bisa tidur sampai semua pengukuran belum selesai :)

MSVS VC++: 18 detik! (optimasi O2)

5  91962520 1.466
6  1609344100 18.283

Jadi 100 juta varian per detik. Banyak waktu yang terbuang untuk memeriksa sel yang kosong (karena rasio pengisiannya kecil). Kecepatan yang dijelaskan oleh Arndt dicapai pada rasio k/n yang lebih tinggi dan sekitar 300-500 juta varian per detik:

n=25, k=15 25140840660 60.981  400 millions per second
person MBo    schedule 21.03.2019
comment
Jalankan kode Anda untuk n=100 dan k=6, ini juga membutuhkan waktu dalam algoritma ini, mungkin berhari-hari - person Shoyeb Sheikh; 21.03.2019
comment
@MBo Menurut saya algoritma ini sangat cepat dan efisien. Sempurna! - person Aman_X; 21.03.2019
comment
@Shoyeb Sheikh Tahukah Anda berapa banyak komposisi dengan parameter seperti itu yang ada? Algoritme apa pun yang menghasilkan triliunan varian memerlukan banyak waktu. Pendekatan yang dijelaskan sangat optimal - diterapkan di C, menghasilkan 300 juta varian per detik (tidak termasuk keluaran atau penulisan - bagian yang paling memakan waktu) - person MBo; 21.03.2019
comment
@MBo, itu bagian dari pertanyaan haha ​​Saya tidak menambahkan apa pun ke dalamnya, 300 juta per detik sangat efisien, Hebat! - person Shoyeb Sheikh; 21.03.2019
comment
@Shoyeb Sheikh Ya, saya perhatikan bahwa penulis seharusnya mendapatkan banyak varian - dimungkinkan untuk menghitungnya, tetapi agak sulit untuk menyimpan dan menggunakan data dalam jumlah besar - person MBo; 21.03.2019
comment
@MBo Itu perbandingan yang sangat menarik. Saya mencobanya juga untuk n=100 dan k=5, dan butuh waktu sekitar 97 detik. BTW, tahukah Anda kalau perhitungannya bisa diparalelkan? - person Aman_X; 22.03.2019
comment
Tidak, algoritma ini tidak mengasumsikan paralelisasi. Bagaimana Anda akan menggunakan data hasil? - person MBo; 22.03.2019
comment
Saya menggunakannya untuk menghasilkan dasar untuk mewakili fungsi gelombang dan perhitungan lebih lanjut. - person Aman_X; 22.03.2019
comment
Tapi... 1,6 triliun varian? - person MBo; 22.03.2019
comment
Ya...itu akan menjadi kasus ekstrim, biasanya n dibatasi hingga 64. Ada cara untuk mengurangi jumlah varian lebih lanjut dengan menggunakan simetri sistem, dll. - person Aman_X; 22.03.2019

Rekomendasi saya:

  1. Tulis ulang sebagai generator menggunakan yield, bukan loop yang menggabungkan variabel global pada setiap iterasi.
  2. Pertahankan jumlah yang berjalan alih-alih menghitung jumlah beberapa subset representasi array dari angka tersebut.
  3. Operasikan satu contoh representasi nomor kerja Anda alih-alih menggabungkan salinannya ke variabel sementara pada setiap iterasi.

Perhatikan bahwa tidak ada urutan tertentu yang tersirat.

person pkfm    schedule 21.03.2019

Saya punya solusi yang lebih baik menggunakan itertools sebagai berikut,

from itertools import product
n = 4 #number of elements
s = 3 #sum of elements
r = []
for x in range(n):
    r.append(x)
result = [p for p in product(r, repeat=n) if sum(p) == s]
print(len(result))
print(result)

Saya mengatakan ini lebih baik karena sistem saya membutuhkan waktu 0,1 detik, sedangkan kode Anda dengan numpy membutuhkan waktu 0,2 detik.

masukkan deskripsi gambar di sini

masukkan deskripsi tautan di sini

Tetapi sejauh n=100 dan s=6, kode ini membutuhkan waktu untuk melewati semua kombinasi, menurut saya perlu waktu berhari-hari untuk menghitung hasilnya.

person Shoyeb Sheikh    schedule 21.03.2019
comment
Sebenarnya saya ingin meningkatkan cara pengkodean algoritma (atau mengkodekan algoritma yang lebih efisien) untuk menghitung hanya kombinasi yang diperlukan. Sejauh yang saya pahami, kode Anda memeriksa semua kemungkinan kombinasi terhadap jumlah yang diberikan. Bisakah Anda mengatur waktu kedua kode untuk M=10, N=3 juga? - person Aman_X; 21.03.2019
comment
Sudah satu jam untuk M=10 dan N=3 dan masih berjalan, saya pikir MBo punya jawaban yang lebih baik di sana. - person Shoyeb Sheikh; 21.03.2019
comment
Tidak, Anda memeriksanya dan beri tahu kami - person Shoyeb Sheikh; 21.03.2019
comment
Oke, saya melakukan itu. Butuh waktu kurang dari satu detik. - person Aman_X; 22.03.2019

Saya juga menemukan solusi menggunakan itertools (Sumber: https://bugs.python.org/msg144273 ). Kode berikut:

import itertools
import operator

def combinations_with_replacement(iterable, r):
    # combinations_with_replacement('ABC', 2) --> AA AB AC BB BC CC
    pool = tuple(iterable)
    n = len(pool)
    if not n and r:
        return
    indices = [0] * r
    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != n - 1:
                break
        else:
            return
        indices[i:] = [indices[i] + 1] * (r - i)
        yield tuple(pool[i] for i in indices)

int_part = lambda n, k: (tuple(map(c.count, range(k))) for c in combinations_with_replacement(range(k), n))
for item in int_part(3,4): print(item)
person Aman_X    schedule 18.11.2019