непростые факторинги с некоторыми повторами

Допустим, у нас есть числовые множители, например 1260:

>>> factors(1260)
[2, 2, 3, 3, 5, 7]

Какой способ лучше всего использовать в комбинациях Python с каждым возможным подпродуктом из этих чисел, то есть со всеми факторингами, а не только с простыми факторами, с суммой факторов меньше, чем max_product?

Если я делаю комбинации из простых факторов, мне приходится рефакторить оставшуюся часть продукта, так как я не знаю оставшуюся часть не в комбинации.

Я также могу улучшить свою функцию divisors, чтобы она производила пары делителей вместо делителей в порядке их размера, но все равно это будет стоить мне сделать это для числа с произведением до 12000. Товар должен оставаться всегда одним и тем же.

Я был связан с подпрограммой divisor, но это не стоило усилий, чтобы доказать, что они адаптируются к моему другому коду. По крайней мере, моя функция делителя заметно быстрее, чем sympy:

def divides(number):
    if number<2:
        yield number
        return
    high = [number]
    sqr = int(number ** 0.5)
    limit = sqr+(sqr*sqr != number)
    yield 1
    for divisor in xrange(3, limit, 2) if (number & 1) else xrange(2, limit):
        if not number % divisor:
            yield divisor
            high.append(number//divisor)
    if sqr*sqr== number: yield sqr
    for divisor in reversed(high):
        yield divisor

Единственная проблема повторного использования этого кода состоит в том, чтобы связать делители с факторинговым ситом или сделать какой-то itertools.product делителей делителей в парах, которые я бы выдавал парами вместо сортировки по порядку.

Примеры результатов:

[4, 3, 3, 5, 7] (one replacement of two)
[5, 7, 36] (one replacement of three)
[3, 6, 14, 5] (two replacements)

Возможно, мне понадобится какой-то способ создать решения для сита или динамического программирования для меньших делителей, которые можно было бы связать с числами, делителями которых они являются. Хотя кажется, что избежать дублирования сложно. У меня есть одна готовая функция решета, которая хранит самый большой простой множитель для каждого числа для ускорения разложения без сохранения полных разложений каждого числа... возможно, это можно было бы адаптировать.

ОБНОВЛЕНИЕ. Сумма факторов должна быть близка к произведению, поэтому, вероятно, в ответе много факторов ‹= 10 (до 14 факторов).

ОБНОВЛЕНИЕ 2: Вот мой код, но я должен выяснить, как выполнить многократное удаление рекурсивно или итеративно для частей > 2 длиной и выкопать лексическое разбиение, чтобы заменить прыгающие битовые шаблоны, которые создают дубликаты (жалкое количество попаданий только для одной замены, и это не учитывает передачу «разделов одного элемента» внутри single_partition):

from __future__ import print_function
import itertools
import operator
from euler import factors

def subset(seq, mask):
    """ binary mask of len(seq) bits, return generator for the sequence """
    # this is not lexical order, replace with lexical order masked passing duplicates
    return (c for ind,c in enumerate(seq) if mask & (1<<ind))


def single_partition(seq, n = 0, func = lambda x: x):
    ''' map given function to one partition  '''
    for n in range(n, (2**len(seq))):
        result = tuple(subset(seq,n))
        others = tuple(subset(seq,~n))
        if len(result) < 2 or len(others) == 0:
            #empty subset or only one or all
            continue
        result = (func(result),)+others
        yield result


if __name__=='__main__':
    seen,  hits, count = set(), 0, 0
    for f in single_partition(factors(13824), func = lambda x: reduce(operator.mul, x)):
        if f not in seen:
            print(f,end=' ')
            seen.add(f)
        else:
            hits += 1
        count += 1
    print('\nGenerated %i, hits %i' %(count,hits))

УТОЧНЕНИЕ Я рад получить только факторинг с максимальным 5 факторами в части неосновного фактора. Я вручную обнаружил, что неубывающие схемы до 5 одинаковых множителей имеют следующую форму:

partitions of 5    applied to 2**5
1  1  1   1  1     2  2   2   2  2
1  1  1     2      2  2   2    4
1  1  1  3         2  2      8
1   2       2      2    4      4 
1       4          2      16
  2      3           4       8

РЕШЕНИЕ Я не удаляю принятый ответ из тонкого решения вниз, но он слишком сложен для работы. Из Project Euler я раскрываю только эту вспомогательную функцию из orbifold of NZ, она работает быстрее и не требует сначала простых множителей:

def factorings(n,k=2):
    result = []
    while k*k <= n:
        if n%k == 0:
            result.extend([[k]+f for f in factorings(n/k,k)])
        k += 1
    return result + [[n]]

Соответствующее решение проблемы 88 его запуска в Python 2.7 за 4,85 с моим декоратором времени и после оптимизации условия остановки с помощью найденного счетчика 3,4 с в 2.6.6 с помощью psyco , 3,7 с в 2,7 без псико. Скорость моего собственного кода увеличилась с 30 секунд с кодом в принятом ответе (добавленная мной сортировка удалена) до 2,25 с (2,7 без psyco) и 782 мс с psyco в Python 2.6.6.


person Community    schedule 05.03.2011    source источник
comment
Вопрос не ясен. Не могли бы вы привести пример вывода? Что вы подразумеваете под всеми возможными комбинациями?   -  person Andrea    schedule 06.03.2011
comment
Я включил три примера вывода в свой пост (почти) с самого начала.   -  person Tony Veijalainen    schedule 06.03.2011


Ответы (3)


Я использую список типа [(2, 9), (3, 3)] (для [2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3]) в качестве базового списка нерасширенных факторов и списка расширенных факторов. В каждом раунде я выбираю один фактор из базы, уменьшая его количество, и

  • добавить его в расширенный список, увеличив его длину, так что у нас есть один дополнительный фактор в сумме (до запонки)
  • умножьте его на все расширенные множители, порождая все возможности

С динамическим программированием и стратегией отсечения это стало очень быстро:

from itertools import groupby

def multiplicies( factors ):
    """ [x,x,x,,y,y] -> [(x,3), (y,2)] """
    return ((k, sum(1 for _ in group)) for k, group in groupby(factors))

def combinate(facs, cutoff=None):
    facs = tuple(multiplicies(facs))

    results = set()
    def explode(base, expanded):
        # `k` is the key for the caching
        # if the function got called like this before return instantly
        k = (base, expanded)
        if k in results:
            return
        results.add(k)

        # pick a factor
        for (f,m) in base:
            # remove it from the bases
            newb = ((g, (n if g!=f else n-1)) for g,n in base)
            newb = tuple((g,x) for g,x in newb if x > 0)

            # do we cutoff yet?
            if cutoff is None or len(newb) + len(expanded) < cutoff:
                explode(newb, tuple(sorted(expanded + (f,))))

            # multiply the pick with each factor in expanded
            for pos in range(len(expanded)):
                newexp = list(expanded)
                newexp[pos] *= f
                explode(newb, tuple(sorted(newexp)))

    explode(facs, ())
    # turn the `k` (see above) into real factor lists
    return set((tuple((x**y) for x,y in bases) + expanded) 
        for (bases, expanded) in results)

# you dont even need the cutoff here!
combinate([2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3])
# but you need it for 
combinate([2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,5,7,9,11], 5)

Попробуйте Psyco, если можете (я не могу, потому что у меня есть только Py2.7), это также может немного ускорить это.

person Jochen Ritzel    schedule 06.03.2011
comment
Какая-то особая причина снова определяет ветку в каждой рекурсии? - person Tony Veijalainen; 06.03.2011
comment
@Tony Veijalainen: рекурсия происходит в branch, а не в combinate. Функция определяется один раз для каждого входа. - person Jochen Ritzel; 06.03.2011
comment
Ваша программа хорошо справилась с примером в моем посте, но, похоже, зависла с коэффициентами 13824, наконец, дав ответ через 3 минуты 35 секунд. Должны найти способ ограничить форму факторов. Да, вы не вызываете комбинат рекурсивно, надо было внимательнее посмотреть ваш код, извините! - person Tony Veijalainen; 06.03.2011
comment
@Tony Veijalainen: Хороший пример, с такими факторами, как [2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3], легко увидеть, что мой подход генерирует множество факторов, таких как [4, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3], [2, 4, 2, 2, 2, 2, 2, 2, 3, 3, 3], [2, 2, 4, 2, 2, 2, 2, 2, 3, 3, 3], которые все одинаковы. Примерно такая же проблема и с unutbu. Итак, я думаю, вам придется сгруппировать факторы вместе, но это становится довольно сложным. Может быть завтра. - person Jochen Ritzel; 06.03.2011
comment
Если бы это было легко, я бы не спрашивал :) Я смогу это решить, но подумал, что прошу совета, чтобы преодолеть некоторые препятствия. Посмотрите на мою жалкую попытку узнать, что я пробовал и почему я остановился, чтобы рассмотреть подход, чтобы избежать регенерации. При наличии до 14 множителей (даже сзади), в основном небольших, взрыв перестановок происходит быстро (то есть факториально, даже не экспоненциально). - person Tony Veijalainen; 06.03.2011
comment
@Tony Veijalainen: Кстати, это для какой-то математической головоломки? - person Jochen Ritzel; 06.03.2011
comment
Спасибо за прекрасный ответ. Я только добавил отсортированный (sorted(sorted(comb) for comb in combinate([2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3]))), а затем должен проверить его в своей программе. Прекрасная работа. Единственное, что теперь я чувствую себя виноватым, если использую его в своей программе, поэтому, возможно, я попытаюсь также сделать собственное решение, основанное на неубывающих 5 разделах для задачи Эйлера 88. Нужно просто добавить правильное количество единиц впереди и зациклить факторинг чисел до 1200, сохраняя наименьшие суммы для каждой длины. - person Tony Veijalainen; 06.03.2011
comment
Судя по форуму, это решение в стиле сита, наконец, лучше всего подходит для моих нужд, так как его нужно делать предпочтительно до 2 * n (я получил правильный результат даже с разложением на множители 1,5 * n), что означает факторизацию всех чисел до 24000. Я получил результат, но немногим более одной минуты, тогда как лучшие решения в Python заняли 1,6 с общей функции memo с psyco. - person Tony Veijalainen; 07.03.2011
comment
См. лучший код в конце моего поста с форума Project Euler. - person Tony Veijalainen; 07.03.2011

