Indurente per meta-radiazione


19

sfondo

Su questo sito, occasionalmente abbiamo domande che richiedono che i programmi siano "induriti con radiazioni"; questo significa che il programma deve essere in grado di sopravvivere alla cancellazione di uno o più byte, indipendentemente da quali byte vengano eliminati.

Come è comune per le attività che vengono spesso impostate nelle sfide di programmazione, è naturale voler creare un linguaggio particolarmente efficace in queste sfide. Dato che il modo naturale per farlo è quello di aggiungere alcuni metadati che consentano di invertire la corruzione, in realtà non è davvero un linguaggio che deve essere progettato, ma una codifica; l'idea è di trasformare ogni input in una sequenza di byte, in modo tale che anche se la sequenza è leggermente irradiata, è possibile estrarre l'input originale.

L'obiettivo

Scrivi due programmi o funzioni, E (un codificatore) e D (un decodificatore), in modo che:

  • E accetta due argomenti, una sequenza di ottetti (che chiameremo " input " in questa specifica) e un numero intero non negativo " radiazione ", e genera una sequenza di ottetti " codifica ";
  • D accetta un argomento, una sequenza di ottetti (" encdng ") e genera una ricostruzione di una sequenza di ottetti " " ;
  • Se si eseguono sia E che D (con encdng , l'input su D, scelto eliminando non più di elementi di radiazione dalla codifica (non necessariamente contigui)), la ricostruzione sarà uguale all'input indipendentemente da quali caratteri sono stati eliminati per formare encdng .

chiarimenti

  • Se invii funzioni, non devi chiamarle Ee D; puoi scegliere il nome più adatto alla tua lingua.
  • Un "ottetto" è fondamentalmente un numero intero compreso tra 0 e 255 inclusi, che puoi codificare come numero intero, carattere o qualunque cosa sia appropriata per la tua lingua.
  • E e D devono essere interamente deterministici (ovvero dare loro gli stessi input produrrà sempre lo stesso output, dove "input" è definito come input e radiazione per E, o encdng per D). In particolare, E non può comunicare informazioni a D tramite un canale laterale.
  • Le eliminazioni vengono eseguite cancellando un elemento della sequenza; pensa di aprire la sequenza in un editor, posizionare il cursore in un punto arbitrario e premere Backspace. Se un elemento viene visualizzato più volte, è possibile che venga eliminata solo una copia dell'elemento (ovvero, le altre istanze dello stesso ottetto non saranno interessate).
  • Sebbene il punteggio sia calcolato solo sulla base di input piuttosto brevi , il programma deve funzionare in teoria per qualsiasi input e radiazione . In particolare, deve funzionare indipendentemente dagli ottetti visualizzati nell'input . (Mi dispiace, le persone che vorrebbero la possibilità di usare caratteri non stampabili che sanno non appariranno nell'input, ma devo assicurarmi che l'input sia incomprimibile in modo che la sfida riguardi l'indurimento delle radiazioni piuttosto che la compressione.)
  • È possibile inviare un solo file che definisce due funzioni; due file che definiscono ciascuno una funzione o che sono entrambi programmi completi; o tre file, due dei quali implementano rispettivamente D ed E (tramite programmi completi o definizione di una funzione) e il terzo che è un file di intestazione o una libreria comune sia a D che a E. Indipendentemente dalla forma di invio utilizzata , l'implementazione del linguaggio di programmazione deve essere in grado di comprendere entrambi i programmi senza ulteriori argomenti come l'ubicazione dei file (oppure è necessario pagare una penalità di byte per invocare l'implementazione in modo insolito, secondo le nostre regole standard).

Condizione di vittoria

Per ogni lunghezza e radiazione , f ( lunghezza , radiazione ) sono le lunghezze totali delle codifiche che corrispondono a tutti gli input con lunghezza di lunghezza e alla radiazione data . (Cioè, f ( lunghezza , radiazione ) = input della somma ha lunghezza lunghezza lunghezza (E ( input , radiazione )). Quindi lascia g ( lunghezza , radiazione ) uguale f ( lunghezza ,radiazione ) ÷ 256 lunghezza . In altre parole, g è la lunghezza media dell'uscita codificata, per una data lunghezza di ingresso e un dato requisito di indurimento per radiazione. (In teoria potresti calcolarlo con la forza bruta, ma probabilmente ci vorrebbe in modo plausibilmente lungo per elaborare il tuo punteggio in quel modo. Mi aspetto che la maggior parte degli invii sarà in grado di formulare un argomento matematico su quale sia il loro punteggio. Se tu ' non sei sicuro, pubblica un punteggio approssimativo e tu o qualcun altro potete calcolarlo più in profondità se un'altra voce pubblica un punteggio simile.)

Il tuo punteggio è uguale alla somma di g ( lunghezza , radiazione ) per tutte le radiazioni nell'intervallo da 0 a 9 incluso, e tutta la lunghezza nell'intervallo da 0 a 99 incluso, più (principalmente per evitare hardcoding, o per far andare avanti la competizione se qualcuno scopre una codifica matematicamente perfetta; questo è probabilmente un fattore minimo altrimenti) il numero totale di byte nella tua presentazione alla sfida (oltre alle penalità standard per cose come richiedere bandiere di interpreti insolite o nomi di file specifici). Il vincitore è la voce con il punteggio più basso (pareggiata dalla prima voce da inviare).


Il decodificatore può conoscere anche il parametro di radiazione ?
orlp

(o lunghezza , ma credo che conoscere entrambi dovrebbe darti l'altro per la maggior parte degli schemi)
orlp

1
@orlp: No, ha solo la stringa. In un problema di indurimento delle radiazioni , il decodificatore (cioè la lingua) non conosce le regole di radiazione utilizzate, quindi anche il decodificatore non le conosce; deve dedurli dal suo input.

Basandomi su questo video per computer, creerei un linguaggio che accetta i codici in terzine a 3 byte: se non corrispondono tutti, qualcosa è andato storto e abbiamo abbastanza informazioni per capire quale sia il valore giusto. Probabilmente c'è un modo per farlo con meno bit, ma non ho il cervello per capire come al momento.
Draco18s

Come si combinano l'intestazione e i programmi?
CalculatorFeline

Risposte:


8

CJam, punteggio ≤ 286.516 + 54 + 36 = 286.606

Codificatore

{_{1\+_:e>)_0a+@@b0\{{)_256b_,\e`,-}g}*256b+\)e*}{*}?}

Provalo online!

decoder

{_{e`1f=(\256b{256b_,\e`,=},,\b1>}&}

Provalo online!

Entrambi prendono e restituiscono un numero intero di elenco. I collegamenti TIO includono la conversione da / in stringhe per comodità. Si noti che questi sono incredibilmente inefficienti per stringhe più lunghe. Se vuoi provare qualche altro personaggio, ti consiglio di usare caratteri con codici di caratteri piccoli.

L'idea di base per la creazione di una codifica indurita dalle radiazioni prevede due passaggi:

  1. Trova una codifica che non contiene mai due ottetti identici consecutivi.
  2. Ripeti ogni ottetto nella stringa codificata r + 1 volte, dove r è il livello di radiazione.

In questo modo, la radiazione non può eliminare completamente una sequenza di caratteri identici, in modo da poter decodificare la stringa prendendo un carattere da ciascuna sequenza e quindi decodificando il passaggio 1.

Quindi l'unica parte interessante è trovare una codifica che non produca mai ottetti ripetuti. L'idea di base è usare qualcosa come A043096 come sistema numerico. Cioè, per codificare un numero intero N , contiamo semplicemente in qualche base b , saltando tutti i numeri con ottetti ripetuti. Credo che la quantità di numeri che possono essere rappresentati in questo modo con cifre fino a d sia la stessa quantità di numeri che possono essere rappresentati nella base biiettiva b-1 (poiché, quando si desidera scrivere un tale numero, è possibile scegliere tra la cifra b-1 per ciascuna posizione senza violare il vincolo).

Naturalmente, per ottenere la massima compressione useremo b = 256 . Per trasformare l'input in un numero intero, possiamo usare anche la conversione di base. Se non fossi pigro, utilizzerei una base biiettiva per l'input, ma per ora sto solo anteponendo un 1(per assicurarmi che non ci siano zeri iniziali) e quindi utilizzare la base più piccola possibile in modo tale che tutti gli ottetti nel l'input è inferiore alla base.

Questa base viene quindi anteposta alla codifica (in modo che il decodificatore sappia quale base utilizzare) e separata dal numero rimanente da uno 0-ottetto (funziona perché il numero rimanente non inizierà mai con uno zero). Come ottimizzazione minore, la stringa vuota rimane una stringa vuota.

Il motivo per cui non ho calcolato un punteggio esatto sopra è che sto solo calcolando un limite superiore per quanto tempo ogni input sarà basato sulla sua lunghezza e il suo ottetto massimo. Tuttavia, per questi due parametri, ci saranno spesso due diverse lunghezze di uscita e non mi sono ancora preoccupato di capire dove si verifica il punto di ribaltamento tra di loro. Ho anche usato la lunghezza della solita base 255 invece della base biiettiva 255 per stimare quella lunghezza, che è di nuovo leggermente più grande di quanto dovrebbe essere. L'esatto codice Mathematica che ho usato per il calcolo è il seguente:

num[l_, 1] = 0;
num[l_, base_] := num[l, base] = base^l - Sum[num[l, b], {b, base - 1}]
Sum[
  num[l, b]*(r + 1)*(2 + IntegerLength[2*b^l - 1, 255])/256^l, 
  {r, 0, 9}, {l, 1, 99}, {b, 2, 256}
]
N@%

num[l, b]dovrebbe indicare il numero di stringhe di lunghezza lcon il numero massimo di ottetti b-1(ad eccezione di b == 1dove l'ho codificato 0perché uso sempre almeno la base 2).


"Supponendo che in media una stringa di lunghezza N non possa essere codificata in meno di (r + 1) * N ottetti a livello di radiazione r." Non vedo alcun motivo per questo per essere vero. Non sarei sorpreso di vedere uno schema di codifica esistente che è O (N + r).
orlp

1
@orlp Non vedo come sarebbe possibile, ma non vedo l'ora di essere smentito. :)
Martin Ender,

Bella idea Non conosco CJam, ma dalla tua descrizione sembra che stai anteponendo la base ai dati codificati. In tal caso, c'è un problema se ci sono caratteri cancellati dai dati anteposti? (Questo è l'errore che ho fatto a @Leo, che ho dovuto risolvere nella mia soluzione.)
Mitchell Spector,

@MitchellSpector la base viene anteposta prima di ripetere ogni carattere r + 1 volte. Quindi anche la base è protetta dalle radiazioni.
Martin Ender,

Va bene, è quello che ho fatto anche nella mia soluzione. Devi solo assicurarti che il decodificatore possa decodificare i dati anteposti prima di sapere quale sia la base.
Mitchell Spector,

6

bash + utility GNU, punteggio 294506 283468

Modifica 1: risolve un problema che @Leo ha notato - grazie!

Modifica 2: Migliorato il metodo di codifica per il parametro di radiazione, per un punteggio migliore.

Encoder (97 byte):

for((j=0;j<$1;j++)){ p+=p\;;}
(sed 's/\(.\)\1/\1a\1a/g'<<<$1;cat)|xxd -p -c1|sed ${p-;}|xxd -r -p

Decodificatore (121 byte):

read n
n=`sed 's/\(.\)\1*/\1/g'<<<$n`
sed -r "s/( ..)\1{0,${n//a}}/\1/g"<<<' 0a '`xxd -p -c1`|sed 's/^ [^ ]*//'|xxd -r -p

Per l'encoder: sequenza di ottetti passata come caratteri nello stdin, parametro di radiazione r passato come argomento.

Per il decodificatore: input passato come caratteri nello stdin.

Per entrambi: output su stdout.

L'encoder antepone ai dati di input le cifre di r, con un carattere "a" inserito tra ciascuna coppia di cifre identiche consecutive, seguito da una nuova riga. Quindi copia tutto l'input (iniziando con i caratteri anteposti), sostituendo ogni carattere con r + 1 copie di quel carattere.

Il decodificatore annulla questo, esaminando ciascuno dei caratteri rimanenti x nel suo input, saltando fino a r copie identiche consecutive di x dopo x e stampando ciò che rimane. I dati anteposti non hanno caratteri ripetuti, quindi possono essere decodificati prima che r sia noto. A quel punto, r è noto e quel valore è necessario per decodificare correttamente il resto dei dati.

Puoi verificare che funzioni anche se l'input originale ha ripetuto caratteri identici.


Calcolo del punteggio:

Supponiamo che l'ingresso abbia la lunghezza L e il parametro di radiazione sia r (che è al massimo 9 per il calcolo del punteggio, quindi si adatta a una cifra e quindi non ha caratteri ripetuti consecutivi). I dati anteposti sono 2 byte (cifra, nuova riga), quindi l'output è (r + 1) (L + 2) byte per il flusso codificato.

Quindi g (L, r) = (r + 1) (L + 2).

Ne consegue che il punteggio totale può essere calcolato come

inserisci qui la descrizione dell'immagine


Cosa succede se l'ottetto rilasciato è il primo? Il decoder non avrebbe dovuto rleggere
Leo,

@Leo hai ragione. Domani cercherò di risolverlo, stasera è troppo tardi. Grazie per averlo individuato.
Mitchell Spector,

@Leo Penso che sia risolvibile includendo r + 1 copie di ciascuna delle cifre di r, seguite da r + 1 newline. Se è corretto, il punteggio non aumenterà molto.
Mitchell Spector,

Qualcosa del genere dovrebbe funzionare. Penso che dovresti prendere alcune misure aggiuntive per assicurarti che funzioni correttamente con radiazioni più alte (ad esempio una radiazione di 222), ma per fortuna il punteggio viene calcolato appena sopra le radiazioni 0-9, quindi non sarà influenzato molto. PS Stavo pensando di implementare questa stessa codifica, ecco perché ho individuato l'errore immediatamente;)
Leo

@Leo Sì, la correzione funziona per tutti i valori per la radiazione, anche se il punteggio tiene conto solo dei valori di radiazione al massimo di 9.
Mitchell Spector,

3

Perl + Math :: {ModInt, Polynomial, Prime :: Util}, punteggio ≤ 92819

$m=Math::Polynomial;sub l{($n,$b,$d)=@_;$n||$d||return;$n%$b,l($n/$b,$b,$d&&$d-1)}sub g{$p=$m->interpolate([grep ref$_[$_],0..$map{$p->evaluate($_)}0..$}sub p{prev_prime(128**$s)}sub e{($_,$r)=@_;length||return'';$s=$r+1;s/^[␀␁]/␁$&/;@l=map{mod($_,p$s)}l(Math::BigInt->from_bytes($_),p$s);$@l+$r>p($s)&&return e($_,$s);$a=0;join'',map{map{chr$_+$a}l($_->residue,128,$s,($a^=128))}g(@l)}sub d{@l=split/([␀-␡]+)/,$_[0];@l||return'';$s=vecmax map length,@l;@l=g map{length==$s&&mod($m->new(map{ord()%128}split//)->evaluate(128),p$s)}@l;$$_=$m->new(map{$_->residue}@l)->evaluate(p$s)->to_bytes;s/^␁//;$_}

Le immagini di controllo vengono utilizzate per rappresentare il carattere di controllo corrispondente (ad es. È un carattere letterale NUL). Non preoccuparti molto di provare a leggere il codice; c'è una versione più leggibile di seguito.

Corri con -Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all. -MMath::Bigint=lib,GMPnon è necessario (e quindi non è incluso nella partitura), ma se lo aggiungi prima delle altre librerie renderà il programma un po 'più veloce.

Calcolo del punteggio

L'algoritmo qui è in qualche modo migliorabile, ma sarebbe piuttosto difficile da scrivere (a causa del fatto che Perl non ha le librerie appropriate). Per questo motivo, ho fatto un paio di compromessi di dimensioni / efficienza nel codice, sulla base del fatto che dato che i byte possono essere salvati nella codifica, non ha senso cercare di eliminare ogni punto dal golf.

Il programma è composto da 600 byte di codice, più 78 byte di penalità per le opzioni della riga di comando, con una penalità di 678 punti. Il resto del punteggio è stato calcolato eseguendo il programma sulla stringa migliore e peggiore (in termini di lunghezza dell'uscita) per ogni lunghezza da 0 a 99 e ogni livello di radiazione da 0 a 9; il caso medio è da qualche parte nel mezzo, e questo dà dei limiti al punteggio. (Non vale la pena provare a calcolare il valore esatto a meno che non arrivi un'altra voce con un punteggio simile.)

Ciò significa quindi che il punteggio dall'efficienza di codifica è compreso tra 91100 e 92141 inclusi, quindi il punteggio finale è:

91100 + 600 + 78 = 91778 ≤ punteggio ≤ 92819 = 92141 + 600 + 78

Versione meno giocata, con commenti e codice di prova

Questo è il programma originale + nuove righe, rientri e commenti. (In realtà, la versione golf è stata prodotta rimuovendo newline / rientri / commenti da questa versione.)

use 5.010;                    # -M5.010; free
use Math::BigInt lib=>'GMP';  # not necessary, but makes things much faster
use bigint;                   # -Mbigint
use Math::ModInt 'mod';       # -MMath::ModInt=mod
use Math::Polynomial;         # -MMath::Polynomial
use ntheory ':all';           # -Mntheory=:all
use warnings;                 # for testing; clearly not necessary

### Start of program

$m=Math::Polynomial;          # store the module in a variable for golfiness

sub l{ # express a number $n in base $b with at least $d digits, LSdigit first
    # Note: we can't use a builtin for this because the builtins I'm aware of
    # assume that $b fits into an integer, which is not necessarily the case.
    ($n,$b,$d)=@_;
    $n||$d||return;
    $n%$b,l($n/$b,$b,$d&&$d-1)
}

sub g{ # replaces garbled blocks in the input with their actual values
    # The basic idea here is to interpolate a polynomial through all the blocks,
    # of the lowest possible degree. Unknown blocks then get the value that the
    # polynomial evaluates to. (This is a special case of Reed-Solomon coding.)
    # Clearly, if we have at least as many ungarbled blocks as we did original
    # elements, we'll get the same polynomial, thus we can always reconstruct
    # the input.
    # Note (because it's confusing): @_ is the input, $_ is the current element
    # in a loop, but @_ is written as $_ when using the [ or # operator (e.g.
    # $_[0] is the first element of @_.
    # We waste a few bytes of source for efficiency, storing the polynomial
    # in a variable rather than recalculating it each time.
    $p=$m->interpolate([grep ref$_[$_],0..$#_],[grep ref,@_]);
    # Then we just evaluate the polynomial for each element of the input.
    map{$p->evaluate($_)}0..$#_
}

sub p{ # determines maximum value of a block, given (radiation+1)
    # We split the input up into blocks. Each block has a prime number of
    # possibilities, and is stored using the top 7 bits of (radiation+1)
    # consecutive bytes of the output. Work out the largest possible prime that
    # satisfies this property.
    prev_prime(128**$s)
}

sub e{ # encoder; arguments: input (bytestring), radiation (integer)
    ($_,$r)=@_; # Read the arguments into variables, $_ and $r respectively
    length||return''; # special case for empty string
    $s=$r+1; # Also store radiation+1; we use it a lot
    # Ensure that the input doesn't start with NUL, via prepending SOH to it if
    # it starts with NUL or SOH. This means that it can be converted to a number
    # and back, roundtripping correctly.
    s/^[␀␁]/␁$&/; #/# <- unconfuse Stack Exchange's syntax highlighting
    # Convert the input to a bignum, then to digits in base p$s, to split it
    # into blocks.
    @l=map{mod($_,p$s)}l(Math::BigInt->from_bytes($_),p$s);
    # Encoding can reuse code from decoding; we append $r "garbled blocks" to
    # the blocks representing the input, and run the decoder, to figure out what
    # values they should have.
    $#l+=$r;
    # Our degarbling algorithm can only handle at most p$s blocks in total. If
    # that isn't the case, try a higher $r (which will cause a huge increase in
    # $b and a reduction in @l).
    @l+$r>p($s)&&return e($_,$s);
    # Convert each block to a sequence of $s digits in base 128, adding 128 to
    # alternating blocks; this way, deleting up to $r (i.e. less than $s) bytes
    # will preserve the boundaries between each block; then convert that to a
    # string
    $a=0; # we must initialize $a to make this function deterministic
    join'',map{map{chr$_+$a}l($_->residue,128,$s,($a^=128))}g(@l)
}

sub d{ # decoder: arguments; encdng (bytestring)
    # Reconstruct the original blocks by looking at their top bits
    @l=split/([␀-␡]+)/,$_[0];
    @l||return''; # special case for empty string
    # The length of the longest block is the radiation parameter plus 1 (i.e.
    # $s). Use that to reconstruct the value of $s.
    $s=vecmax map length,@l;
    # Convert each block to a number, or to undef if it has the wrong length.
    # Then work out the values for the undefs.
    @l=g map{
        # Convert blocks with the wrong length to undef.
        length==$s&&
            # Convert other blocks to numbers, via removing any +128 and then
            # using Math::Polynomial to convert the digit list to a number.
            mod($m->new(map{ord()%128}split// #/# <- fix syntax highlighting
            )->evaluate(128),p$s)
    }@l;
    # Remove the redundant elements at the end; now that they've reconstructed
    # the garbled elements they have no further use.
    $#l-=$s-1;
    # Convert @l to a single number (reversing the conversion into blocks.)
    $_=$m->new(map{$_->residue}@l)->evaluate(p$s)
        # Convert that number into a string.
        ->to_bytes;
    # Delete a leading SOH.
    s/^␁//;  #/# <- unconfuse Stack Exchange's syntax highlighting
    # Finally, return the string.
    $_
}


### Testing code
use Encode qw/encode decode/;

# Express a string using control pictures + IBM437, to make binary strings
# easier for a human to parse
sub format_string {
    ($_)=@_;
    $_ = decode("Latin-1", $_);
    s/[\0-\x1f]/chr (0x2400 + ord $&)/eag;
    s/\x7f/chr 0x2421/eag;
    s/[ -~\x80-\xff]/decode("IBM437",$&)/eag;
    encode("UTF-8","\x{ff62}$_\x{ff63}")
}

sub test {
    my ($string, $radiation, $samples) = @_;
    say "Input: ", format_string($string);
    my $encoding = e($string, $radiation);
    say "Encoding: ", format_string($encoding);
    say "Input length ", length($string), ", encoding length ", length($encoding), ", radiation $radiation";
    my $decoding = d($encoding);
    $decoding eq $string or die "Mistake in output!";
    say "Decoding: ", format_string($decoding), " from ",
        format_string($encoding);

    # Pseudo-randomly generate $samples radiation-damaged versions.
    srand 1;
    for my $i (1..$samples) {
        my $encdng = $encoding;
        for my $r (1..$radiation) {
            substr $encdng, int(rand(length $encdng)), 1, "";
        }
        my $newdecoding = d($encdng);
        say "Decoding: ", format_string($newdecoding), " from ",
            format_string($encdng);
        $newdecoding eq $string or die "Mistake in output!";
    }

    say "";
    length $encoding;
}

test "abcdefghijklm", 1, 10;
test "abcdefghijklm", 2, 10;
test "abcdefghijklm", 5, 10;
test "abcdefghijklm", 10, 10;
test "\0\0\0\0\0", 1, 10;
test "\5\4\3\2\1", 2, 10;
test "a", 10, 10;

my %minlength = ();
my %maxlength = ();

for my $length (0..99) {
    my ($min, $max) = ("", "");
    $length and ($min, $max) =
        ("\2" . "\0" x ($length - 1), "\1" . "\377" x ($length - 1));
    for my $radiation (0..9) {
        $minlength{"$length-$radiation"} = test $min, $radiation, 1;
        $maxlength{"$length-$radiation"} = test $max, $radiation, 1;
    }
}

say "Minimum score: ", vecsum values %minlength;
say "Maximum score: ", vecsum values %maxlength;

Algoritmo

Semplificare il problema

L'idea di base è quella di ridurre questo problema di "codifica di cancellazione" (che non è ampiamente esplorato) in un problema di codifica di cancellazione (un'area della matematica esplorata in modo completo). L'idea alla base della codifica di cancellazione è che stai preparando i dati da inviare su un "canale di cancellazione", un canale che a volte sostituisce i caratteri che invia con un carattere "groviglio" che indica una posizione nota di un errore. (In altre parole, è sempre chiaro dove si è verificata la corruzione, anche se il personaggio originale è ancora sconosciuto.) L'idea alla base è piuttosto semplice: dividiamo l'input in blocchi di lunghezza ( radiazione+ 1) e usa sette degli otto bit in ciascun blocco per i dati, mentre il bit rimanente (in questa costruzione, MSB) si alterna tra l'impostazione di un intero blocco, azzera per l'intero blocco successivo, impostata per il blocco dopo quello e così via. Poiché i blocchi sono più lunghi del parametro di radiazione, almeno un carattere di ciascun blocco sopravvive nell'output; così prendendo sequenze di personaggi con lo stesso MSB, possiamo capire a quale blocco apparteneva ciascun personaggio. Anche il numero di blocchi è sempre maggiore del parametro di radiazione, quindi abbiamo sempre almeno un blocco intatto nell'encdng; sappiamo quindi che tutti i blocchi più lunghi o legati più a lungo sono integri, permettendoci di trattare eventuali blocchi più corti come danneggiati (quindi una garble). Possiamo anche dedurre il parametro di radiazione in questo modo (è '

Codifica di cancellazione

Per quanto riguarda la parte di codice di cancellazione del problema, questo utilizza un semplice caso speciale della costruzione Reed-Solomon. Questa è una costruzione sistematica: l'uscita (dell'algoritmo di codifica della cancellazione) è uguale all'ingresso più un numero di blocchi extra, pari al parametro di radiazione. Siamo in grado di calcolare i valori necessari per questi blocchi in modo semplice (e da golf!), Trattandoli come rifiuti, quindi eseguendo l'algoritmo di decodifica su di essi per "ricostruire" il loro valore.

L'idea reale alla base della costruzione è anche molto semplice: adattiamo un polinomio, il grado minimo possibile, a tutti i blocchi della codifica (con le garbature interpolate dagli altri elementi); se il polinomio è f , il primo blocco è f (0), il secondo è f (1) e così via. È chiaro che il grado del polinomio sarà uguale al numero di blocchi di input meno 1 (perché prima adattiamo un polinomio a quelli, quindi lo usiamo per costruire i blocchi extra "check"); e poiché d +1 punti definiscono in modo univoco un polinomio di grado d, confondendo un numero qualsiasi di blocchi (fino al parametro di radiazione) lascerà un numero di blocchi non danneggiati pari all'input originale, che è abbastanza informazioni per ricostruire lo stesso polinomio. (Non ci resta che valutare il polinomio come ungarble di un blocco.)

Conversione di base

L'ultima considerazione lasciata qui riguarda i valori effettivi presi dai blocchi; se eseguiamo interpolazione polinomiale sugli interi, i risultati possono essere numeri razionali (anziché interi), molto più grandi dei valori di input o altrimenti indesiderabili. Come tale, invece di usare gli interi, usiamo un campo finito; in questo programma, il campo finito utilizzato è il campo di numeri interi modulo p , dove p è il primo più grande inferiore a 128 radiazioni +1(ovvero il primo più grande per il quale possiamo adattare un numero di valori distinti pari a quel primo nella parte di dati di un blocco). Il grande vantaggio dei campi finiti è che la divisione (tranne per 0) è definita in modo univoco e produrrà sempre un valore all'interno di quel campo; pertanto, i valori interpolati dei polinomi si inseriranno in un blocco nello stesso modo dei valori di input.

Per convertire l'ingresso in una serie di dati di blocco, quindi, dobbiamo eseguire la conversione di base: convertire l'ingresso dalla base 256 in un numero, quindi convertire in base p (ad es. Per un parametro di radiazione di 1, abbiamo p= 16381). Ciò è stato in gran parte ostacolato dalla mancanza di routine di conversione delle basi di Perl (Math :: Prime :: Util ne ha alcune, ma non funzionano per le basi di bignum e alcuni dei numeri primi con cui lavoriamo qui sono incredibilmente grandi). Dato che stiamo già usando Math :: Polynomial per l'interpolazione polinomiale, sono stato in grado di riutilizzarlo come funzione "Converti da sequenza di cifre" (visualizzando le cifre come coefficienti di un polinomio e valutandolo), e questo funziona per i bignum va bene. Andando dall'altra parte, però, ho dovuto scrivere la funzione da solo. Fortunatamente, non è troppo difficile (o dettagliato) da scrivere. Sfortunatamente, questa conversione di base significa che l'input è in genere reso illeggibile. C'è anche un problema con gli zero iniziali;

Va notato che non possiamo avere più di p blocchi nell'output (altrimenti gli indici di due blocchi diventerebbero uguali e tuttavia potrebbe essere necessario produrre output diversi dal polinomio). Questo accade solo quando l'ingresso è estremamente grande. Questo programma risolve il problema in un modo molto semplice: aumentando radiazione (che rende i blocchi grandi e p molto più grande, che significa che possiamo sistemare più dati, e che chiaramente conduce a un risultato corretto).

Un altro punto degno di nota è che codifichiamo la stringa null su se stessa, perché altrimenti il ​​programma come scritto andrebbe in crash. È anche chiaramente la migliore codifica possibile e funziona indipendentemente dal parametro di radiazione.

Potenziali miglioramenti

La principale inefficienza asintotica in questo programma ha a che fare con l'uso di modulo-prime come campi finiti in questione. Esistono campi finiti di dimensione 2 n (che è esattamente quello che vorremmo qui, perché le dimensioni del carico utile dei blocchi sono naturalmente una potenza di 128). Sfortunatamente, sono piuttosto più complessi di una semplice costruzione di moduli, il che significa che Math :: ModInt non lo taglierebbe (e non ho trovato alcuna libreria su CPAN per la gestione di campi finiti di dimensioni non prime); Dovrei scrivere un'intera classe con un'aritmetica sovraccarica affinché Math :: Polynomial sia in grado di gestirla, e a quel punto il costo in byte potrebbe potenzialmente superare la (molto piccola) perdita derivante dall'uso, ad esempio, 16381 anziché 16384.

Un altro vantaggio dell'utilizzo di dimensioni di potenza di 2 è che la conversione di base diventerebbe molto più semplice. Tuttavia, in entrambi i casi, sarebbe utile un metodo migliore per rappresentare la lunghezza dell'input; il metodo "anteporre un 1 in casi ambigui" è semplice ma dispendioso. La conversione della base biiettiva è un approccio plausibile qui (l'idea è che tu abbia la base come cifra e 0 come non una cifra, in modo che ogni numero corrisponda a una singola stringa).

Sebbene le prestazioni asintotiche di questa codifica siano molto buone (ad es. Per un input di lunghezza 99 e un parametro di radiazione di 3, la codifica è sempre lunga 128 byte, anziché i ~ 400 byte che otterrebbero gli approcci basati sulla ripetizione), le sue prestazioni è meno buono su input corti; la lunghezza della codifica è sempre almeno il quadrato del (parametro di radiazione + 1). Quindi per input molto brevi (lunghezza da 1 a 8) alla radiazione 9, la lunghezza dell'output è comunque 100. (Alla lunghezza 9, la lunghezza dell'output è talvolta 100 e talvolta 110.) Gli approcci basati sulla ripetizione battono chiaramente questa cancellazione -approccio basato sulla codifica su input molto piccoli; potrebbe valere la pena cambiare tra più algoritmi in base alla dimensione dell'input.

Infine, non si ottiene realmente nel calcolo del punteggio, ma con parametri di radiazione molto elevati, l'utilizzo di un bit di ogni byte (⅛ della dimensione dell'uscita) per delimitare i blocchi è dispendioso; sarebbe invece più economico usare delimitatori tra i blocchi. Ricostruire i blocchi dai delimitatori è piuttosto più difficile che con l'approccio a MSB alternato, ma ritengo sia possibile, almeno se i dati sono sufficientemente lunghi (con dati brevi, può essere difficile dedurre il parametro di radiazione dall'output) . Sarebbe qualcosa da considerare se si punta a un approccio asintoticamente ideale indipendentemente dai parametri.

(E ovviamente, potrebbe esserci un algoritmo completamente diverso che produce risultati migliori di questo!)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.