회문 압축


15

도전

ASCII 텍스트를 무손실로 압축 및 압축 해제하는 프로그램을 작성하십시오. 대소 문자를 구분하지 않고 문장 부호를 구분하지 않는 회문을 포함하여 회문과 잘 작동하도록 전문화해야합니다. 가장 작은 소스로 최상의 압축이 이깁니다.

채점

total_bytes_saved / sqrt(program_size) -최고 점수 승

total_bytes_saved압축 된 문자열이 원본보다 몇 바이트 더 작은 지, 아래 테스트 사례에서 전체입니다. program_size압축 및 압축 해제 프로그램의 소스 코드 크기 (바이트)입니다. 둘 사이에 공유 된 코드는 한 번만 계산하면됩니다.

예를 들어, 10 개의 테스트 사례가 있고 100 바이트 프로그램이 7 개의 테스트 사례에서 5 바이트를 저장하고 각각 2 개씩 10 개를 저장했지만 마지막 테스트 사례는 2 바이트 더 길면 솔루션의 점수는 5.3입니다. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

테스트 사례

  • tacocat
  • toohottohoot
  • todderasesareddot
  • amanaplanacanalpanama
  • wasitacaroracatisaw?
  • Bob
  • IManAmRegalAGermanAmI
  • DogeeseseeGod
  • A Santa at NASA
  • Go hang a salami! I'm a lasagna hog.

규칙

  1. 표준 허점이 적용됩니다.
  2. 압축은 회문뿐만 아니라 인쇄 가능한 모든 ASCII (바이트 32-126 포함) 텍스트 문자열에서 작동해야합니다. 그러나 실제로 입력을위한 공간을 절약 할 필요는 없습니다.
  3. 출력은 구현 또는 내부 표현에 관계없이 바이트 또는 문자 시퀀스 일 수 있습니다 (예 : 문자열, 목록 및 배열은 모두 공정한 게임입니다). UTF-8로 인코딩하는 경우 문자가 아닌 바이트 수를 계산하십시오. 와이드 코드 (예 : UTF-16 또는 UTF-32)는 사용 가능한 유일한 코드 포인트가 0에서 255 사이가 아닌 한 허용되지 않습니다.
  4. 압축 / 압축 해제 기본 제공은 허용되지 않습니다.

우리 자신의 즐거움을 위해 압축 코드를 소스 코드와 함께 게시하십시오.

업데이트 1 : 더 나은 압축에 더 많은 무게를주고 공격적인 골프에 더 적은 무게를주기 위해 점수가에서 total_bytes_saved / program_size로 변경되었습니다 total_bytes_saved / sqrt(program_size). 그에 따라 점수를 조정하십시오.

업데이트 2 : 고정 wasitacaroraratisaw?wasitacaroracatisaw?


2
입력에서 구두점과 간격이 제거 된 경우 입력이 엄격한 회문이 보장됩니까? 편집 : 신경 끄시 고 - 내가 보는 wasitacaroraratisaw?것과 반증이다
디지털 외상

2
입력에서 지원할 ASCII 문자 범위는 무엇입니까? 그렇 [32-126]습니까?
Arnauld

1
그래, 내가 생각하지 않는 1000 *부분이 정말 필요하고, 더 내가 더 "만족"점수 느끼게됩니다 생각하지 않는다;)
에릭 Outgolfer

1
압축 / 압축 해제 내장 기능을 사용할 수 있습니까?
Lynn

4
입력이 거의 없으므로 영리한 작업을 수행 할 수있는 범위가별로 없습니다. 적어도 몇 배 더 많은 것이 좋을 것입니다.
user1502040

답변:


16

자바 스크립트 (ES6), 3.143 (81 바이트 저장, 664 바이트 프로그램)