То, что вы ищете, чаще называют делителем. Это отвечает на этот вопрос может вам помочь.

person President James K. Polk    schedule 05.03.2011
comment
Да, и у меня есть довольно эффективная процедура для этого, но проблема в том, что моя процедура не создает подмножества факторов, а только пары делителей, которые перекрываются при использовании факторов. Мне нужно было бы зациклить, чтобы произвести все возможные расстановки для чисел меньше 12000. По крайней мере, с наименьшими факторами. - person Tony Veijalainen; 06.03.2011

from __future__ import print_function
import itertools
import operator

def partition(iterable, chain=itertools.chain, map=map):
    # http://code.activestate.com/recipes/576795/
    # In [1]: list(partition('abcd'))
    # Out[1]: 
    # [['abcd'],
    #  ['a', 'bcd'],
    #  ['ab', 'cd'],
    #  ['abc', 'd'],
    #  ['a', 'b', 'cd'],
    #  ['a', 'bc', 'd'],
    #  ['ab', 'c', 'd'],
    #  ['a', 'b', 'c', 'd']]    
    s = iterable if hasattr(iterable, '__getslice__') else tuple(iterable)
    n = len(s)
    first, middle, last = [0], range(1, n), [n]
    getslice = s.__getslice__
    return [map(getslice, chain(first, div), chain(div, last))
            for i in range(n) for div in itertools.combinations(middle, i)]

