Nén Palindrom


15

Thử thách

Viết chương trình nén và giải nén văn bản ASCII một cách dễ dàng. Nó nên được chuyên môn hóa để làm việc tốt với palindromes, bao gồm cả palindromes không nhạy cảm trường hợp và dấu chấm câu. Nén tốt nhất với nguồn nhỏ nhất sẽ thắng.

Chấm điểm

total_bytes_saved / sqrt(program_size) - Điểm số cao nhất chiến thắng

total_bytes_savedlà các chuỗi nén nhỏ hơn bao nhiêu byte so với gốc, tổng số trong các trường hợp thử nghiệm bên dưới. program_sizelà kích thước tính bằng byte của mã nguồn của cả chương trình nén và giải nén. Mã được chia sẻ giữa hai chỉ cần được tính một lần.

Chẳng hạn, nếu có 10 trường hợp thử nghiệm và chương trình 100 byte lưu 5 byte trên 7 trường hợp thử nghiệm, 10 trường hợp trên 2 trong số đó, nhưng trường hợp thử nghiệm cuối cùng dài hơn 2 byte, giải pháp sẽ đạt điểm 5.3. ( (7 * 5 + 10 * 2 - 2) / sqrt(100) = 5.3)

Các trường hợp thử nghiệm

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

Quy tắc

  1. Tiêu chuẩn áp dụng.
  2. Việc nén phải hoạt động trên tất cả các chuỗi văn bản ASCII (byte 32-126, đã bao gồm) có thể in được, không chỉ các palindromes. Tuy nhiên, nó không thực sự phải tiết kiệm không gian cho bất kỳ đầu vào nào.
  3. Đầu ra có thể là bất kỳ chuỗi byte hoặc ký tự nào, bất kể việc triển khai hay biểu diễn bên trong của nó (chuỗi, danh sách và mảng đều là trò chơi công bằng, chẳng hạn). Nếu mã hóa thành UTF-8, hãy đếm byte, không phải ký tự. Các chuỗi rộng (ví dụ UTF-16 hoặc UTF-32) không được phép trừ khi các loại tiền mã hóa duy nhất có thể được sử dụng nằm trong khoảng từ 0 đến 255.
  4. Nội dung nén / giải nén không được phép.

Vì lợi ích của riêng chúng tôi, hãy đăng các chuỗi nén với mã nguồn của bạn.

CẬP NHẬT 1: Ghi điểm thay đổi từ total_bytes_saved / program_sizeđể total_bytes_saved / sqrt(program_size)tăng thêm trọng lượng để nén tốt hơn và giảm trọng lượng cho việc chơi golf tích cực. Điều chỉnh điểm số của bạn cho phù hợp.

CẬP NHẬT 2: cố định wasitacaroraratisaw?wasitacaroracatisaw?


2
Nếu trường hợp, dấu chấm câu và khoảng cách được loại bỏ khỏi đầu vào, nó có được đảm bảo rằng đầu vào sẽ là palindromes nghiêm ngặt? Chỉnh sửa: nevermind - Tôi thấy đó wasitacaroraratisaw?là một ví dụ cho điều đó
Chấn thương kỹ thuật số

2
Phạm vi các ký tự ASCII nào chúng tôi phải hỗ trợ trong đầu vào? Phải [32-126]không
Arnauld

1
Vâng, tôi không nghĩ rằng 1000 *phần đó là thực sự cần thiết, và không tôi không nghĩ rằng nó sẽ làm cho điểm số cảm thấy "thỏa mãn" hơn;)
Erik the Outgolfer 30/1/18

1
Chúng ta có thể sử dụng nén / giải nén tích hợp không?
Lynn

4
Với rất ít đầu vào, không có nhiều phạm vi để làm bất cứ điều gì thông minh. Nó sẽ là tốt đẹp để có ít nhất một vài lần nữa.
user1502040

Câu trả lời:


16

JavaScript (ES6), 3.143 (đã lưu 81 byte, chương trình 664 byte)

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)}

Bây giờ tôi khá hài lòng với chương trình này (và hệ thống tính điểm), tôi sẽ viết một chút giải thích.

Ý tưởng cơ bản là nén đầu vào thành một chuỗi bit, sau đó nén mỗi bộ 8 bit thành một byte. Với mục đích giải thích, tôi sẽ chỉ thao tác chuỗi bit.

Chuỗi bit có thể được tách thành nhiều phần:

input  -> Taco Cat.
output -> 0101000000100011011111110100001100100011101011100000000

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

Tiêu đề là một ánh xạ rất đơn giản:

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

Dữ liệu thư cũng khá đơn giản. Đầu tiên, tất cả các chữ cái không được trích xuất từ ​​chuỗi và tất cả các chữ cái được chuyển đổi thành chữ hoa. Nếu chuỗi kết quả là một palindrom, một nửa đảo ngược bị tước bỏ. Sau đó, ánh xạ này được áp dụng:

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

Phần này được chấm dứt với 111. Sau đó là dữ liệu kiểu dáng, lưu trữ dữ liệu chữ hoa / thường và chữ không. Điều này hoạt động như vậy:

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

Vì vậy, đi qua ví dụ hiển thị ở trên, chúng ta có

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

Khi kết thúc chuỗi bit, tất cả các ký tự còn lại từ dữ liệu chữ cái sẽ được thêm vào kết quả. Điều này giúp chúng ta không phải thực hiện lần cuối 000...001và cho phép chúng ta cắt các bit này khỏi chuỗi.

