Решето Эра­то­сфена на sed.


Анно­та­ция

Опи­сы­ва­ется сце­на­рий для широко извест­ного тек­сто­вого редак­тора sed, гене­ри­ру­ю­щий про­стые числа с помо­щью решета Эра­то­сфена. На основе дан­ной реа­ли­за­ции решета напи­сан скрипт фак­то­ри­за­ции чисел, а так­же скрипты, реа­ли­зу­ю­щие аль­тер­на­тив­ные под­ходы к обнаружению/детектированию ана­грамм и к чис­лен­ной симу­ля­ции дина­ми­че­ской системы из гипо­тезы Кол­латца. В конце сооб­ще­ния речь идёт о воз­мож­ных опти­ми­за­циях решета Эра­то­сфена, и, в част­но­сти, опи­сы­ва­ется решето Эйлера, тоже реа­ли­зо­ван­ное в виде скрипта для sed.


Содер­жа­ние:

Вве­де­ние

Ино­гда в сце­на­риях (скрип­тах) для поточ­ного редак­тора sed тре­бу­ются про­стые числа. И, учи­ты­вая отсут­ствие в sed нор­маль­ной под­держки ариф­ме­тики, исполь­зо­ва­ние решета Эра­то­сфена [1] выгля­дит удач­ным реше­нием для гене­ра­ции про­стых чисел прямо в sed-скрипте. Поверх­ност­ный поиск в поис­ко­вых маши­нах интер­нета не дал кон­крет­ных резуль­та­тов, поэтому я решил напи­сать этот скрипт само­сто­я­тельно, а в этой заметке рас­ска­зать о его работе.

Скрипт (sieve.sed) полу­чился довольно ком­пакт­ным, по край­ней мере в том смысле, что код печати резуль­та­тов зани­мает лишь немного меньше поло­вины всего исход­ного тек­ста. Инте­ресно, что на основе этой реа­ли­за­ции решета не состав­ляет труда напи­сать ути­литу фак­то­ри­за­ции чисел (см. factor.sed). Фак­ти­че­ски, пер­вая вер­сия factor.sed была полу­чена уда­ле­нием кода печати резуль­та­тов (i.e. уда­ле­нием почти поло­вины кода, см. выше) и добав­ле­нием всего одной строчки/инструкции в код решета. Она печа­тала фак­торы в унар­ной системе счис­ле­ния и не опре­де­ляла сте­пени най­ден­ных про­стых дели­те­лей (т.е., строго говоря, нахо­дила не все дели­тели). Её улуч­ше­ние потре­бо­вало неко­то­рых уси­лий и при­вело к неко­то­рому уве­ли­че­нию и дуб­ли­ро­ва­нию кода (дру­гими сло­вами, суще­ствует потен­циал для сокра­ще­ния объ­ема кода factor.sed).

(Обсуж­да­е­мые скрипты могут быть най­дены в [2].)

Тео­ре­ти­че­ская основа про­се­и­ва­ния

Работа решета заклю­ча­ется в ите­ра­тив­ном уда­ле­нии — вычер­ки­ва­нии — состав­ных чисел из теку­щего рабо­чего мно­же­ства чисел . На каж­дом шаге уда­ля­ются все числа, крат­ные оче­ред­ному най­ден­ному про­стому числу . Новым про­стым чис­лом ста­но­вится наи­мень­шее невы­черк­ну­тое число, боль­шее : , где . Если же , то про­цесс завер­ша­ется, а послед­нее мно­же­ство (N.B., рав­ное ) ста­но­вится резуль­та­том его работы.

В каче­стве началь­ного при­бли­же­ния выби­ра­ется интер­вал для дан­ного , — длины решета, — и пола­га­ется .

Более струк­ту­ри­ро­ванно:


Утвер­жда­ется, что , где – мно­же­ство про­стых чисел.

Набро­сок дока­за­тель­ства

Озву­чен­ный алго­ритм порож­дает индук­тив­ное дока­за­тель­ство.

База: .

Пред­по­ло­же­ние индук­ции: , где . Эта рекур­рент­ная фор­мула своим след­ствием имеет


