Эффективное создание лексикографических рядов в Python

Я хочу создать лексикографический ряд чисел, чтобы для каждого числа сумма цифр была заданной константой. Это несколько похоже на «проблему суммы подмножества». Например, если я хочу сгенерировать 4-значные числа с суммой = 3, у меня есть ряд вроде:

[3 0 0 0]

[2 1 0 0]

[2 0 1 0]

[2 0 0 1]

[1 2 0 0] ... и так далее.

Мне удалось успешно сделать это на Python с помощью следующего кода:

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)

Я не думаю, что это очень эффективный способ (поскольку я новичок в Python). Он отлично работает для малых значений M и N (<10), но очень медленно при более высоких значениях. Я хочу использовать его для M ~ 100 и N ~ 6. Как я могу сделать свой код более эффективным или есть лучший способ его кодирования?


person Aman_X    schedule 20.03.2019    source источник
comment
Взгляните на целочисленную композицию   -  person Joseph Wood    schedule 20.03.2019
comment
Данная ссылка содержит очень полезную информацию, однако не помогает в уточнении алгоритма.   -  person Aman_X    schedule 21.03.2019


Ответы (4)


Очень эффективный алгоритм, адаптированный из книги Йорга Арндта "Matters Computational"
(Глава 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]

Количество композиций и время в секундах для простого Python (возможно, массивы numpy быстрее) для n = 100 и k = 2,3,4,5 (2,8 ГГц, 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

То же самое с массивами numpy (x = np.zeros((n,), dtype=int)) дает худшие результаты, но, возможно, потому, что я не знаю, как их правильно использовать

2  5050 0.07999992370605469
3  171700 2.390003204345703
4  4421275 54.74532389640808

Собственный код (это Delphi, компиляторы C / C ++ могут оптимизировать лучше) генерирует 100/6 за 21 секунду

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

Не могу заснуть, пока не будут сделаны все измерения :)

MSVS VC ++: 18 секунд! (Оптимизация O2)

5  91962520 1.466
6  1609344100 18.283

Итак, 100 миллионов вариантов в секунду. Много времени тратится на проверку пустых ячеек (из-за небольшой степени заполнения). Скорость, описанная Арндтом, достигается при более высоких отношениях k / n и составляет порядка 300-500 миллионов вариантов в секунду:

n=25, k=15 25140840660 60.981  400 millions per second
person MBo    schedule 21.03.2019
comment
Выполните свой код для n = 100 и k = 6, это также требует времени в этом алгоритме, может быть дней - person Shoyeb Sheikh; 21.03.2019
comment
@MBo Я считаю этот алгоритм очень быстрым и эффективным. Идеально! - person Aman_X; 21.03.2019
comment
@Shoyeb Sheikh А вы знаете, сколько существует композиций с такими параметрами? Любой алгоритм, генерирующий триллионы вариантов, требует много времени. Описанный подход очень оптимизирован - будучи реализованным на C, он генерирует 300 миллионов вариантов в секунду (не считая вывода или записи - наиболее трудоемкая часть) - person MBo; 21.03.2019
comment
@MBo хорошо, что это было частью вопроса lol, я ничего не добавил к этому, 300 миллионов в секунду это очень эффективно, отлично! - person Shoyeb Sheikh; 21.03.2019
comment
@Shoyeb Sheikh Да, я заметил, что автор предполагает получить множество вариантов - их можно вычислить, но хранить и использовать такой огромный объем данных достаточно сложно. - person MBo; 21.03.2019
comment
@MBo Это действительно интересное сравнение. Я тоже пробовал это для n = 100 и k = 5, и это заняло примерно 97 секунд. Кстати, знаете ли вы, можно ли распараллелить вычисления? - person Aman_X; 22.03.2019
comment
Нет, этот алгоритм не предполагает распараллеливания. Как вы собираетесь использовать данные результатов? - person MBo; 22.03.2019
comment
Я использую его для создания основы для представления волновой функции и дальнейших расчетов. - person Aman_X; 22.03.2019
comment
Но ... 1,6 триллиона вариантов? - person MBo; 22.03.2019
comment
Что ж ... это будет крайний случай, обычно n ограничено 64. Есть способы дополнительно уменьшить количество вариантов, используя симметрию системы и т. Д. - person Aman_X; 22.03.2019

Мои рекомендации:

  1. Перепишите его как генератор, использующий yield, а не цикл, который объединяет глобальную переменную на каждой итерации.
  2. Сохраняйте текущую сумму вместо вычисления суммы некоторого подмножества представления массива в виде числа.
  3. Работайте с одним экземпляром вашего представления рабочего числа вместо того, чтобы прикреплять его копию к временной переменной на каждой итерации.

Обратите внимание, что какой-либо конкретный порядок не подразумевается.

person pkfm    schedule 21.03.2019

У меня есть лучшее решение, использующее itertools следующим образом:

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)

Я говорю, что это лучше, потому что в моей системе это заняло 0,1 секунды, в то время как ваш код с numpy занял 0,2 секунды.

введите описание изображения здесь

введите описание ссылки здесь

Но поскольку n = 100 и s = 6, этому коду требуется время, чтобы пройти все комбинации, я думаю, что на вычисление результатов уйдут дни.

person Shoyeb Sheikh    schedule 21.03.2019
comment
На самом деле я хочу улучшить способ кодирования алгоритма (или создать более эффективный алгоритм) для вычисления только необходимых комбинаций. Насколько я понимаю, ваш код проверяет все возможные комбинации на заданную сумму. Можете ли вы рассчитать оба кода для M = 10, N = 3? - person Aman_X; 21.03.2019
comment
Это был час для M = 10 и N = 3, и он все еще работает, я думаю, что у MBo есть лучший ответ. - person Shoyeb Sheikh; 21.03.2019
comment
Нет, вы проверите это и дайте нам знать - person Shoyeb Sheikh; 21.03.2019
comment
Хорошо, я сделал это. На это ушло меньше секунды. - person Aman_X; 22.03.2019

Я также нашел решение, используя itertools (Источник: https://bugs.python.org/msg144273 ). Код следующий:

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