Оптимизация процесса Brute Force в python/pypy для поиска номеров One-Child

Подход грубой силы предназначен не для решения вопроса, а для помощи в его исследовании. Я работаю над задачей Project Euler, в которой мне нужно найти все числа от X до единицы меньше Y, которые имеют ровно одна «подстрока», кратная количеству цифр в числе.

Это так называемые однодетные номера. 104 — однодетное число. Из его подстрок [1, 0, 4, 10, 04, 104] только 0 делится на 3. Задача состоит в том, чтобы найти количество однодочерних чисел, которые встречаются меньше 10*17. Метод грубой силы не является правильным подходом; однако у меня есть теория, которая требует, чтобы я знал количество дочерних чисел, встречающихся до 10*11.

Мне не удалось найти этот номер даже после того, как я оставил свой ноутбук включенным на полдня. Я попробовал Cython, поставил я начинающий программист, который ничего не знает о C. Результат был очень плохим. Я даже пробовал облачные вычисления, но мой ssh-канал всегда ломается до завершения процесса.

Если бы кто-то мог помочь мне определить некоторые другие подходы или оптимизацию для предварительного формирования метода BRUT FORCE для этой проблемы до 10**11, я был бы очень признателен.

ПОЖАЛУЙСТА, НЕ НАДО...

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

## a one child number has only one "substring" divisable by the
## number of digits in the number. Example: 104 is a one child number as 0
## is the only substring which 3 may divide, of the set [1,0,4,10,04,104]

## FYI one-child numbers are positive, so the number 0 is not one-child


from multiprocessing import Pool
import os.path

def OneChild(numRange): # hopefully(10*11,1)
    OneChild = []
    start = numRange[0]
    number = numRange[1]

    ## top loop handles one number at a time
    ## loop ends when start become larger then end
    while number >= start:

        ## preparing to analayze one number
        ## for exactly one divisableSubstrings
        numberString = str(start)
        numDigits = len(numberString)
        divisableSubstrings = 0
        ticker1,ticker2 = 0, numDigits

        ## ticker1 starts at 0 and ends at number of digits - 1
        ## ticker2 starts at number of digits and ends +1 from ticker1
        ## an example for a three digit number: (0,3) (0,2) (0,1) (1,3) (1,2) (2,3)
        while ticker1 <= numDigits+1:
            while ticker2 > ticker1:
                if int(numberString[ticker1:ticker2]) % numDigits == 0:
                    divisableSubstrings += 1
                    if divisableSubstrings == 2:
                        ticker1 = numDigits+1
                        ticker2 = ticker1

                ##Counters    
                ticker2 -= 1
            ticker1 += 1
            ticker2 = numDigits             
        if divisableSubstrings == 1: ## One-Child Bouncer 
            OneChild.append(start) ## inefficient but I want the specifics
        start += 1 
    return (OneChild)

## Speed seems improve with more pool arguments, labeled here as cores
## Im guessing this is due to pypy preforming best when task is neither
## to large nor small
def MultiProcList(numRange,start = 1,cores = 100): # multiprocessing
    print "Asked to use %i cores between %i numbers: From %s to %s" % (cores,numRange-start, start,numRange)
    cores = adjustCores(numRange,start,cores)
    print "Using %i cores" % (cores)

    chunk = (numRange+1-start)/cores
    end = chunk+start -1 
    total, argsList= 0, []
    for i in range(cores):
        # print start,end-1
        argsList.append((start,end-1))
        start, end = end , end + chunk
    pool = Pool(processes=cores)
    data = pool.map(OneChild,argsList)
    for d in data:
        total += len(d)
    print total

##    f = open("Result.txt", "w+")
##    f.write(str(total))
##    f.close()

def adjustCores(numRange,start,cores):
    if start == 1:
        start = 0
    else:
        pass
    while (numRange-start)%cores != 0:
        cores -= 1
    return cores

#MultiProcList(10**7)
from timeit import Timer
t = Timer(lambda: MultiProcList(10**6))
print t.timeit(number=1)