Шаг индук­ции: необ­хо­димо пока­зать, что – сле­ду­ю­щее про­стое число. Итак, допу­стим мы выбрали . Фак­то­ри­зуем его, , где . Фор­мула (1) гово­рит, что числа, крат­ные про­стым уже уда­лены из , а зна­чит и из ; поэтому .

(Име­ется в виду, что для неко­то­рого , можно выне­сти за знак про­из­ве­де­ния: , и именно поэтому не может быть меньше или равен – такие крат­ные уже вычерк­ну­ты; если же есть един­ствен­ный мно­жи­тель, то тем более он дол­жен быть больше – по опре­де­ле­нию мно­же­ства , по кото­рому и берется мини­мум для нахож­де­ния раз­ла­га­е­мого .)

Так­же понятно, что мно­жи­тели не пре­вы­шают самого числа , т.е. .

Кон­кретно, из и нефак­то­ри­зу­е­мо­сти сле­дует, что (i.e., нико­гда не вычер­ки­ва­лось и не будет вычер­ки­ваться). А из сле­дует, что . При этом, если (а не ), то это про­ти­во­ре­чит мини­маль­но­сти .

Зна­чит, от про­тив­ного, . Т.е. и дру­гих про­стых между и нет (иначе они не были бы вычерк­нуты на преды­ду­щих шагах и вхо­дили бы в , что опять про­ти­во­ре­чило бы мини­маль­но­сти в ).

Реа­ли­за­ция

Прин­цип работы sieve.sed

Общий поша­го­вый вид алго­ритма:

Воз­можно есть смысл немного дета­ли­зи­ро­вать работу решета на этапе поиска сле­ду­ю­щего про­стого числа. Сна­чала перед реше­том при­пи­сы­ва­ется мар­кер #. Далее, в уже частично запол­нен­ном решете отыс­ки­ва­ется пер­вая еди­ница, лежа­щая пра­вее мар­кера #, после чего этот мар­кер пере­ме­ща­ется на пози­цию сразу после этой еди­ницы. Длина части решета, лежа­щей левее мар­кера есть новое про­стое число. Этот пре­фикс копи­ру­ется на сле­ду­ю­щую строчку и может счи­таться запи­сью най­ден­ного про­стого числа в унар­ной системе счис­ле­ния.

После сим­вола # добав­ля­ется вспо­мо­га­тель­ный мар­кер @, а в начало сле­ду­ю­щей строки – мар­кер :, после чего оба мар­кера начи­нают сдви­гаться вправо на один сим­вол за раз. Если мар­кер : ока­зы­ва­ется в конце вто­рой строки, а @ ещё не дошёл до конца пер­вой, то мар­кер @ с необ­хо­ди­мо­стью ока­зы­ва­ется сразу после сле­ду­ю­щего состав­ного числа и мы можем запи­сать в эту пози­цию ноль, неза­ви­симо от зна­че­ния, кото­рое было там ранее. Теперь мар­кер : воз­вра­ща­ется в начало вто­рой строки.

Про­цесс пере­ме­ще­ния мар­ке­ров @ и : про­дол­жа­ется до вычер­ки­ва­ния всех мно­жи­те­лей теку­щего про­стого числа (i.e., пока @ не дой­дёт до конца пер­вой строки), после чего эти мар­керы уда­ля­ются и опи­сан­ный про­цесс повто­ря­ется.

Прин­цип работы factor.sed

Этот скрипт напи­сан на основе выше­опи­сан­ного sieve.sed, но код обна­ру­же­ния нового про­стого числа слегка моди­фи­ци­ро­ван, а после него добав­лен код опре­де­ле­ния пока­за­теля сте­пени для най­ден­ного про­стого мно­жи­теля.

Число, под­ле­жа­щее фак­то­ри­за­ции исполь­зу­ется про­сто в каче­стве длины решета. Понятно, что про­стые, вхо­дя­щие в этого решето (и только они) будут вхо­дить в раз­ло­же­ние дан­ного числа на про­стые мно­жи­тели.