def product(factors,mul=operator.mul):
    return reduce(mul,factors,1)

def factorings(factors,product=product,
               permutations=itertools.permutations,
               imap=itertools.imap,
               chain_from_iterable=itertools.chain.from_iterable,
               ):
    seen=set()
    seen.add(tuple([product(factors)]))
    for grouping in chain_from_iterable(
        imap(
            partition,
            set(permutations(factors,len(factors)))
            )):
        result=tuple(sorted(product(group) for group in grouping))
        if result in seen:
            continue
        else:
            seen.add(result)
            yield result

if __name__=='__main__':
    for f in factorings([2,2,3,3,5,7]):
        print(f,end=' ')

урожаи

(3, 420) (9, 140) (28, 45) (14, 90) (2, 630) (3, 3, 140) (3, 15, 28) (3, 14, 30) (2, 3, 210) (5, 9, 28) (9, 10, 14) (2, 9, 70) (2, 14, 45) (2, 7, 90) (3, 3, 5, 28) (3, 3, 10, 14) (2, 3, 3, 70) (2, 3, 14, 15) (2, 3, 7, 30) (2, 5, 9, 14) (2, 7, 9, 10) (2, 2, 7, 45) (2, 3, 3, 5, 14) (2, 3, 3, 7, 10) (2, 2, 3, 7, 15) (2, 2, 5, 7, 9) (2, 2, 3, 3, 5, 7) (5, 252) (10, 126) (18, 70) (6, 210) (2, 5, 126) (5, 14, 18) (5, 6, 42) (7, 10, 18) (6, 10, 21) (2, 10, 63) (3, 6, 70) (2, 5, 7, 18) (2, 5, 6, 21) (2, 2, 5, 63) (3, 5, 6, 14) (2, 3, 5, 42) (3, 6, 7, 10) (2, 3, 10, 21) (2, 3, 5, 6, 7) (2, 2, 3, 5, 21) (4, 315) (20, 63) (2, 2, 315) (4, 5, 63) (4, 9, 35) (3, 4, 105) (7, 9, 20) (3, 20, 21) (2, 2, 9, 35) (2, 2, 3, 105) (4, 5, 7, 9) (3, 4, 5, 21) (3, 3, 4, 35) (3, 3, 7, 20) (2, 2, 3, 3, 35) (3, 3, 4, 5, 7) (7, 180) (3, 7, 60) (2, 18, 35) (2, 6, 105) (3, 10, 42) (2, 3, 6, 35) (15, 84) (12, 105) (3, 5, 84) (5, 12, 21) (7, 12, 15) (4, 15, 21) (2, 15, 42) (3, 5, 7, 12) (3, 4, 7, 15) (2, 6, 7, 15) (2, 2, 15, 21) (21, 60) (30, 42) (6, 7, 30) (5, 7, 36) (2, 21, 30) (5, 6, 6, 7) (3, 12, 35) (6, 14, 15) (4, 7, 45) (35, 36) (6, 6, 35)
person unutbu    schedule 06.03.2011
comment
Выглядит красиво, у меня есть одна из моих собственных функций разбиения. Хотя, похоже, этот способ более эффективен. Однако первый результат следует отбросить для правильного ответа на факторы. Надеюсь, регенерации не много, так как я вижу, что вы используете увиденный набор. Я думаю, вы используете его для выполнения «permutations_with_repetitions», как у меня есть одна версия, взятая из сети раньше. Нужно посмотреть, лучше ли это, чем моя идея использовать двоичное число подходящего размера (бит на элемент) для выбора и дополнить его, чтобы узнать оставшуюся часть из элементов. - person Tony Veijalainen; 06.03.2011
comment
@Tony Veijalainen: Да, вероятно, есть лучшие способы реализовать это. Если вы поместите счетчик в блок if result in seen, вы увидите более 600 повторений уже сгенерированных результатов. - person unutbu; 06.03.2011
comment
Нужно попробовать код перестановки с функцией повторов. Возможно, необходимо подсчитать кратность и выполнить разбиение чисел экспоненты для каждого фактора. Моя собственная функция разбиения также производила повторы, и я использовал увиденный набор, подобный вашему решению этой проблемы, когда я использовал его в последний раз. Похоже, что используемое вами разбиение плохо справляется с повторами. - person Tony Veijalainen; 06.03.2011
comment
@Tony Veijalainen: Я думаю, что повторения происходят из-за таких ситуаций: [2,2,3,3,5,7] делится на [[2],[2],[3],[3],[5,7]], а [2,2,3,3,7,5] делится на [[2],[2],[3],[3],[7,5]]. Обратите внимание, что [5,7] и [7,5] в конечном итоге образуют один и тот же делитель, 35. Я не придумал хорошего способа избежать этого. - person unutbu; 06.03.2011