프롤로그 골프를위한 팁


16

프롤로그 골프에 대한 일반적인 팁은 무엇입니까? 저는 일반적으로 프롤로그에 다소 특정한 코드 골프 문제에 적용될 수있는 아이디어를 찾고 있습니다 (예 : 하나의 문자 변수는 프로그램의 크기를 줄이기 위해 프롤로그에만 국한되지 않습니다).

Prolog 구현에 고유 한 경우 팁에 표시하십시오 (예 : SWI-Prolog 특정 내장)

답변 당 하나의 팁 또는 동일한 기본 아이디어와 밀접한 관련이있는 팁 목록을 게시하십시오.


1
prolog태그는 좀 쓸모가 없다. Interpret Prolog 챌린지가 없으면 필요하지 않습니다.
cat

답변:


10

술어 이름에 연산자 사용

연산자가 사전 정의 된 연산자 중 하나이고 ( 여기에 나열 됨 ) 술어로 아직 정의되지 않은 한 술어 연산자를 이름으로 지정할 수 있습니다. 연산자 술어는 일반적인 name(arg1,arg2,etc..)형식 으로 작성 될 필요가없고 연산자로 예상되는대로 호출 될 수 있으므로 술어를 정의하고 호출 할 때 몇 바이트가 절약됩니다 .

하나와 두 개의 인수 술어의 경우 각각 단항 연산자와 이항 연산자의 이름을 지정할 수 있습니다. 더 높은 arity 술어의 경우 패턴 일치를 사용하여 괄호를 피할 수 있습니다. 예를 들어, 술어가 있으면 A+B+C:-...Prolog는 연산자 우선 순위와 연관성 규칙 을 사용 (A+B)+C:-...하여 첫 번째 인수와 패턴이 일치하는 연산자 술어 로 변환합니다 A+B. 아니면 A-B+C*D:-...하게되는 (A-B)+(C*D)첫 번째 인자는 일치 패턴이되도록 A-B그 제는 패턴이 일치한다 C*D.

_+_+_.
A-B+C*D:-between(A,B,C),C+D.
\X:-X>1,X<10.
X+Y:-length(Y,X),member(X,Y).



5+[10,5,3,2,5],a+b+c,0-20+X*[2,4,6,5,40],\9.

출력은 X = 5.

온라인으로 사용해보십시오!

추론

DCG 는 술어에 대한 구문 설탕 이므로 이름에 대한 연산자도 제공 될 수 있습니다. 이것은 DCG에서 DCG로 호출하거나 DCG phrase와 함께 작동하도록 설계된 술어 또는 다른 술어를 사용하여 DCG로 호출 할 때 예상대로 작동합니다 . DCG 술어가 차이 목록에 대해 두 개의 추가 인수를 취 하므로 술어로이를 호출 할 때 괄호가 필요합니다 (예 : A+B-->...처럼 호출해야 함 +(A,B,...)). 연산자 패턴 일치를 사용하는 인수가 두 개 이상인 DCGs 연산자의 경우 패턴 일치 연산자가 올바르게 분배되는지를 술어로 호출 할 때 중요합니다.

추가 인수를 취하지 않는 DCG에 운영자 이름을 제공하면 프로그램 내에서 호출해야하는 경우 괄호를 사용하지 않고도 연산자 이름을 호출 할 수 있습니다. 괄호 안에 저장 한 내용이 인접한 연산자를 구문 분석하는 데 필요한 간격을 잃을 수 있으므로주의가 필요합니다.

/ -->a+b+X,X+d+e.
A+B+C-->[A],[B],[C].


X/[],member(c,X),phrase(f+o+o,Y),+(b+a,r,Z,[]).

출력은

X = [a, b, c, c, d, e],
Y = [f, o, o],
Z = [b, a, r].

온라인으로 사용해보십시오!

경고

단항 연산자 +및을 사용 -하면 Prolog는 또는 술어 에 대한 호출 대신 +20또는 -20숫자로 해석 합니다. 단항 또는 이름으로 제공된 술어 는 여전히 괄호 ( , )를 사용하여 숫자로 호출 될 수 있습니다 . 괄호에서 추가 바이트를 방지하는 등의 바람직한 다른 단항 연산자 인 경우 , 등 이름 대신 사용할 수 있습니다.+/1-/1+-+(20)-(20)\$