Поиск сле­ду­ю­щего про­стого отли­ча­ется от ана­ло­гич­ного кода в скрипте sieve.sed фак­ти­че­ски лишь тем, что при обна­ру­же­нии про­стого числа обну­ля­ется отдель­ный счет­чик, а при вычер­ки­ва­нии каж­дого состав­ного числа этот счёт­чик инкре­мен­ти­ру­ется. Оче­видно, что после вычер­ки­ва­ния всех мно­жи­те­лей теку­щего про­стого числа (не выхо­дя­щих за пре­делы решета, есте­ственно) зна­че­ние счёт­чика будет равно целой части резуль­тата деле­ния исход­ного фак­то­ри­зу­е­мого числа на теку­щий про­стой мно­жи­тель.

Далее мы должны про­сто про­дол­жать делить зна­че­ние этого счёт­чика на теку­щее про­стое число, пока счёт­чик не обну­лится. При­чём после каж­дой опе­ра­ции деле­ния мы снова пере­хо­дим к коду, выпол­нен­ному после обна­ру­же­ния теку­щего про­стого числа (т.е. мы печа­таем тоже самое про­стое число ещё раз, делим счёт­чик на это про­стое и т.д.).

В резуль­тате, скрипт печа­тает теку­щий про­стой мно­жи­тель нуж­ное коли­че­ство раз, а после печати всех про­стых мно­жи­те­лей, мы полу­чаем пол­ное раз­ло­же­ние исход­ного числа, рав­ного длине решета.

Больше эзо­те­рики

Так­же были напи­саны два демон­стра­ци­он­ных скрипта, anagram.sed и collatz.sed, исполь­зу­ю­щих про­стые числа и фак­то­ри­за­цию для реше­ния задач, в кото­рых в явном виде про­стые числа обычно не при­ме­няют. Пер­вый скрипт (вме­сто кото­рого, есте­ственно, гораздо эффек­тив­нее было бы исполь­зо­вать ком­по­зи­цию сор­ти­ровки сим­во­лов в стро­ках и срав­не­ния резуль­ти­ру­ю­щих строк-последовательностей) реа­ли­зует идею из [3,4] и экс­плу­а­ти­рует основ­ную тео­рему ариф­ме­тики [5] для детек­ти­ро­ва­ния ана­грамм, т.е. для опре­де­ле­ния, явля­ются ли две дан­ные строки-последовательности пере­ста­нов­ками друг-друга. Каж­дому сим­волу после­до­ва­тель­но­стей сопо­став­ля­ется един­ствен­ным обра­зом неко­то­рое про­стое число, после чего все эти числа, соот­вет­ству­ю­щие сим­во­лам из кон­крет­ной строки, пере­мно­жа­ются. Основ­ная тео­рема ариф­ме­тики вме­сте с ком­му­та­тив­но­стью умно­же­ния гаран­ти­руют, что ана­граммы будут давать оди­на­ко­вые произведения-сигнатуры.

Ранее напи­сан­ный алго­ритм про­се­и­ва­ния был модифицирован/упрощён для работы только с дво­ич­ными чис­лами. Для умно­же­ния дво­ич­ных чисел я исполь­зо­вал код из gist’а [6].

Вто­рой скрипт, collatz.sed, реа­ли­зует извест­ную дина­ми­че­скую систему из гипо­тезы (см. [7,8]), но вме­сто при­выч­ных опе­ра­ций и исполь­зу­ются мани­пу­ля­ции с про­стыми чис­лами, на мой взгляд лучше пока­зы­ва­ю­щие истин­ную теоретико-числовую суть гипо­тезы Кол­латца.

Пусть дана функ­ция


Гипо­теза Кол­латца утвер­ждает, что . Более точно, похоже, что для всех нату­раль­ных ите­ра­ции схо­дятся к циклу .

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

При­бав­ле­ние еди­ницы гаран­ти­рует, что резуль­тат не будет делиться на всё, на что дели­лось число , или, дру­гими сло­вами, в фак­то­ри­за­ции резуль­тата опе­ра­ции будут задей­ство­ваны совсем дру­гие мно­жи­тели, ни одного из кото­рых не было в фак­то­ри­за­ции ; с исполь­зо­ва­нием наи­боль­шего общего дели­теля, . Добав­ле­ние же тройки гаран­ти­рует, что в раз­ло­же­нии ника­ких троек тоже не будет, даже если их и не было в раз­ло­же­нии изна­чально.

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