Trải qua các trường hợp thử nghiệm:

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

Ồ Tôi thực sự ấn tượng bởi phương pháp này. Tôi sẽ không bao giờ nghĩ sẽ tạo ra một mã hóa bit như thế này. (Tôi đã nghĩ về trường hợp đóng gói ASCII thành 7 bit, nhưng không tiết kiệm nhiều không gian cho palindromes) Tôi rất ấn tượng rằng bạn cũng đã tiết kiệm được không gian Bob.
Beefster

4
Đây là một ví dụ tuyệt vời về các nguyên tắc cơ bản của kỹ thuật. Lấy một mô tả vấn đề, suy nghĩ về các cách khác nhau để giải quyết nó và tạo ra sự đánh đổi giữa các yêu cầu (nghĩa là có bao nhiêu bit để dành cho các phong cách khác nhau), v.v.
Robert Fraser

@Beefster Cảm ơn :-) Bobthực sự vừa rơi vào vị trí - 1 bit cho tiêu đề, 10 + 3 bit cho hai chữ cái và 2 bit cho chữ cái in hoa đơn. Không thể có được nó ngắn hơn nếu tôi đã cố gắng hết sức ...
Sản phẩm ETH

1
@KevinCruijssen vấn đề là thứ được thêm vào là một chuỗi, vì vậy nó phải được chuyển đổi thành một số đầu tiên. Cách này ngắn hơn một byte so với-0+9
ETHproductions

1
@ETHproductions Tất nhiên (không nhận thấy đó là một chuỗi)! +9sẽ nối với chuỗi, trong khi -~8sẽ thực hiện một cách hợp lý +9(vì -không làm gì cho chuỗi, vì vậy nó diễn giải nó thành một số). Trong trường hợp đó -~8là khá thông minh. :) Câu trả lời tốt đẹp btw, +1 từ tôi! Rất thông minh lưu trữ tất cả các thông tin trong các bit như thế, thậm chí tiết kiệm một byte trên Bob.
Kevin Cruijssen

2

Python 2: 2.765 (đã lưu 70 byte, chương trình 641 byte)

Tôi đã thay đổi cách tiếp cận của tôi một chút. Bây giờ nó hoạt động tốt trên palindromes không hoàn hảo. Không có chuỗi nén nào sẽ dài hơn đầu vào. Các palindromes có chiều dài đồng đều hoàn hảo sẽ luôn nén tới 50% kích thước ban đầu.

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

Các kết quả

'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)

Và như một phần thưởng, nó tiết kiệm 6 byte trên bảng màu không chính xác mà tôi có trước đây.

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

Giải trình

Giải nén sử dụng một ngăn xếp. Codepoint từ 32-127 được xử lý theo nghĩa đen. Nếu một ký tự là một chữ cái, một giá trị cũng được đẩy lên ngăn xếp. Các giá trị 128-192 được sử dụng cho các trường hợp lật các chữ cái, do đó, các chữ cái bị lật (o^32 vì cách trình bày ASCII) được đẩy lên ngăn xếp và chữ cái bình thường được thêm vào chuỗi. Các giá trị 192-255 được sử dụng để thêm các chữ cái mà không cần đẩy vào ngăn xếp, do đó, giá trị này được sử dụng khi các chữ cái không khớp và đối với chữ cái ở giữa các palindromes có độ dài lẻ. Codepoints 1-15 chỉ ra rằng ngăn xếp nên được bật số lần đó. Codepoints 17-31 tương tự nhau, nhưng chúng in một khoảng trắng trước khi bật ra khỏi ngăn xếp. Ngoài ra còn có một lệnh "bật cho đến khi trống" ở cuối đầu vào.

Máy nén hoạt động từ cả hai đầu và gấp trong các chữ cái khớp với giá trị 1-31. Nó bỏ qua các chữ cái không. Khi các chữ cái khớp nhưng trường hợp không, nó thêm 64 vào chữ cái bên trái và tăng chữ cái bên phải. Điều này cho phép nó tiết kiệm không gian trênIManAmRegalAGermanAmI . Ở giữa hoặc khi các chữ cái không khớp, nó xuất hiện 128 cho cả hai bên. Tôi không thể thêm vào đó vì tôi cần tránh trường hợp đặc biệt left == right. Khi gấp các điểm đánh dấu pop lân cận ở phía bên phải, tôi phải kiểm tra xem cái lân cận sẽ không tràn vào mã số 16 vì tôi cần nó cho khoảng trắng. (Đây thực sự không phải là vấn đề đối với bất kỳ chuỗi trường hợp thử nghiệm nào)

EDIT 1 : Không còn phiên bản vô tri.


1

Python3, 1.833 (đã lưu 25 byte, chương trình 186 byte)

Chỉ cần mã hóa entropy 0 xác suất bằng thứ tự đơn giản. Không tối ưu hóa đặc trưng cho palindrom.

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, điểm: 1.355 (20 byte được lưu / 218 (107 + 111) byte)

Hàm nén (chứa ba ký tự ASCII không thể in):

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

Hàm giải nén (chứa hai ký tự ASCII không thể in được):

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

Giải trình:

Hãy thử trực tuyến.

Chỉ nén palindromes hoàn hảo.

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.