패턴 일치와 연산자 이름 술어의 조합이 완전히 결함이있는 것은 아닙니다. 이름과 연산자가 같고 패턴이 일치하는 두 술어가 다른 술어보다 일반적으로 더 일반적인 경우, 더 일반적인 것이 먼저 호출되거나 덜 일반적인 것은 실패합니다 (소스에서의 순서에 따라) . 예를 들어, 위의 예에서 A-B+C*D입력과 일치하지 않으면 Prolog가 호출을 시도 X+Y합니다. 때문에 오류가 발생합니다 length/2필요 Y는 형태가 될 것이기 때문에이되지 않습니다 정수로 C*D. 두 술어에 이름과 같은 연산자가 없는지 확인하거나 실패한 경우 컷을 사용하고 소스를 신중하게 순서대로 정렬하면이를 방지 할 수 있습니다.


3
이 방법이 코드 골프에 얼마나 유용한 지 놀랍습니다. 이 팁만으로 답의 바이트 수를 16 % 줄였습니다!
DLosc

6

가능한 모든 사례를 하나의 규칙으로 작성하십시오.

Prolog에서 프로그램을 작성하는 확실한 방법은 동일한 술어에 대해 여러 규칙을 선언하는 것입니다. 예를 들어, 누산기로 목록을 뒤집는 조건자는 다음과 같습니다.

r([],Z,Z).
r([H|T],Z,R):-r(T,[H|Z],R).

Code-golf에서 첫 번째 규칙을 제거 ;하고 두 번째 규칙 끝에 a 를 추가 하여 재귀 끝을 코딩 할 수 있습니다.

r([H|T],Z,R):-r(T,[H|Z],R);R=[H|Z].

우리는 첫 번째 조건을 알고 r(T,[H|Z],R)T가 비어있는 경우, 즉 재귀를 종료해야하는 경우 이 실패 있으므로 그 뒤에 종료 또는 절로 종료를 추가 할 수 있습니다.

동일한 원칙이 많은 상황에서 작동합니다. 그러나 때로는이 작업을 수행하는 대신 다른 규칙을 선언하는 것이 실제로 더 짧은 경우도 있습니다.


6

자주 유용한 한 가지 트릭 : CLP (FD) 제약 조건 사용 정수 산술에 을 사용하여 여러 방향으로 자동으로 사용될 수있는 조건자를 확보하여 조건 및 전용 분기 및 변형을 피하십시오.

라이브러리를로드 할 필요없이 바로 사용할 수있는 B-Prolog 또는 GNU Prolog를 사용하십시오.


2
이것은 항상 도전을 할 때 SWI-Prolog를 싫어하는 것입니다. CLPFD를 사용하면 더 깨끗하고 짧게 만들 수 있지만, 일반적으로 가치가 없어서 작동하도록하려면 추가 코드를 많이 추가해야합니다 (포함 자체가 많이 있습니다 ...). 나는 이 답변 에서만 그것을 사용했다고 생각 합니다.
Fatalize

3
나는 그것을 싫어하고, 결국 SWI-Prolog에서도 사전로드 되거나 최소한 자동로드 된 라이브러리 library(clpfd)로 사용할 수 있는 시간이 올 것 입니다. 구식의 저수준 기능에 대한 수십 년간의 경험을 쌓은 모든 사용자가 선언적 산술을 완전히 이해하고 이해하는 데 몇 년이 걸릴 수 있습니다. CLP (FD)를 많이 사용하고 옹호할수록 기본적으로 더 빨리 얻을 수 있습니다. 그때까지는 SWI-Prolog의 "변이체"를 사용하고 있다고 간단히 말하면 됩니다 . :- use_module(library(clpfd)).~/.swiplrc
mat

6

산술 연산자를 튜플 생성자와 단점 쌍으로 사용

두 개 이상의 값으로 구성된 단일 구조를 전달해야하는 경우 가장 분명한 것은 목록입니다. [A,B] . 그래도 정말 장황하다.