(Фак­ти­че­ски, мы имеем дело с гон­кой двух про­цес­сов – про­цес­сом «ге­не­ра­ции» про­стых чисел, ска­жем, реше­том, и про­цес­сом их «по­треб­ле­ния» за счёт при­бав­ле­ния еди­ни­цы; судя по экс­пе­ри­мен­там, про­цесс потреб­ле­ния рабо­тает быст­рее – в какой-то момент, неза­ви­симо от стар­то­вого зна­че­ния, новых про­стых не ока­зы­ва­ется в нали­чии и ите­ра­ции схо­дятся к уже упо­ми­нав­ше­муся циклу )

Гипо­теза по праву отно­сится к классу задач, мно­гими назы­ва­е­мыми «ма­те­ма­ти­че­скими болез­ня­ми»; в таких зада­чах соче­та­ется пре­дель­ная про­стота фор­му­ли­ровки усло­вия с непод­да­ю­щейся мно­гим мате­ма­ти­кам слож­но­стью реше­ния, а в дан­ном слу­чае, по всей види­мо­сти, вообще с пол­ной него­тов­но­стью совре­мен­ной мате­ма­тики к дока­за­тель­ству этой гипо­тезы.

Об опти­ми­за­ции

В этом сооб­ще­нии и в пред­ло­жен­ных скрип­тах вообще не при­ме­ня­ются рас­про­странён­ные опти­ми­за­ции решета Эра­то­сфена. Но для пол­ноты кар­тины, навер­ное стоит упо­мя­нуть воз­мож­ные направ­ле­ния для улуч­ше­ния. Во-первых, можно огра­ни­читься рас­смот­ре­нием только чисел , таких, что для неко­то­рого ; e.g., при это соот­вет­ствует игно­ри­ро­ва­нию всех чёт­ных чисел, боль­ших двух, все­гда по-определению явля­ю­щихся состав­ными.

Во-вторых, пере­бор крат­ных для нечёт­ных про­стых чисел можно вести с шагом , а не ; при исполь­зо­ва­нии выше­опи­сан­ного обоб­ще­ния на слу­чай чисел , вза­имно про­стых с несколь­кими пер­выми про­стыми чис­лами [не пре­вы­ша­ю­щими ], можно гене­ри­ро­вать крат­ные так, чтобы они тоже были вза­имно про­стыми с чис­лами из . См. так­же [9].

В-третьих, про­цесс вычер­ки­ва­ния чисел, крат­ных оче­ред­ному най­ден­ному про­стому , можно начи­нать сразу с , а не с ; как след­ствие, не имеет смысла про­дол­жать вычер­ки­ва­ние крат­ных если . Т.е., мно­жи­тели для , исполь­зу­ю­щи­еся при вычер­ки­ва­нии состав­ных чисел, обра­зуют мно­же­ство (мак­си­мум здесь выбран так, чтобы после умно­же­ния на полу­чи­лось , а мини­мум соот­вет­ствует началу вычер­ки­ва­ния с ).

(Есте­ственно, жела­ю­щие могут попро­бо­вать доба­вить эти опти­ми­за­ции в обсуж­да­е­мый скрипт, хотя с квад­ра­тами в sed, навер­ное, лучше не свя­зы­ваться).

Решето Эйлера

Решето Эра­то­сфена может вычер­ки­вать одни и те же числа по нескольку раз. Эйлер при­ду­мал (для реша­е­мой им в тот момент вполне прак­ти­че­ской мате­ма­ти­че­ской задачи) моди­фи­ка­цию [10] решета, поз­во­ля­ю­щую вычер­ки­вать состав­ные числа только один раз. Алго­ритм можно опи­сать так:


Здесь явля­ется дли­ной решета (как и в ранее опи­сан­ном алго­ритме для решета Эра­то­сфена). Сге­не­ри­ро­ван­ные про­стые числа кол­лек­ци­о­ни­ру­ются в мно­же­стве , кото­рое и воз­вра­ща­ется при завер­ше­нии работы алго­ритма.