R='replace',S=String.fromCharCode,T=c=>c.charCodeAt(),U='toUpperCase',V='0000000',W=(a,b,c=2)=>a.toString(c).slice(b),X=x=>'0b'+x,Y=a=>[...a].reverse().join``,Z=/[^]/g
C=s=>S(...((Y(q=s[U]()[R](/[^A-Z]/g,m=''))==q?(q=q.slice(0,p=-~q.length/2),p%1&&10):11)+q[R](Z,x=>W(T(x),2))+111+s[R](Z,c=>/[a-z]/.test(c)?W("00",m,m=1):m+(/[A-Z]/.test(c,m='')?"01":W(c<'!'?2:T(c)+384)))+V).match(/(?!0+$).{8}/g).map(X))
D=s=>{s=s[R](Z,c=>W(256+T(c),1))+V;M=r=>(s=s[R](p=s.match(`^${r}|`)[0],''),p);for([,a]=M`1.|0`,t=u=i='';!M`111`;)t+=W(X(M`.{5}`)-~8,0,36);for(t+=W(Y(t),a?a/0:1);p;)u+=M`0(?=00)|00?1`?(c=t[i++])?+p[1]?c[U]():c:'':M`10`?' ':M`11`&&S(X(M`.{7}`));return u+W(t,i)}

이 프로그램 (및 채점 시스템)에 상당히 만족 했으므로 약간의 설명을하겠습니다.

기본 아이디어는 입력을 비트 열로 압축 한 다음 8 비트의 각 세트를 바이트로 압축하는 것입니다. 설명을 위해 비트 문자열을 조작하겠습니다.

비트 문자열은 여러 섹션으로 구분 될 수 있습니다.

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

0      | 10100 00001 00011 01111 111 | 01 00001 10 01 0001 110101110
header | letter data                 | styling data

헤더는 매우 간단한 매핑입니다.

0  -> odd-length palindrome
10 -> even-length palindrome
11 -> non-palindrome

문자 데이터도 매우 간단합니다. 먼저 모든 비 문자가 문자열에서 추출되고 모든 문자가 대문자로 변환됩니다. 결과 문자열이 회문이면 반전 된 절반이 제거됩니다. 그런 다음이 매핑이 적용됩니다.

A -> 00001
B -> 00010
C -> 00011
D -> 00100
...
Z -> 11010

이 섹션은로 끝납니다 111. 그 후 대문자 / 소문자 데이터와 비 문자를 저장하는 스타일링 데이터가 제공됩니다. 이것은 다음과 같이 작동합니다.

01 -> next letter as uppercase
0...01 (n 0s) -> next (n-1) letters as lowercase
10 -> space
11xxxxxxx -> character with code point 0bxxxxxxx

위에서 보여준 예를 통해

header: 0 -> palindrome
letter data: 10100 00001 00011 01111 111 -> taco
styling data:
  01        -> T
  00001     -> aco
  10        -> <space>
  01        -> C
  0001      -> at
  110101110 -> .

비트 문자열의 끝에 도달하면 문자 데이터의 나머지 모든 문자가 결과에 추가됩니다. 이렇게하면 마지막 작업을 수행 할 필요 000...001가 없으며 문자열에서이 비트를자를 수 있습니다.

테스트 사례 진행 :

tacocat -> 3 bytes (-4)
    24 bits: 010100000010001101111111
toohottohoot -> 5 bytes (-7)
    35 bits: 10101000111101111010000111110100111
todderasesareddot -> 7 bytes (-10)
    49 bits: 0101000111100100001000010110010000011001100101111
amanaplanacanalpanama -> 8 bytes (-13)
    59 bits: 00000101101000010111000001100000110000001011100000100011111
wasitacaroracatisaw? -> 11 bytes (-9)
    84 bits: 010111000011001101001101000000100011000011001001111111000000000000000000001110111111
Bob -> 2 bytes (-1)
    16 bits: 0000100111111101
IManAmRegalAGermanAmI -> 13 bytes (-8)
    98 bits: 00100101101000010111000001011011001000101001110000101100111010100010100101000001010100000010100101
DogeeseseeGod -> 7 bytes (-6)
    54 bits: 000100011110011100101001011001100101111010000000000101
A Santa at NASA -> 8 bytes (-7)
    63 bits: 100000110011000010111010100000011110110010000011000011001010101
Go hang a salami! I'm a lasagna hog. -> 20 bytes (-16)
   154 bits: 1000111011110100000001011100011100001100110000101100000010110101001111010011000000110001100000000111010000110011101001110011000110000000001100000111010111

