Comments 61
436273009..436273291: 282
649580171..649580447: 276
944192807..944193067: 260
Этот комментарий спонсирован PARI/GP, на котором это вычисляется в одну строчку за 20 секунд (из которых предвычисление таблицы занимает 4).
default(primelimit,10^9);q=0;forprime(p=2,10^9,if(p-q>=256,print(q," .. ",p,": ",p-q));q=p)
Плюсую PARI/GP.
Интересная задача, на сжатие диапазонов. На самом деле, с учетом отсутствием смысла хранить 0 (даже с учетом половин расстояния) сразу просится использовать значение 0 как 9й бит к следующему за ним байту. а если обобщать эту идею то использовать что-то вроде того как кодируется UTF (там в каждом приставном байте содержится пул для его дальнешейшего расширения дополнительными приставными)
в частности очень эффективным оказывается то что только коды символов до 127 оказываются однобайтовыми, зато если старший бит 1 то оставшиеся 7 складываются со следующими в одно значение - таким образом получается резкий скачек на 15-ти битные значения. (в приложении к данной задаче это скорее оверхед) Но можно сделать гораздо интереснее числа >= 256 - 2**n интерперетировать как n бит должны присоединиться к следующему байту
для n=4 пример: Числа большие либо равные 256-2**4 = 240 // что в 2-чной b11110000 т.е.
- с одной стороны мы совсем не много потеряли в доступном однобайтовом диапазоне 240/256 составляют 93.75% байтового диапазона
- с другой стороны вторым байтом мы опционально получаем 12-ти битную индексацию вплоть до 4095 что составляет 4095/256 = 1600% однобайтного
n - выбирается эмпирически. еще n может быть динамическим в зависимости от текущего числа (оно будет строго расти, потому можно апроксимировать простым правилом сколько старших значений должны резервироваться под "расширяющие" биты)
- с ростом n все сильнее будет срезаться однобайтовый диапазон. Но он всегда необходим для записи простых близнецов которые похоже попадаются бесконечно
в общем случае для всех последующих байт рекурсивно подходит та же конструкция расширения бит.
в свое время я реализовал алгебраическую систему с переменным основанием разряда и предикативным косвенным значением его максимума - это пожалуй апогей сжатия таким способом, но он ресурсоемок т.к. требует деления длинной арифметики
пример без длинной арифметики тут: https://codepen.io/impfromliga/pen/xxggRoq
- любители питона могут дешево интегрировать идею имя встроенную длинную арифметику (для объемных данных все равно придется числа дробить, иначе даже питон не справиться)
Вообще интересный вектор анализа простых чисел задает эта задача.
Есть ли какая-то неравномерность вероятности появления конкретных диапазонов?
Полученное число будет иметь остаток от деления на любое из первых 14 миллионов простых чисел, равное 2.
То есть надо проверять делимость на числа после 14-миллионного простого числа, хотя до корня их гораздо больше, чем 14 миллионов.
Хочу составить простое число размером в 100 миллионов разрядов и выиграть 150 тысяч долларов :) EFF Cooperative Computing Awards. Вот только как доказать, что оно простое, за разумное время?
Мой вам совет: не переводите время попусту.
И зачем вам для этой задачи извращения с компактным хранением? 14 миллионов чисел, даже если взять 128 бит на число, будут занимать 224 мегабайта.
Вопрос собственно только в том зачем вы вообще собрались хранить простые числа в БД да ещё в виде расстояний если единственное для чего они нужны — это получить их произведение???
Не проще ли вычислять простое число и сразу умножать его на предыдущие? И хранить нигде не надо будет.
2) Нет, остаток от деления на 2 будет равен 0, поэтому число будет составным.
2*3*5*7+2 делится на 2.
Ваша идея, по всей вероятности, основана на известном доказательстве того факта, что простых чисел бесконечно много.
Дескать, допустим, что простых чисел конечное количество, перемножим их все, прибавим 1, и получим число, которое не делится ни на одно из известных простых чисел, значит есть бОльшее простое число.
Так вот, это доказательство не является конструктивным. Оно не говорит, какое именно простое число болшее перемноженных существует.
Как уже написали, не переводите время попусту. Потратьте его лучше на углубленное изучение теории чисел.
1) P+1, где в P используется множитель 2.
2) P+2, где в P НЕ используется множитель 2.
Доказательство Евклида вполне логичное. Сначала он принял на веру некоторое утверждение, затем пришел к противоречию, вывод: изначальное утверждение неверно. Знать значение простого числа в качестве делителя для каждого (произведения плюс 1) нет смысла: достаточно знать, что оно существует, что и показано Евклидом.
Смотрите, считаем произведение в Maxima:
(%i1) 3*5*7*11*13*17*19*23*29*31*37*41*43*47*53*59*61*67*71+2;
(%o1) 278970415063349480483707697
Видим, что последняя цифра не 5.
Теперь факторизуем:
(%i2) factor(3*5*7*11*13*17*19*23*29*31*37*41*43*47*53*59*61*67*71+2);
(%o2) 107 229 2832808637 4019033726627
и вуаля, число распадается на простые множители, которых не было в исходном ряде и которые превышают любое из чисел исходного ряда.
Так что для проверки простоты 100M значного числа вам нужно его факторизовать. Вы к этому готовы?
700к считает примерно за секунду (нужно взять хотя бы 15М чисел).
20М считает секунд за 40.
Вывод не считаю, поскольку не зависит от алгоритма. Зачем решето Аткина?
смелое предположение.
У меня просто аналогичная задача стояла лет 10 назад, ещё во времена модемного интернета. Хотелось насчитать праймов для хэш-таблички. Компы были ещё 32-битными, так что больше было не нужно. Я просто взял блоб на 512Мб и считал в нём битмаской (как раз 2^32 бит) решетом Эратосфена. На 1-поточном 32-битном 4-м пентиуме это заняло какое-то незначительное время (то ли 5, то ли 10 минут, а возможно и намного меньше — за давностью лет уже не помню). Но никак не 3 часа! Потому собственно, и удивляюсь: как так?
(ЗЫ: по итогу мне нужны были не все числа, а с шагом увеличения в 1.2 раза. Потому вывод дополнительно проредил и табличку потом поместил в код хэша)
Вы, наверное, хотели сказать, что 282 (вдвое большее) — максимальное возможное расстояние.
Первое число, для которого половина расстояния между простыми перестанет помещаться в один байт, — это 304 599 508 537. Его разность со следующим простым равна 514.
По-английски это называется prime gap и им посвящена обширная литература. Можно начать хотя бы с википедии.
Я намекаю на то, что чем более хитрый алгоритм сжатия вы придумаете (с разделение на файлы, с хранением разных видов разностей, разделенных на что-нибудь), тем дольше будет длиться распаковка вплоть до того момента, когда она сравняется с вычислением простых чисел на лету при помощи primegen.
Интересно, как же решето получилось так неудачно?
Код на питончике
def eratosthenes(N):
simp, nonsimp = [2], set()
for i in range(3, N + 1, 2):
if i not in nonsimp:
nonsimp |= {j for j in range(3 * i, N + 1, 2 * i)}
simp.append(i)
return simp
print(len(eratosthenes(11 * 10 ** 6)))
делает 726517 простых секунд за 30.
В питоне операции с множествами имеют O(1).
N^2 на 11 миллионах замер бы навсегда…
Вы уверены, что после |= не приходится реаллоцировать все множество на новое место в памяти или что-нибудь в этом роде?
nonsimp = nonsimp | {j for j in range(3 * i, N + 1, 2 * i)}
то было бы O(сумма элементов там и там)
Но мы подливаем в существующее множество, поэтому имеем O(количество добавляемых элементов)
По мелочи:
— вычёркивание, начинающееся с
3 * i
, теряет кучу времени, повторяя заведомо уже вычеркнутое до i * i
,— после того, как
i
достигнет корня из N
, операция |=
вообще молотит вхолостую.import array
def eratosthenes(N):
# nonsimp[j] corresponds to 2j+1
simp, nonsimp = [2], array.array('b', [0 for i in xrange((N + 1) // 2)])
i = 3
while i * i <= N:
if not nonsimp[(i - 1) // 2]:
for j in xrange((i * i - 1) // 2, (N + 1) // 2, i):
nonsimp[j] = 1
simp.append(i)
i += 2
simp.extend([2 * j + 1 for j in xrange((i - 1) // 2, (N + 1) // 2) if not nonsimp[j]])
return simp
print(len(eratosthenes(11 * 10 ** 6)))
константы очень сильно разные
У меня для вас плохие новости: ваш алгоритм быстрее всего в два раза…
А после оптимизаций аналогичных вашим на моём компе 2.9с к 2.3с.
В целом вы правы, константы разные, но прикол в том, что в питоне множества и словари молниеносно быстры, в то время как обычная арифметика не так, чтобы летает (по сравнению с сями). Поэтому если цель сделать что-то быстро, то нет нужды заморачиваться.
А после оптимизаций аналогичных вашим на моём компе 2.9с к 2.3с.
А покажите версию после оптимизаций, интересно.
def eratosthenes(N):
simp, nonsimp = [2, 3], {i for i in range(3, N + 1, 3)}
for i in range(5, int(N ** 0.5), 2):
if i not in nonsimp:
nonsimp |= {j for j in range(i * i, N + 1, 2 * i)}
simp.append(i)
simp.extend([j for j in range(i, N + 1, 2) if j not in nonsimp])
return simp
print(len(eratosthenes(11 * 10 ** 6)))
Там есть ещё возможности оптимизировать, но они дурацкие и дают малый прирост.
То есть разработчик не связан какой-то тучей программ, программа выдала результат, он сохранил результат и забыл про нее, сосредоточился на дальнейшем решении задач. И вот так, маленькими шажками, как маленький ребенок :)
Вообще интересно построить какие-нибудь графики по этим данным… например чтобы увидеть, как количество простых чисел уменьшается с увеличением x…
y=количество_простых_чисел_до(x)
по идее должно быть что-то типа sqrt(x)
ну и дальше поиграть с масштабами, чтобы понять что за кривая.
График количества делителей (правда с учетом единицы и самого числа).
Автор открыл для себя дельта-кодирование?
2/3 текста занимает листинг программы, считающей (обожемой) статистику по расстоянию между соседними простыми числами. Зачем он? Кому он может оказаться полезен?
В чем состоит тайна простых чисел, анонсированная в первой строке? В том, что они… хммм… простые?
И, наконец, при чем тут вообще Big Data?
Детский сад какой-то, чесслово.
Тогда встал вопрос, как сохранить 14 миллионов простых чисел?
Можно же использовать длинную арифметику. Это же проще, поддерживается в ряде языков (Java, .Net). Да, вычисления могут быть чуть дольше. Зато код сократится.
gmplib.org/
github.com/libtom/libtommath
github.com/infusion/PHP/tree/master/ext/bcmath/libbcmath (хоть и лежит в дистрибутиве php, вполне самостоятельна)
Я простыми числами интересовался лет 10 назад (на совершенно дилетантском уровне), когда у меня был Celeron-667 со 128 метрами памяти — и именно память меня лимитировала, поскольку уход в своп катастрофически снижал скорость. Но если не превышать, все считалось быстро.
Ну в общем, я считал решето Эратосфена, в котором каждое число было битом, причем за пропуском четных, как заведомо не простых. То есть число не кодировалось непосредственно, а задавалось позицией в массиве. В каждом байте таким образом содержалось 16 чисел, в каждом int — 64 числа. И бегал я по этому массиву Битовыми масками.
Точную производительность уже не помню, есессно, но ни про какие 3 часа речи не шло. Несколько минут может. Это на той технике.
Fast C/C++ prime number generator — и код самой primesieve; реализация шикарная, полированная, ибо человек лет 15 серьезно этим вопросом занимается. Работает primesieve с сумасшедшей производительностью даже на настольном скромном тазике, а подробности реализации сегментированного решета и wheel факторизации полностью документированы. Однако, он явным образом пишет, что «primesieve can generate primes and prime k-tuplets up to 2^64» и всё, край географии. Интересно, почему. Наверное он просто такой задачи себе не ставил, сделать больше.
База данных простых чисел