Все опе­ра­ции про­из­во­дятся с рабо­чим мно­же­ством чисел , изна­чально рав­ным отрезку . На каж­дой ите­ра­ции выби­ра­ется мини­маль­ный эле­мент этого мно­же­ства и объ­яв­ля­ется сле­ду­ю­щим про­стым чис­лом (сразу же сохра­ня­е­мым в при теку­щем зна­че­нии ), после чего фор­ми­ру­ется новое мно­же­ство про­из­ве­де­ний , т.е., числа крат­ные , но ещё не ни разу не вычерк­ну­тые, поме­ча­ются для после­ду­ю­щего уда­ле­ния. После этого, поме­чен­ные числа уда­ля­ются из рабо­чего мно­же­ства с помо­щью теоретико-множественной опе­ра­ции вычи­та­ния . Став­ший теперь ненуж­ным эле­мент , до сих пор оста­вав­шийся в мно­же­стве (т.к. до этого, при фор­ми­ро­ва­нии вспо­мо­га­тель­ного мно­же­ства поме­чен­ных чисел , умно­жался как мини­мум на 2), уда­ля­ется из . И если это мно­же­ство стало пустым, то алго­ритм завер­ша­ется, в про­тив­ном же слу­чае инкре­мен­ти­ру­ется счёт­чик ите­ра­ций и про­цесс повто­ря­ется (с поиска нового мини­маль­ного эле­мента ).

Несмотря на неко­то­рые внеш­ние раз­ли­чия, суть этого алго­ритма всё та же: для каж­дого оче­ред­ного про­стого числа из решета уда­ля­ются/вычеркиваются все числа, крат­ные ему, в резуль­тате чего сле­ду­ю­щее невы­черк­ну­тое число тоже ока­зы­ва­ется про­стым. Ите­ри­ро­ва­ние этих мани­пу­ля­ций при­во­дит к «про­се­и­ва­нию» исход­ного набора чисел и к уда­ле­нию всех состав­ных чисел (в решете Эйлера, в отли­чии от ори­ги­наль­ного решета Эра­то­сфена, про­стые числа тоже уда­ля­ются из рабо­чего мно­же­ства, но по мере их обна­ру­же­ния они накап­ли­ва­ются в дру­гом мно­же­стве или, ска­жем, выво­дятся на печать).

Важно, что эле­менты, поме­ча­е­мые для уда­ле­ния (т.е. добав­ля­е­мые в ), по преж­нему участ­вуют в про­цессе попол­не­ния мно­же­ства .

(Ниже­сле­ду­ю­щий мате­риал напи­сан по моти­вам [11].)

Чем же обес­пе­чи­ва­ется глав­ное свой­ство решета Эйлера, т.е. почему в нико­гда не попа­дают уже уда­лен­ные из числа? Если в попало число, боль­шее , то оно, оче­видно, не может быть уда­лено повторно из , так как там таких боль­ших чисел про­сто не было изна­чально: по постро­е­нию. Если же не пре­вы­шает , то исходя из смысла при­сва­и­ва­ния (см. шаг 4 выше­при­ве­ден­ного алго­ритма), , где .

Суть в том, что любое нату­раль­ное число можно един­ствен­ным обра­зом пред­ста­вить в виде , где – наи­мень­ший про­стой дели­тель (фак­тор) числа . При этом среди фак­то­ров числа , оче­видно, не будет чисел, мень­ших . (Т.е. мы про­сто «от­со­еди­ня­ем» наи­мень­ший фак­тор от и фор­ми­руем раз­ло­же­ние пола­гая ; тогда из основ­ной тео­ремы ариф­ме­тики [5], т.е. из един­ствен­но­сти фак­то­ри­за­ции, как раз и сле­дует един­ствен­ность тре­бу­е­мого раз­ло­же­ния .)

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

Пока­жем, что такое опре­де­ле­ние сов­па­дает с из алго­ритма (см. выше). На каж­дой ите­ра­ции, если , то , т.е. все числа из , боль­шие чем не делятся на про­стые мень­шие ведь состав­ные числа, деля­щи­еся на уже были вычерк­нуты на преды­ду­щих ите­ра­циях алго­ритма. (Здесь идет речь об до шага 5, т.е. до опе­ра­ции .)