와. 나는이 접근법에 깊은 감명을 받았다. 나는 이런 식으로 비트 팩 인코딩을 생각하지 않았을 것입니다. (ASCII를 7 비트로 압축하는 경우를 생각했지만 회문을위한 공간을 많이 절약하지는 못합니다) 공간을 절약 할 수 있다는 것에 깊은 인상을 받았습니다 Bob.
Beefster

4
이것은 공학의 기초에 대한 훌륭한 예입니다. 문제 설명을 취하고, 그것을 해결하는 다른 방법에 대해 생각하고, 요구 사항 (즉, 다양한 스타일에 헌신 할 비트 수) 사이의 균형을
Robert Fraser

@Beefster Thanks :-) Bob실제로 헤더의 경우 1 비트, 두 문자의 경우 10 + 3 비트, 단일 대문자의 경우 2 비트로 떨어졌습니다. 내가 가장 열심히 노력하면 더 짧게 얻을 수 없습니다 ...
ETHproductions

1
@ KevinCruijssen 문제는 추가되는 것이 문자열이므로 먼저 숫자로 변환해야한다는 것입니다. 이 방법은 바이트보다 짧습니다-0+9
ETHproductions

1
@ETHproductions Ah 물론입니다 (문자열임을 알지 못했습니다)! +9문자열겠습니까의 CONCAT, 동안 -~8할 것이다 +9(이후 산술적으로 -는 숫자로 해석하므로, 문자열에 대한 아무것도하지 않습니다). 이 경우 -~8꽤 똑똑합니다. :) 좋은 답변 btw, +1! 바이트를 저장하더라도 모든 정보를 비트 단위로 저장하는 것이 현명합니다 Bob.
Kevin Cruijssen

2

Python 2 : 2.765 (70 바이트 저장, 641 바이트 프로그램)

나는 접근 방식을 조금 바꿨다. 이제는 불완전한 회문에서 잘 작동합니다. 입력보다 긴 압축 문자열이 없습니다. 완벽한 짝수 회문은 항상 원래 크기의 50 %로 압축됩니다.

A=lambda x:chr(x).isalpha()
def c(s):
 r=bytearray(s);q=len(r);L=0;R=q-1;v=lambda:R+1<q and r[R+1]<15
 while L<=R:
  while not A(r[L])and L<R:L+=1
  while not A(r[R])and R:
   if v()and r[R]==32:r[R]=16+r.pop(R+1)
   R-=1
  j=r[L];k=r[R]
  if A(j)*A(k):
   if L!=R and j&31==k&31:
    r[L]+=(j!=k)*64;r[R]=1
    if v():r[R]+=r.pop(R+1)
   else:r[L]|=128;r[R]|=128
  L+=1;R-=1
 while r[-1]<16:r.pop()
 return r
def d(s):
 r='';t=[]
 for o in s:
  if 15<o<32:r+=' ';o-=16
  while 0<o<16:r+=chr(t.pop());o-=1
  if o==0:continue
  if 127<o<192:o-=64;t+=[o^32]
  elif o>192:o-=128
  elif A(o):t+=[o]
  r+=chr(o)
 while t:r+=chr(t.pop())
 return r

결과

'tacocat' <==> 'tac\xef'
4/7 (3 bytes saved)
'toohottohoot' <==> 'toohot'
6/12 (6 bytes saved)
'todderasesareddot' <==> 'todderas\xe5'
9/17 (8 bytes saved)
'amanaplanacanalpanama' <==> 'amanaplana\xe3'
11/21 (10 bytes saved)
'wasitacaroracatisaw?' <==> 'wasita\xe3ar\xef\x09?'
12/20 (8 bytes saved)
'Bob' <==> '\x82\xef'
2/3 (1 bytes saved)
'IManAmRegalAGermanAmI' <==> 'I\x8d\xa1n\x81m\x92e\xa7\xa1\xec'
11/21 (10 bytes saved)
'Dogeeseseegod' <==> '\x84ogees\xe5'
7/13 (6 bytes saved)
'A Santa at NASA' <==> 'A S\xa1\xaeta\x12\x14'
9/15 (6 bytes saved)
"Go hang a salami! I'm a lasagna hog." <==> "\x87o hang a salam\xa9!\x11'\x01\x11\x17\x13."
24/36 (12 bytes saved)