대안이 있습니다. 프롤로그 값은 평가되지 않은 임의의 중첩 구조를 저장할 수 있습니다. 작동 방식을 보여주는 예는 다음과 같습니다.

| ?- member(member(A,B),C).
C = [member(A,B)|_] ? ;
C = [_,member(A,B)|_] ? ;
(etc.)

member(A,B) 이 상황에서 그냥 명명 된 튜플입니다. member (함수 호출)는이를 처리합니다.

명명 된 튜플은 골프가 아닌 프롤로그 프로그래밍에 상당히 유용하지만 목록 방식보다 더 장황하게 보일 수 있습니다. 그러나 튜플 생성자의 이름에는 거의 임의의 문자를 사용할 수 있습니다 (올바르게 인용되어 있다고 가정). 귀여운 member캐릭터 나 단일 캐릭터 대신 a다음과 같이 할 수 있습니다.

| ?- A = '-'('/'(1,2), '/'(3,4)).
A = 1/2-3/4

여기에서 튜플 생성자는 '-''/'입니다. 그리고 예쁜 프린터가 그들과 함께 한 일을 주목하는 것은 흥미 롭습니다. 그것은 사용의 중위 튜플에 대한 표기를. 이것은 실제로 간결하며 비슷한 산술 연산과 같은 방식으로 구문 분석합니다. (이것은 또한 산술이 사용 is하지 않는 이유를 설명합니다 =. tupleA = 1+2통합 되므로 실제로 평가되지 않은 산술 표현식을 평가하려면 별도의 구문이 필요합니다.) 튜플 생성자는 무언가를 호출해야하기 때문에 간결한 문자를 사용할 수도 있습니다 구문 (그리고 보너스로, 그리고A '+'(1,2)-/i루프 변수로 자주 사용되는 것과 같은 방식으로 의미있는 것보다는 빠른 튜 어웨이 튜플 생성자를 원할 때 골프가 아닌 코드에서 가장 일반적인 선택입니다 . 따라서 사용 하는 것이 합리적입니다. 어떤 이유로 튜플을 원한다면 입력 및 출력).

'-'그리고 '/'그들은 간결하게 쓰기 튜플 리터럴에 당신을 수 있도록 잘 행동하고 유용한 우선 순위를 가지고 있기 때문에 튜플의 생성자를위한 좋은 선택이다. 그러나 프로그램 내에서 중간 값이 생성 될 때 우선 순위에 대해 걱정할 필요는 없습니다. 프롤로그는 튜플을 소스 코드가 아닌 트리로 저장하며, 예쁜 프린터는이를 명확하게 출력 할 수 있습니다.

| ?- A = '-'('-'(1,2), '-'(3,4)).
A = 1-2-(3-4)

튜플 구문이 너무 간결하기 때문에 ( f(A,B)보다 짧지 않음 f(A-B)) 여러 선언문 인수를 무료로 튜플로 대체 할 수 있습니다. 즉, 술어가 두 개 이상의 인수를 다른 술어에 전달해야하는 경우 종종 다음과 같이 구성 할 수 있습니다. 튜플과 튜플을 전달하십시오 (튜플 생성자와 쉼표의 적절한 조합을 사용하려면 술어 자체뿐만 아니라 모든 호출을 술어에 변경해야하지만).

이 구문의 또 다른 장점은 표준 술어와 상호 운용하지 않고 내부적으로 목록을 사용해야하는 경우입니다. 목록은 기본적으로 중첩 된 죄수 셀 집합이며, 죄수 셀은 '.'여기에서 볼 수 있듯이 constructor가있는 튜플입니다 .

| ?- Q = '.'('.'(A,B),'.'(C,D)).
Q = [[A|B],C|D]

코드에서 "수동으로"목록을 사용하는 경우보다 적은 튜플 생성자를 사용하는 것이 좋습니다 '.'. 나를위한 일반적인 선택은 죄수 셀을 다음과 같이 나타내는 것입니다 '/'(Tail,Head)(문자를 낭비하지 않고 디버그 출력에서 ​​얻을 수있는 가장 읽기 쉬운 것이기 때문입니다). 당신은 아마 당신 자신의 []동등 물을 원할 것입니다 ; 당신 사용할 있습니다[] 하지만 긴 2 바이트를, 그리고 대신 사용할 수있는 1 바이트 원자 (모든 소문자 문자)의 많음이있다.