Т.о., содер­жит числа из про­ме­жутка , не деля­щи­еся на про­стые, мень­шие , т.е. . Это сов­па­дает с выра­же­нием для .

Оста­лось пока­зать, что , т.е., что под­ле­жа­щие вычер­ки­ва­нию числа уже при­сут­ствуют в (либо про­сто выхо­дят за пре­делы решета) и их не при­дётся вычер­ки­вать повторно. Как мы уже выяс­нили, каж­дый такой , для неко­то­рого един­стве­нен и так как на всех преды­ду­щих ите­ра­циях алго­ритма мы уда­ляли лишь числа, наи­мень­ший фак­тор кото­рых был меньше , то из и началь­ного при­бли­же­ния для , рав­ного , как раз и сле­дует, что .

За более подроб­ной инфор­ма­цией о решете Эйлера сле­дует обра­титься, e.g., к [1,12,11] . Инте­ресно, что в неко­то­рых рабо­тах, напри­мер, в той же [12], пря­мые отсылы к Эйлеру отсут­ствуют, поэтому ино­гда гово­рят, что это решето неод­но­кратно пере­и­зоб­ре­та­лось раз­лич­ными авто­рами.

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


для . Для дока­за­тель­ства важ­ного тож­де­ства , Эйлер строил после­до­ва­тель­ность начи­ная с . Здесь – такое мно­же­ство, что .

Сна­чала, есте­ственно, , и поэтому равен 2, т.е. пер­вому про­стому числу. Умно­же­ние на сумму , рав­ную даёт новую сумму, в кото­рой все осно­ва­ния кратны двум. Соот­вет­ственно, вычи­та­ние обну­лит в такие сла­га­е­мые (в дан­ном слу­чае, с чёт­ными осно­ва­ни­ями сте­пе­ней). Если постро­ить для только что полу­чен­ной суммы мно­же­ство осно­ва­ний , то ока­жется, что равно трём – вто­рому про­стому числу. Про­дол­жая этот про­цесс мы будем на каж­дом шаге полу­чать новое про­стое число и исполь­зо­вать его для обнуления/вычеркивания сла­га­е­мых с осно­ва­ни­ями, крат­ными ему, фак­ти­че­ски, вос­про­из­водя про­се­и­ва­ние Эратосфена/Эйлера без повтор­ных вычер­ки­ва­ний. (Кстати, нетрудно видеть, что здесь мно­же­ство почти пол­но­стью соот­вет­ствует мно­же­ству из разо­бран­ного ранее алго­ритма решета Эйле­ра; разве что в есть лиш­няя еди­ни­ца…)

Даёт ли решето Эйлера уско­ре­ние?

При работе решета Эра­то­сфена тре­бу­ется вычерк­нуть числа, крат­ные всем про­стым . Для каж­дого , оче­видно, тре­бу­ется вычер­ки­ва­ний. Т.о. для оценки коли­че­ства тре­бу­е­мых опе­ра­ций нужно про­сум­ми­ро­вать это выра­же­ние по всем про­стым, не пре­вы­ша­ю­щим , т.е. общее коли­че­ство опе­ра­ций равно . Известно, что . Таким обра­зом, вре­мен­на́я асимп­то­ти­че­ская слож­ность решета Эра­то­сфена оце­ни­ва­ется как , где выпол­няет роль сред­него коли­че­ства раз­лич­ных дели­те­лей числа . (N.B., за -нотацией ещё пря­чется сла­га­е­мое , т.е. время, тре­бу­е­мое на ини­ци­а­ли­за­цию решета постро­е­нием мно­же­ства .)

Эта асимп­то­ти­че­ская оценка ста­но­вится линей­ной (если вычер­ки­ва­ние оче­ред­ного числа выпол­ня­ется за кон­стант­ное время) в слу­чае решета Эйлера. Для языка про­грам­ми­ро­ва­ния с дешё­вым умно­же­нием это поз­во­ляет рабо­тать решету Эйлера быст­рее (в тео­рии): про­тив .

