Ця відповідь є частиною співпраці між собою та 0 '. Ми обидва працювали над цим разом, єдина причина, по якій я публікую це, це тому, що я виграв рок, папір, ножиці.
\Q-->{Q=1};"(",\N,")",\B,{findnsols(N,I,(between(2,inf,I),\+ (between(3,I,U),0=:=I mod(U-1))),L)->append(_,[Y],L),Q is Y*B}.
Спробуйте в Інтернеті!
Пояснення
Ця відповідь є прекрасним прикладом того, що робить гольф у пролозі цікавим.
У цій відповіді використовується потужна система Prologs для певних граматик пункту. Ось наша граматика трохи неперевершена.
head(1)-->[].
head(Q)-->"(",head(N),")",head(B),{prime(N,Y),Q is Y*B}.
isprime(I):- \+ (between(3,I,U),0 =:= I mod(U-1)).
prime(N,Y):-
findnsols(N,I,(
between(2,inf,I),
isprime(I)
),L),
append(_,[Y],L),!.
Перше правило будівництва:
head(1)-->[].
Це говорить Прологу, що порожній рядок відповідає 1.
Наше друге правило будівництва - це трохи складніше.
head(Q)-->"(",head(N),")",head(B),{prime(N,Y),Q is Y*B}.
Це говорить нам про те, що будь-який не порожній рядок містить круглі дужки навколо пропозиції з цими ж правилами, праворуч від пропозиції з цими ж правилами.
Це також говорить нам, що значення цього пункту ( Q) відповідає правилу:
{prime(N,Y),Q is Y*B}
Розбиваючи це, Qдобуток 2 чисел Yі B. B- просто значення пункту зліва і Yє головним Nчислом, де Nє значенням пропозиції в дужках.
Це правило охоплює обидва правила формування факторного дерева
- Конкатенація множиться
- Додаток займає п яте місце
Тепер для визначення предикатів. У версії unololfed є два предикати (у моєму фактичному коді я вперед прикував присудки). Два релевантні предикати тут є isprime/1, що відповідає простому числу, і prime/2, що, з урахуванням Nі Y, відповідає iff Y- Nго простого числа. Спочатку маємо
isprime(I):- \+ (between(3,I,U),0 =:= I mod(U-1)).
Це працює з досить стандартним визначенням первинності, ми наполягаємо, що немає числа між 2 і I, включаючи 2, але не Iте, що розділяє I.
Наступний присудок також досить простий
prime(N,Y):-
findnsols(N,I,(
between(2,inf,I),
isprime(I)
),L),
append(_,[Y],L),!.
Ми використовуємо findnsolsдля пошуку перших Nчисел, які є простими, потім повертаємо останні. Хитрість тут полягає в тому, що, хоча findnsolsне гарантовано знайти найменші N праймери, через те, що SWI поводиться, betweenвін завжди знайде менші праймери раніше. Це, однак, означає, що ми повинні вирізати, щоб запобігти знаходженню більшої кількості праймів.
Гольфи
Ми можемо переслати причину в нашому коді двічі. Оскільки isprimeвикористовується лише після того, як його визначення можна перемістити всередину prime. Наступним є переміщення primeбезпосередньо всередині DCG, однак, оскільки ми використовуємо надріз primeдля запобігання появи findnsolsзанадто багато праймерів, у нас є проблема. Розріз, скорочує весь DCG замість того, щоб ми хотіли трохи. Після трохи копання документації ми виявили, що once/1можна було б використати для скорочення саме цієї частини, але не всієї DCG. Однак більше розкопок документації показало, що ->оператор також може бути використаний для виконання подібного завдання. ->Оператор приблизно еквівалентний ,!,тому ми перемістили наш розріз на іншу сторону append/3і замінити його ->.
У SWI-Prolog предикатам (і правилам) можна вказати операторів як імена, що дозволяє нам скидати круглі дужки, як правило, потрібні. Тим самим ми можемо зберегти 6 байт, викликавши правило \.