person James Beezho    schedule 13.03.2013    source источник
comment
Почему вы используете метод грубой силы?   -  person Blender    schedule 13.03.2013
comment
Он использует грубую силу только для небольшого подмножества проблемы, а затем использует теорию чисел для фактического решения.   -  person max k.    schedule 13.03.2013
comment
Самый эффективный подход, который я могу придумать, занимает 15 секунд, чтобы выполнить 10 ** 7, даже с PyPy. Я действительно сомневаюсь, что есть способ взломать это.   -  person Blender    schedule 13.03.2013
comment
Вы запускали его через какое-либо программное обеспечение для тестирования, чтобы увидеть, что занимает больше всего времени? VisualStudio имеет довольно хороший по памяти   -  person max k.    schedule 13.03.2013
comment
Как насчет разрыва двойного цикла while, когда divisableSubstrings == 2.   -  person HYRY    schedule 13.03.2013
comment
@max k совершенно правильно. Решения 10i (где i - простое число) имеют большие простые делители, которые приводят к меньшим простым числам, если предварительно сформировано pi(). Делители для 10i i=[2,3,5,7] последовательно требуют +1 pi операций, чтобы получить не простое число. Эти не простые числа также имеют базовый порядок. Проблема в том, что 7 и 5 — единственные числа, у которых их странные простые делители меньше решения. Я подозреваю, что 2 и 5 не дадут мне информацию, которую я ищу, потому что они делят 10 поровну. Наличие третьего простого числа, которое не делится на десять, сделает все намного яснее. Пожалуйста! не порти   -  person James Beezho    schedule 13.03.2013
comment
@JamesBeezho: сомневаюсь, что вы услышите спойлеры. Только 94 человека решили проблему.   -  person Blender    schedule 13.03.2013
comment
@макск. Я никогда не слышал о визуальной студии, и это дорого. Стоит ли учитывать, что основная часть работы приходится на циклы while уровня 2 и 3? Когда я делал этот код, я передал длинные списки разложенных чисел специальному методу для подсчета одного дочернего элемента, и скорость моей памяти была узким местом. Я недавно узнал о библиотеках для C-массивов, но они мне пока чужды. Стоит ли мне тратить время на программу, которая создает массивы в одном процессе и навязывает деление в другом? Кто-нибудь знает о массивах C, которые вы можете использовать с pypy. Должен ли я даже беспокоиться о numpy.   -  person James Beezho    schedule 13.03.2013
comment
Может быть, тогда оно того не стоило, я когда-либо использовал его только на работе (отлично, что кто-то другой платит), но в основном режим, о котором я говорил, дает вам базовый профиль того, какой процент (или сколько мс) тратится на каждый метод. или вызов функции, поэтому он позволяет вам поменять местами вещи и найти несколько более оптимизированные способы выполнения задач. Звучит как грубая сила, это будет борьба, несмотря ни на что   -  person max k.    schedule 13.03.2013


Ответы (1)


Это мой самый быстрый код грубой силы. Он использует cython для ускорения вычислений. Вместо того, чтобы проверять все числа, он рекурсивно находит все числа с одним дочерним элементом.

%%cython
cdef int _one_child_number(int s, int child_count, int digits_count):
    cdef int start, count, c, child_count2, s2, part, i
    if s >= 10**(digits_count-1):
        return child_count
    else:
        if s == 0:
            start = 1
        else:
            start = 0
        count = 0
        for c in range(start, 10):
            s2 = s*10 + c
            child_count2 = child_count
            i = 10
            while True:
                part = s2 % i
                if part % digits_count == 0:
                    child_count2 += 1
                    if child_count2 > 1:
                        break
                if part == s2:
                    break
                i *= 10

            if child_count2 <= 1:
                count += _one_child_number(s2, child_count2, digits_count)
        return count 

def one_child_number(int digits_count):
    return _one_child_number(0, 0, digits_count)

Чтобы найти число F (10 ** 7), требуется около 100 мс, чтобы получить результат 277674.

print sum(one_child_number(i) for i in xrange(8))

Для вычисления больших результатов требуется 64-битное целое число.

РЕДАКТИРОВАТЬ: я добавил некоторые комментарии, но мой английский не очень хорош, поэтому я преобразовал код в чистый код Python и добавил немного печати, чтобы помочь вам понять, как это работает.

_one_child_number добавляет цифру слева к s рекурсивно, child_count — это количество дочерних элементов в s, digits_count — последние цифры s.

def _one_child_number(s, child_count, digits_count):
    print s, child_count
    if s >= 10**(digits_count-1): # if the length of s is digits_count
        return child_count # child_count is 0 or 1 here, 1 means we found one one-child-number.
    else:
        if s == 0: 
            start = 1 #if the length of s is 0, we choose from 123456789 for the most left digit.
        else:
            start = 0 #otherwise we choose from 0123456789 
        count = 0 # init the one-child-number count
        for c in range(start, 10): # loop for every digit
            s2 = s*10 + c  # add digit c to the right of s

            # following code calculates the child count of s2
            child_count2 = child_count 
            i = 10
            while True:
                part = s2 % i
                if part % digits_count == 0:
                    child_count2 += 1
                    if child_count2 > 1: # when child count > 1, it's not a one-child-number, break
                        break
                if part == s2:
                    break
                i *= 10

            # if the child count by far is less than or equal 1, 
            # call _one_child_number recursively to add next digit.
            if child_count2 <= 1: 
                count += _one_child_number(s2, child_count2, digits_count)
        return count 

Здесь он выводит _one_child_number(0, 0, 3), а количество трехзначного числа одного дочернего элемента представляет собой сумму второго столбца, в котором первый столбец представляет собой трехзначное число.