Но sed, оче­видно, не отно­сится к таким язы­кам – в нём вообще нет поня­тия умно­же­ния, хотя уни­вер­саль­ность sed и поз­во­ляет реа­ли­зо­вать умно­же­ние низ­ко­уров­не­выми сред­ствами. При­чем, выбран­ный для рас­смат­ри­ва­е­мой здесь реа­ли­за­ции решета Эйлера спо­соб умно­же­ния в унар­ной системе счис­ле­ния, пол­но­стью ниве­ли­рует какие бы то ни было пре­иму­ще­ства этого вари­анта решета.

Фак­ти­че­ски, поли­чив­шийся код рабо­тает даже мед­лен­нее чем исход­ное решето Эра­то­сфена или его наив­ная «эй­ле­ро­фи­ка­ция» заме­ной s/1@/0@/ на /1@/ s/1@/0@/. :) Но раз он всё-таки был напи­сан, то я вклю­чил файл euler-sieve.sed в основ­ной репо­зи­то­рий [2].

Решето Эйлера могло бы быть полез­ным в гипо­те­ти­че­ском слу­чае про­се­и­ва­ния чисел, нахо­дя­щихся не в опе­ра­тив­ной памяти, а на носи­теле типа flash-микросхем, име­ю­щем огра­ни­че­ние на коли­че­ство опе­ра­ций пере­за­писи содер­жи­мого. Кроме этого, можно вооб­ра­зить ситу­а­цию с про­се­и­ва­нием чисел в удалён­ной базе дан­ных: в этом слу­чае решето Эйлера поз­во­ляет мини­ми­зи­ро­вать как вос­хо­дя­щий (для отправки команд записи/вычер­ки­ва­ния), так и нис­хо­дя­щий (для счи­ты­ва­ния содер­жи­мого ячеек решета) сете­вой тра­фик. Нако­нец, при теоретико-игровой интер­пре­та­ции, это решето сго­ди­лось бы при [даже ещё более гипо­те­ти­че­ской] игре типа «мор­ской бой», в кото­рой состо­я­ние поля-решета замас­ки­ро­вано «ту­ма­ном вой­ны» и можно полу­чать лишь про­ре­жен­ные све­де­ния о послед­нем най­ден­ном про­стом числе. При этом нельзя попа­дать в «сво­их» (в про­стые числа) или тра­тить «бо­е­при­па­сы» и время впу­стую в попыт­ках вычер­ки­ва­ния уже вычерк­ну­тых чисел.

Похоже, что у решета Эйлера в реаль­но­сти два при­ме­не­ния – уско­ре­ние гене­ра­ции про­стых чисел при исполь­зо­ва­нии под­хо­дя­щих язы­ков про­грам­ми­ро­ва­ния типа C, и при­ме­не­ние тео­ре­ти­че­ское (для чего, соб­ственно, Эйлер и при­ду­мал это решето [10]), e.g. в зада­чах, тре­бу­ю­щих един­ствен­но­сти каж­дого гене­ри­ру­е­мого реше­том состав­ного числа.

Прин­цип работы euler-sieve.sed

Скрипт euler-sieve.sed полу­чен из sieve.sed исправ­ле­нием несколь­ких стро­чек. Набор вспо­мо­га­тель­ных мар­ке­ров был рас­ши­рен с #@: до #_@:,. Мар­кет # как и преж­де нахо­дится после оче­ред­ного най­ден­ного про­стого числа. Справа от него нахо­дится мар­кер _, отме­ча­ю­щий оче­ред­ной мно­жи­тель, на кото­рый тре­бу­ется умно­жить теку­щее про­стое число (N.B., если _ стоит сразу после #, то это озна­чает, что мно­жи­те­лем будет само про­стое число, т.е. это соот­вет­ствует вычис­ле­нию ). Мар­кер дви­жется вправо и уста­нав­ли­ва­ется на сле­ду­ю­щее невы­черк­ну­тое число.

