パリンドローム圧縮


15

チャレンジ

ASCIIテキストを無損失で圧縮および圧縮解除するプログラムを作成します。大文字と小文字を区別しない、句読点と区別しないパリンドロームなど、パリンドロームとうまく機能するように専門化する必要があります。最小のソースで最高の圧縮が優先されます。

得点

total_bytes_saved / sqrt(program_size) -最高得点

total_bytes_saved圧縮された文字列が元の文字列よりも小さいバイト数で、以下のテストケース全体で合計です。program_size圧縮プログラムと解凍プログラムの両方のソースコードのバイト単位のサイズです。2つの間で共有されるコードは1回だけカウントする必要があります。

たとえば、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)コンプレッションを改善するための重量を増やし、アグレッシブなゴルフの重量を減らしました。それに応じてスコアを調整します。

UPDATE 2:固定wasitacaroraratisaw?しますwasitacaroracatisaw?


2
大文字小文字、句読点、および間隔が入力から削除された場合、入力が厳密な回文であることが保証されますか?編集:ネバーマインド-私はそれwasitacaroraratisaw?に対する反例だと思う
デジタルトラウマ

2
入力でサポートするASCII文字の範囲はどれですか?それは[32-126]
アーナウルド

1
そう私は考えていない1000 *);部分が本当に必要とされ、ノー私はそれがより多くの「満足」スコア感触を作るとは思わない
エリックOutgolfer

1
圧縮/解凍のビルトインを使用できますか?
リン

4
入力が非常に少ないため、巧妙なことをする余地はあまりありません。少なくとも数倍以上あればいいと思います。
user1502040

回答:


16

JavaScript(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もスペースを節約できたことに感銘を受けました。
ビーフスター

4
これは、エンジニアリングの基礎の素晴らしい例です。問題の説明を取り、それを解決するさまざまな方法を考え、要件(つまり、さまざまなスタイルに専念するビット数)などのトレードオフを行う
ロバートフレイザー

@Beefsterありがとう:-) Bob実際のところ、ヘッダーには1ビット、2文字には10 + 3ビット、1つの大文字には2ビットが配置されました。私が一生懸命やろうとしたら、それ以上短くなりませんでした
...-ETHproductions

1
@KevinCruijssenの問題は、追加されるものが文字列であるため、最初に数値に変換する必要があることです。この方法は、-0+9
ETHproductions

1
@ETHproductions Ahもちろん(それが文字列であることに気付かなかった)!+9は文字列に連結しますが、算術的に-~8行い+9ます(-文字列には何もしないため、数値として解釈します)。その場合-~8、かなりスマートです。:)私から+1、ところでいい答え!すべての情報をそのようなビットで非常にスマートに保存し、にバイトを保存しますBob
ケビンクルイッセン

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:これ以上のバージョンはありません。


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)バイト)

圧縮機能(3つの印刷できないASCII文字を含む):

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

解凍機能(2つの印刷できない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.