0 0
1 0
10 1
101 1
104 1
107 1
11 0
110 1
111 1
112 1
113 1
114 1
115 1
116 1
117 1
118 1
119 1
12 1
122 1
125 1
128 1
13 1
131 1
134 1
137 1
14 0
140 1
141 1
142 1
143 1
144 1
145 1
146 1
147 1
148 1
149 1
15 1
152 1
155 1
158 1
16 1
161 1
164 1
167 1
17 0
170 1
171 1
172 1
173 1
174 1
175 1
176 1
177 1
178 1
179 1
18 1
182 1
185 1
188 1
19 1
191 1
194 1
197 1
2 0
20 1
202 1
205 1
208 1
21 1
211 1
214 1
217 1
22 0
220 1
221 1
222 1
223 1
224 1
225 1
226 1
227 1
228 1
229 1
23 1
232 1
235 1
238 1
24 1
241 1
244 1
247 1
25 0
250 1
251 1
252 1
253 1
254 1
255 1
256 1
257 1
258 1
259 1
26 1
262 1
265 1
268 1
27 1
271 1
274 1
277 1
28 0
280 1
281 1
282 1
283 1
284 1
285 1
286 1
287 1
288 1
289 1
29 1
292 1
295 1
298 1
3 1
31 1
311 1
314 1
317 1
32 1
322 1
325 1
328 1
34 1
341 1
344 1
347 1
35 1
352 1
355 1
358 1
37 1
371 1
374 1
377 1
38 1
382 1
385 1
388 1
4 0
40 1
401 1
404 1
407 1
41 0
410 1
411 1
412 1
413 1
414 1
415 1
416 1
417 1
418 1
419 1
42 1
422 1
425 1
428 1
43 1
431 1
434 1
437 1
44 0
440 1
441 1
442 1
443 1
444 1
445 1
446 1
447 1
448 1
449 1
45 1
452 1
455 1
458 1
46 1
461 1
464 1
467 1
47 0
470 1
471 1
472 1
473 1
474 1
475 1
476 1
477 1
478 1
479 1
48 1
482 1
485 1
488 1
49 1
491 1
494 1
497 1
5 0
50 1
502 1
505 1
508 1
51 1
511 1
514 1
517 1
52 0
520 1
521 1
522 1
523 1
524 1
525 1
526 1
527 1
528 1
529 1
53 1
532 1
535 1
538 1
54 1
541 1
544 1
547 1
55 0
550 1
551 1
552 1
553 1
554 1
555 1
556 1
557 1
558 1
559 1
56 1
562 1
565 1
568 1
57 1
571 1
574 1
577 1
58 0
580 1
581 1
582 1
583 1
584 1
585 1
586 1
587 1
588 1
589 1
59 1
592 1
595 1
598 1
6 1
61 1
611 1
614 1
617 1
62 1
622 1
625 1
628 1
64 1
641 1
644 1
647 1
65 1
652 1
655 1
658 1
67 1
671 1
674 1
677 1
68 1
682 1
685 1
688 1
7 0
70 1
701 1
704 1
707 1
71 0
710 1
711 1
712 1
713 1
714 1
715 1
716 1
717 1
718 1
719 1
72 1
722 1
725 1
728 1
73 1
731 1
734 1
737 1
74 0
740 1
741 1
742 1
743 1
744 1
745 1
746 1
747 1
748 1
749 1
75 1
752 1
755 1
758 1
76 1
761 1
764 1
767 1
77 0
770 1
771 1
772 1
773 1
774 1
775 1
776 1
777 1
778 1
779 1
78 1
782 1
785 1
788 1
79 1
791 1
794 1
797 1
8 0
80 1
802 1
805 1
808 1
81 1
811 1
814 1
817 1
82 0
820 1
821 1
822 1
823 1
824 1
825 1
826 1
827 1
828 1
829 1
83 1
832 1
835 1
838 1
84 1
841 1
844 1
847 1
85 0
850 1
851 1
852 1
853 1
854 1
855 1
856 1
857 1
858 1
859 1
86 1
862 1
865 1
868 1
87 1
871 1
874 1
877 1
88 0
880 1
881 1
882 1
883 1
884 1
885 1
886 1
887 1
888 1
889 1
89 1
892 1
895 1
898 1
9 1
91 1
911 1
914 1
917 1
92 1
922 1
925 1
928 1
94 1
941 1
944 1
947 1
95 1
952 1
955 1
958 1
97 1
971 1
974 1
977 1
98 1
982 1
985 1
988 1
person HYRY    schedule 13.03.2013
comment
Поскольку эта программа является рекурсивной, операторы печати и вложенные операторы печати неэффективны для передачи магии вашего кода. Если он не раскрывает какую-то большую базовую структуру за одним дочерним числом, которая испортила бы мне проблему, не могли бы вы объяснить, как это работает. - person James Beezho; 15.03.2013