Для каж­дого поло­же­ния мар­ке­ров # и _, пре­фиксы, состо­я­щие из сим­во­лов, рас­по­ло­жен­ных левее каж­дого из этих мар­ке­ров, копи­ру­ются на две сле­ду­ю­щие за реше­том строки. Из пре­фик­сов уда­ля­ются лиш­ние сим­волы (в т.ч. выше­упо­мя­ну­тые мар­керы), а перед пре­фик­сами добав­ля­ются новые мар­керы: мар­кер : перед пер­вым пре­фик­сом и , – перед вто­рым. Оба пре­фикса, т.о., всего-лишь обо­зна­чают под­ле­жа­щие пере­мно­же­нию числа [в унар­ной системе счис­ле­ния]. Их про­из­ве­де­ние сле­дует вычерк­нуть из решета.

Для этого перед реше­том добав­ля­ется мар­кер @ (N.B., изна­чально левее мар­ке­ров # и _, что кон­тра­сти­рует с выше­опи­сан­ным кодом для решета Эра­то­сфена). Далее, в двух вло­жен­ных цик­лах начи­нают сме­щаться вправо мар­керы : и ,. При­чем мар­кер , сме­ща­ется на одну пози­цию когда мар­кер : дохо­дит до конца его строки. После каж­дого сме­ще­ния мар­кера , мар­кер : реи­ни­ци­а­ли­зи­ру­ется и вновь ока­зы­ва­ется перед своей стро­кой. При каж­дом пере­ме­ще­нии мар­кера : на одну пози­цию, на одну пози­цию вправо сме­ща­ется и мар­кер @. Т.к. на пути мар­кера @ лежат мар­керы # и _ (кото­рых он гаран­ти­ро­ванно обго­нит), то после окон­ча­ния работы обоих цик­лов, т.е. по дости­же­нию мар­ке­ром , конца своей строки, пози­ция @ кор­рек­ти­ру­ется, сме­ща­ясь вправо на две пози­ции.

В конеч­ном счета, мар­кер @ ока­зы­ва­ется сразу после состав­ного числа, рав­ного про­из­ве­де­нию двух выде­лен­ных пре­фик­сов и под­ле­жа­щего вычер­ки­ва­нию. Однако, если в реа­ли­за­ции решета Эра­то­сфена, на ана­ло­гич­ном этапе про­из­во­ди­лось, соб­ственно, вычер­ки­ва­ние коман­дой s/1@/0@/, то сей­час выбран­ный эле­мент решета лишь поме­ча­ется к уда­ле­нию, заме­ня­ясь не на 0, а на x.

После всего этого мы можем перейти к новой ите­ра­ции по пере­ме­ще­нию мар­кера _, отме­ча­ю­щего сле­ду­ю­щий мно­жи­тель для теку­щего про­стого числа (мар­кер _ пере­пры­ги­вает к пер­вому нену­ле­вому сим­волу, лежа­щему пра­вее него). Если же пра­вее мар­кера _ не ока­за­лось ни одного сим­вола 1 или x (т.е. невы­черк­ну­того, хотя, воз­можно, и поме­чен­ного числа), то все поме­чен­ные к уда­ле­нию числа вычер­ки­ва­ются окон­ча­тельно коман­дой s/x/0/g и мы пере­хо­дим к сле­ду­ю­щей ите­ра­ции сме­ще­ния мар­кера #. Если пра­вее от мар­кера # не ока­зы­ва­ется про­стых чисел, т.е. если нет ячеек со зна­че­нием 1, то алго­ритм про­се­и­ва­ния завер­шает свою работу, пере­да­вая управ­ле­ние финаль­ной части скрипта, про­из­во­дя­щей печать резуль­та­тов.

Вычис­ле­ние неко­то­рых теоретико-числовых функ­ций про­се­и­ва­нием

(Этот раз­дел был добав­лен в апреле 2021 г.)

[Моди­фи­ци­ро­ван­ное] решето Эра­то­сфена может при­ме­няться для вычис­ле­ния неко­то­рых функ­ций, хорошо извест­ных в тео­рии чисел. При­меры таких функ­ций [13]:

Ком­плек­та­ция поставки

Ссылки

М.И.Никитин
г.Алматы, сен­тябрь, 2020
(послед­няя правка: апрель, 2021)


метки-категории: про­грам­ми­ро­ва­ние, мате­ма­тика, эзо­те­рика, sed, теория-чисел, trivia, гипотеза-Коллатца, 3x+1, математические-болезни

[ЭЦП (SHA-256, RSA)] (ключ)