Ця відповідь є частиною співпраці між собою та 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 байт, викликавши правило \
.