그리고 보너스로, 이전에 가지고 있던 잘못된 회문에 6 바이트를 절약합니다.

'wasita\xe3ar\xef\x02\xf2\x06?' <==> 'wasitacaroraratisaw?'
6 bytes saved

설명

압축 해제는 스택을 사용합니다. 32-127의 코드 포인트는 문자 그대로 처리됩니다. 문자가 문자이면 스택에도 값이 푸시됩니다. 128-192 값은 대소 문자를 뒤집은 문자에 사용되므로 대소 문자를 구분 한 문자 ( o^32ASCII 배치 방법으로 인해)가 스택에 푸시되고 일반 문자가 문자열에 추가됩니다. 값 192-255는 스택을 밀지 않고 문자를 추가하는 데 사용되므로 문자가 일치하지 않을 때와 홀수 길이 회 문의 중간 문자에 사용됩니다. 코드 포인트 1-15는 스택이 해당 횟수만큼 팝되어야 함을 나타냅니다. 코드 포인트 17-31은 비슷하지만 스택에서 튀어 나오기 전에 먼저 공백을 인쇄합니다. 입력이 끝날 때 암시 적 "비어있을 때까지 팝"명령이 있습니다.

컴프레서는 값 1-31과 일치하는 문자로 양쪽 끝과 접힘에서 작동합니다. 비 레터를 건너 뜁니다. 문자가 일치하지만 대소 문자가 일치하지 않으면 왼쪽 문자에 64를 더하고 오른쪽 문자를 증가시킵니다. 이를 통해 공간을 절약 할 수 있습니다 IManAmRegalAGermanAmI. 중간에 또는 글자가 일치하지 않으면 양쪽에 128이됩니다. 특별한 경우를 피해야하기 때문에 거기에 추가 할 수 없습니다 left == right. 오른쪽에 인접한 팝 마커를 접을 때 공백에 대해 필요하기 때문에 인접한 팝 마커가 코드 포인트 16으로 오버플로되지 않는지 확인해야합니다. (실제로는 테스트 케이스 문자열에 문제가되지 않습니다)

편집 1 : 더 이상 ungolfed 버전이 없습니다.


1

Python3, 1.833 (25 바이트 저장, 186 바이트 프로그램)

단순한 0 차 등 확률 엔트로피 코딩. 회문 별 최적화가 없습니다.

def C(s):
    u=0
    for c in s:u=u*96+ord(c)-31
    return u.to_bytes((u.bit_length()+7)//8,'big')
def D(a):
    u,s=int.from_bytes(a,'big'),''
    while u:s,u=s+chr((u%96)+31),u//96
    return s[::-1]

0

Java 8, 점수 : 1.355 (20 바이트 절약 / 218 (107 + 111) 바이트)

압축 기능 (인쇄 할 수없는 ASCII 문자 3 개 포함) :

s->{int l=s.length();return s.contains(new StringBuffer(s).reverse())?s.substring(l/2)+(l%2<1?"":""):s;}

압축 해제 기능 (인쇄 할 수없는 두 개의 ASCII 문자 포함) :

s->{return s.contains("")?new StringBuffer((s=s.replaceAll("","")).substring(s.length()&1^1)).reverse()+s:s;}

설명:

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

완벽한 회문만을 압축합니다.

s->{                      // Method with String as both parameter and return-type
  int l=s.length();       //  Get the length of the input
  return s.contains(new StringBuffer(s).reverse())?
                          //  If the input is a palindrome:
    s.substring(l/2)      //   Only return the second halve of the String
    +(l%2<1?"":"")        //   + either one (if even) or two (if odd) unprintables 
   :                      //  Else:
    s;}                   //   Simply return the input again

s->{                      // Method with String as both parameter and return-type
  return s.contains("")?  //  If the input contains an unprintable:
    new StringBuffer((s=s.replaceAll("",""))
                          //   Remove the unprintables
                     .substring(s.length()&1^1))
                          //   And take either the full string (if even),
                          //   or minus the first character (if odd)
    .reverse()            //    And reverse that part
    +s                    //   And append the rest of the input (minus the unprintables)
   :                      //  Else:
    s;}                   //   Simply return the input again
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.