예를 들어 다음 목록은

[1,2,3]

다음과 같은 문자 수로 수동 표현으로 변환 할 수 있습니다.

x/3/2/1

[H|T]스타일 패턴 일치를보다 간결하게 작성할 수 있는 장점을 얻는 반면 T/H빈 목록에 대한 테스트 x는 더 이상이 아닌 더 길다 []. (물론,이 것이 명백한 단점과 함께 제공 member, append등이 표현에서 작동하지 않습니다.)


+1! 사용하여 작은 따옴표가 필요 없다 -하고 /. 이것들은 이미 완전히 정상적인 원자입니다.
mat

.다음 문자가 %레이아웃도 아니더라도 작은 따옴표가 필요 하지 않습니다.
false

5

리스트리스트를위한 짧은 문법과 맵을 선언하는 방법

목록 목록에 바이트를 저장할 수 있습니다. 리스트가 있다면 [[1,2],[3,4]], 실제로 이것을로 선언 [1:2,3:4]하면 4 개의 괄호 = 4 바이트를 절약 할 수 있습니다 . 이외의 다른 것을 사용할 수 있습니다 :(예 :) ^.

1:2이 경우 실제로 목록은 아니지만 [1,2]내부적으로로 표시됩니다 :(1,2). 따라서 콜론을 사용하는 서브리스트의리스트에서 작동하는 술어를 사용할 수 없습니다.

이 트릭은 주로 맵, 즉 값이 첨부 된 키 목록을 선언하는 데 사용됩니다. 예를 들어 M영어와 프랑스어로 숫자 철자가 포함 된지도를 선언 하려면 다음과 같이 할 수 있습니다.

M=[0:'Zero':'Zéro',1:'One':'Un',2:'Two':'Deux', ... ]

그런 다음 예와 같은 내장 술어를 사용하여 맵의 요소를 검색 할 수 있습니다 member/2. 예를 들어 'Quatre'in에 해당하는 숫자와 영어 단어를 원하는 경우 다음을 M수행 할 수 있습니다.

member(Digit:Name:'Quatre',M).

1
이것을 일반화 할 수 있습니다. 예를 들어,을 wirte 할 수 [[1,2],[3,4]]있으며 1*2+3*4, +(*(1,2),*(3,4))따라서 괄호를 열고 닫는 데 2 ​​바이트가 필요한 1 바이트 만 사용할 수 있습니다.
mat

큰 따옴표 목록은 더 짧습니다. "abc"대신[a,b,c]
false

5

깔끔한 트릭 : 실패 해야 할 때는 false / 0 과 동일  하지만 더 짧은 것을 사용하십시오.

π- 반복, writeln (hi), 0 = 1 .

3
의무적 프롤로그 기술 인용문 : " 프롤로그 프로그래밍 (일반적으로 삶과 대조적으로)에서 우리의 목표는 가능한 빨리 실패하는 것입니다 ." 재미있는 사실 : 당신은 또한 사용할 수있는 \+!3 실제로 컷 트리거하지 않는 실패 이상한 방법 바이트로 !(볼 에 대한 이유 ). 3 바이트 미만으로 실패 할 수 있다고 생각하지 않습니다.
Fatalize

나는 또한 이것을 쓸 때 그 인용구에 대해 생각하고 있었다 ;-)
mat

2
우리는 두 가지 버전이 필요합니다 \+!. 왼쪽에는 다른 그래픽 캐릭터에 0=1붙이고 왼쪽에는 접착제로 이름을 붙입니다.
false

5

다른 호출 모드로 술어 재사용

예를 들어, 동일한 인수를 사용하여 변수 인수로 한 번, 접지 용어로 다른 시간으로 구조를 구문 분석하고 인쇄 할 수 있습니다. 나는 신축성 뱀 키스 만들기 에서이 접근법을 사용했습니다 . 물론 모든 도전에서 가능하지는 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.