Chất làm cứng bức xạ meta


19

Lý lịch

Trên trang này, đôi khi chúng tôi có câu hỏi yêu cầu các chương trình phải "cứng hóa bức xạ"; điều này có nghĩa là chương trình phải có khả năng tồn tại trong việc xóa một hoặc nhiều byte, bất kể byte nào bị xóa.

Như thường thấy đối với các nhiệm vụ thường xuyên được đặt trong các thử thách lập trình, việc tạo ra một ngôn ngữ đặc biệt tốt cho các thử thách này là điều tự nhiên. Cho rằng cách tự nhiên để thực hiện điều này là thêm một số siêu dữ liệu giúp đảo ngược tham nhũng, thực sự nó không thực sự là một ngôn ngữ cần thiết kế, mà là mã hóa; ý tưởng là biến đổi mỗi đầu vào thành một chuỗi byte, theo cách mà ngay cả khi chuỗi đó được chiếu xạ một chút, có thể trích xuất đầu vào ban đầu.

Nhiệm vụ

Viết hai chương trình hoặc hàm, E (bộ mã hóa) và D (bộ giải mã), sao cho:

  • E nhận hai đối số, một chuỗi các octet (mà chúng ta sẽ gọi là " đầu vào " trong thông số kỹ thuật này) và một " bức xạ " số nguyên không âm , và đưa ra một chuỗi các octet " mã hóa ";
  • D nhận một đối số, một chuỗi các octet (" encdng ") và đưa ra một chuỗi các octet " tái cấu trúc ";
  • Nếu bạn chạy cả E và D (với encdng , đầu vào của D, được chọn bằng cách xóa không quá các phần tử bức xạ khỏi mã hóa (không nhất thiết phải liền kề)), thì việc xây dựng lại sẽ bằng với đầu vào cho dù ký tự nào bị xóa để tạo thành encdng .

Làm rõ

  • Nếu bạn gửi chức năng, bạn không phải gọi chúng ED; bạn có thể chọn bất cứ tên nào phù hợp nhất với ngôn ngữ của bạn.
  • Một "octet" về cơ bản là một số nguyên từ 0 đến 255, bao gồm bạn có thể mã hóa thành số nguyên, ký tự hoặc bất cứ thứ gì phù hợp với ngôn ngữ của bạn.
  • E và D phải hoàn toàn xác định (nghĩa là cung cấp cho chúng cùng một đầu vào sẽ luôn tạo ra cùng một đầu ra, trong đó "đầu vào" được định nghĩa là đầu vàobức xạ cho E, hoặc mã hóa cho D). Cụ thể, E có thể không liên lạc thông tin với D qua kênh bên.
  • Việc xóa được thực hiện bằng cách xóa một phần tử của chuỗi; nghĩ đến việc mở seuqence trong trình chỉnh sửa, đặt con trỏ tại một điểm tùy ý và nhấn Backspace. Nếu một phần tử xuất hiện nhiều lần, có thể chỉ một bản sao của phần tử sẽ bị xóa (tức là các phiên bản khác của cùng một octet sẽ không bị ảnh hưởng).
  • Mặc dù điểm số chỉ được tính toán trên cơ sở đầu vào khá ngắn , chương trình của bạn phải hoạt động trên lý thuyết cho bất kỳ đầu vàobức xạ nào . Cụ thể, nó phải hoạt động bất kể octet nào xuất hiện trong đầu vào . (Xin lỗi, những người muốn sử dụng các ký tự không thể in được mà họ biết sẽ không xuất hiện trong đầu vào, nhưng tôi cần đảm bảo rằng đầu vào không bị nén để thách thức là làm cứng bức xạ thay vì nén.)
  • Bạn có thể gửi một tệp xác định hai chức năng; hai tệp mà mỗi tệp xác định một chức năng hoặc cả hai đều là chương trình đầy đủ; hoặc ba tệp, hai trong số đó triển khai D và E tương ứng (thông qua là chương trình đầy đủ hoặc thông qua xác định hàm) và tệp thứ ba là tệp tiêu đề hoặc thư viện chung cho cả D và E. Bất kể bạn sử dụng hình thức gửi nào , việc triển khai ngôn ngữ lập trình của bạn phải có khả năng hiểu cả hai chương trình mà không cần phải tranh luận thêm như vị trí tệp (nếu không bạn phải trả tiền phạt byte để yêu cầu triển khai theo cách khác thường, theo quy tắc chuẩn của chúng tôi).

Điều kiện chiến thắng

Đối với mỗi chiều dàibức xạ , hãy để f ( chiều dài , bức xạ ) là tổng chiều dài của các mã hóa tương ứng với tất cả các đầu vào có chiều dàibức xạ đã cho . (Nghĩa là, f ( chiều dài , bức xạ ) = tổng đầu vào có chiều dài chiều dài (E ( đầu vào , bức xạ )).) Sau đó cho g ( chiều dài , bức xạ ) bằng f ( chiều dài ,phóng xạ ) ÷ 256 chiều dài . Nói cách khác, g là chiều dài trung bình của đầu ra được mã hóa, với độ dài đầu vào nhất định và yêu cầu làm cứng bức xạ nhất định. . không chắc chắn, hãy đăng một số điểm gần đúng và bạn hoặc ai đó có thể tính toán sâu hơn nếu một mục khác đăng một số điểm tương tự.)

Điểm của bạn bằng tổng g ( chiều dài , bức xạ ) cho tất cả các bức xạ trong phạm vi từ 0 đến 9, và tất cả độ dài trong phạm vi từ 0 đến 99, bao gồm (chủ yếu là để tránh mã hóa hoặc để duy trì sự cạnh tranh nếu ai đó phát hiện ra một mã hóa hoàn hảo về mặt toán học, đây có thể là một yếu tố tối thiểu khác) tổng số byte trong bài nộp của bạn cho thử thách (cộng với các hình phạt tiêu chuẩn cho những việc như yêu cầu cờ phiên dịch bất thường hoặc tên tệp cụ thể). Người chiến thắng là mục có số điểm thấp nhất (được chia theo mục đầu tiên để gửi).


Bộ giải mã có thể biết thông số bức xạ không?
orlp

(hoặc độ dài , nhưng tôi tin rằng việc biết một trong hai sẽ cho bạn cái khác trong hầu hết các kế hoạch)
orlp

1
@orlp: Không, nó chỉ có chuỗi. Trong một vấn đề làm cứng bức xạ , bộ giải mã (tức là ngôn ngữ) không biết các quy tắc bức xạ được sử dụng, vì vậy bộ giải mã của bạn cũng không biết chúng; nó phải suy ra chúng từ đầu vào của nó.

Dựa trên video máy tính này, tôi sẽ tạo một ngôn ngữ lấy các bộ mã trong bộ ba byte: nếu chúng không khớp nhau, có gì đó không ổn và chúng tôi có đủ thông tin để tìm ra giá trị đúng là gì. Có lẽ có một cách để làm điều đó với ít bit hơn nhưng tôi không có bộ não để tìm ra cách thức vào lúc này.
Draco18

Làm thế nào để bạn kết hợp tiêu đề và các chương trình?
Máy

Câu trả lời:


8

CJam, điểm số ≤ 286,516 + 54 + 36 = 286.606

Mã hoá

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

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

Bộ giải mã

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

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

Cả hai đều lấy và trả về một số nguyên danh sách. Các liên kết TIO bao gồm chuyển đổi từ / sang chuỗi để thuận tiện. Lưu ý rằng đây là không hiệu quả đáng kinh ngạc cho chuỗi dài hơn. Nếu bạn muốn thử thêm một vài ký tự, tôi khuyên bạn nên sử dụng các ký tự có mã ký tự nhỏ.

Ý tưởng cơ bản để tạo ra một mã hóa cứng bức xạ bao gồm hai bước:

  1. Tìm một mã hóa không bao giờ chứa hai octet giống hệt nhau liên tiếp.
  2. Lặp lại mỗi octet trong chuỗi được mã hóa r + 1 lần, trong đó r là mức bức xạ.

Bằng cách này, bức xạ không thể xóa hoàn toàn một lần chạy các ký tự giống hệt nhau, để chúng ta có thể giải mã chuỗi bằng cách lấy một ký tự từ mỗi lần chạy và sau đó giải mã bước 1.

Vì vậy, phần thú vị duy nhất là tìm kiếm một mã hóa không bao giờ mang lại các octet lặp đi lặp lại. Ý tưởng cơ bản là sử dụng một cái gì đó như A043096 làm hệ thống số. Nghĩa là, để mã hóa một số nguyên N , chúng ta chỉ cần đếm trong một số b cơ sở , bỏ qua tất cả các số với các octet lặp lại. Tôi tin rằng số lượng các số có thể được biểu diễn theo cách này với tối đa các chữ số d giống như số lượng các số có thể được biểu thị trong cơ sở phỏng đoán b-1 (vì khi bạn muốn viết một số như vậy, bạn có thể chọn giữa b-1 chữ số cho mỗi vị trí mà không vi phạm ràng buộc).

Tất nhiên, để có được nén tối đa, chúng ta sẽ sử dụng b = 256 . Để biến đầu vào thành một số nguyên, chúng ta cũng có thể sử dụng chuyển đổi cơ sở. Nếu tôi không lười biếng, tôi sẽ sử dụng cơ sở tính toán cho đầu vào, nhưng hiện tại tôi chỉ đang chuẩn bị một 1(để đảm bảo không có số 0 đứng đầu) và sau đó sử dụng cơ sở nhỏ nhất có thể sao cho tất cả các octet trong đầu vào ít hơn cơ sở.

Cơ sở này sau đó được thêm vào mã hóa (để bộ giải mã biết cơ sở nào sẽ sử dụng) và được tách ra khỏi số còn lại bằng 0-octet (điều này hoạt động vì số còn lại sẽ không bao giờ bắt đầu bằng 0). Là một tối ưu hóa nhỏ, chuỗi trống vẫn là một chuỗi trống.

Lý do tôi chưa tính được một điểm chính xác ở trên là vì tôi chỉ tính toán giới hạn trên trong bao lâu mỗi đầu vào sẽ dựa trên chiều dài và octet tối đa của nó. Tuy nhiên, đối với hai tham số này, thường sẽ có hai độ dài đầu ra khác nhau và tôi không bận tâm tìm ra điểm bùng phát giữa chúng xảy ra ở đâu. Tôi cũng đã sử dụng chiều dài của cơ sở 255 thông thường thay vì cơ sở phỏng đoán 255 để ước tính độ dài đó, một lần nữa lớn hơn một chút so với mức cần thiết. Mã Mathicala chính xác mà tôi đã sử dụng để tính toán như sau:

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]nên đưa ra số chuỗi có độ dài lvới octet tối đa b-1(ngoại trừ b == 1, nơi tôi đã mã hóa nó 0bởi vì tôi luôn sử dụng ít nhất là cơ sở 2).


"Giả sử rằng trung bình một chuỗi độ dài N không thể được mã hóa dưới (r + 1) * N octet ở mức bức xạ r." Tôi thấy không có lý do cho điều này là đúng. Tôi sẽ không ngạc nhiên khi thấy một sơ đồ mã hóa hiện có là O (N + r).
orlp

1
@orlp Tôi không thấy điều đó có thể xảy ra, nhưng tôi mong được chứng minh là sai. :)
Martin Ender

Ý kiến ​​hay. Tôi không biết CJam, nhưng từ mô tả của bạn, có vẻ như bạn đang chuẩn bị cơ sở cho dữ liệu được mã hóa. Nếu đó là trường hợp, có vấn đề gì không nếu có các ký tự bị xóa khỏi dữ liệu được chuẩn bị trước? (Đó là những sai lầm tôi thực hiện mà @Leo chỉ ra, rằng tôi phải sửa chữa trong dung dịch của tôi.)
Mitchell Spector

@MitchellSpector cơ sở được chuẩn bị trước khi lặp lại mỗi ký tự r + 1 lần. Vì vậy, các cơ sở là bức xạ an toàn là tốt.
Martin Ender

Điều đó tốt - đó là những gì tôi đã làm trong giải pháp của mình. Bạn chỉ cần chắc chắn rằng bộ giải mã có thể giải mã dữ liệu được chuẩn bị trước khi biết cơ sở là gì.
Spector Mitchell

6

tiện ích bash + GNU, điểm 294506 283468

Chỉnh sửa 1: Khắc phục sự cố mà @Leo nhận thấy - cảm ơn bạn!

Chỉnh sửa 2: Cải thiện phương pháp mã hóa cho tham số bức xạ, để có điểm cao hơn.

Bộ mã hóa (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

Bộ giải mã (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

Đối với bộ mã hóa: Chuỗi Octet được truyền dưới dạng các ký tự trong stdin, tham số bức xạ r được truyền dưới dạng đối số.

Đối với bộ giải mã: Đầu vào được chuyển dưới dạng các ký tự trong stdin.

Đối với cả hai: Đầu ra trên thiết bị xuất chuẩn.

Bộ mã hóa chuẩn bị cho dữ liệu đầu vào các chữ số của r, với một ký tự 'a' được chèn giữa mỗi cặp chữ số giống nhau liên tiếp, theo sau là một dòng mới. Sau đó, nó sao chép tất cả các đầu vào (bắt đầu bằng các ký tự được chuẩn bị trước), thay thế mỗi ký tự bằng r + 1 bản sao của ký tự đó.

Bộ giải mã hoàn tác điều này, đi qua từng ký tự x còn lại trong đầu vào của nó, bỏ qua các bản sao x liên tiếp giống hệt nhau của x sau x và in những gì còn lại. Dữ liệu được chuẩn bị trước không có ký tự lặp lại, vì vậy nó có thể được giải mã trước khi biết r. Tại thời điểm đó, r được biết và giá trị đó là cần thiết để giải mã chính xác phần còn lại của dữ liệu.

Bạn có thể kiểm tra xem cái này có hoạt động ngay cả khi đầu vào ban đầu lặp lại các ký tự giống hệt nhau không.


Tính toán điểm:

Giả sử đầu vào có độ dài L và tham số bức xạ là r (nhiều nhất là 9 cho tính toán tính điểm, do đó, nó khớp với một chữ số và do đó không có ký tự lặp lại liên tiếp). Dữ liệu được chuẩn bị trước là 2 byte (chữ số, dòng mới), do đó, đầu ra là (r + 1) (L + 2) byte cho luồng được mã hóa.

Vậy g (L, r) = (r + 1) (L + 2).

Theo sau đó, tổng số điểm có thể được tính là

nhập mô tả hình ảnh ở đây


Điều gì nếu octet bị rơi là cái đầu tiên? Bộ giải mã sẽ không cần rđọc
Leo

@Leo Bạn nói đúng. Tôi sẽ xem xét việc sửa chữa vào ngày mai - tối nay đã quá muộn. Cảm ơn vì đã phát hiện ra nó.
Spector Mitchell

@Leo Tôi nghĩ rằng nó có thể sửa được bằng cách bao gồm r + 1 bản sao của mỗi chữ số của r, theo sau là r + 1 dòng mới. Nếu đó là chính xác, điểm số sẽ không tăng lên rất nhiều.
Spector Mitchell

Một cái gì đó như thế nên làm việc. Tôi nghĩ bạn nên thực hiện một số biện pháp bổ sung để đảm bảo rằng nó hoạt động chính xác với bức xạ cao hơn (ví dụ như bức xạ 222), nhưng may mắn là điểm số được tính chỉ bằng bức xạ 0-9, vì vậy nó sẽ không bị ảnh hưởng nhiều. Tái bút: Tôi đã nghĩ đến việc thực hiện cùng một mã hóa này, đó là lý do tại sao tôi phát hiện ra lỗi ngay lập tức;)
Leo

@Leo Có, cách khắc phục có hiệu quả đối với tất cả các giá trị của bức xạ, mặc dù điểm số chỉ tính đến các giá trị bức xạ tối đa là 9.
Mitchell Spector

3

Perl + Math :: {ModInt, Đa thức, Prime :: Util}, điểm 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/^␁//;$_}

Hình ảnh điều khiển được sử dụng để thể hiện ký tự điều khiển tương ứng (ví dụ: ký tự NUL theo nghĩa đen). Đừng lo lắng nhiều về việc cố gắng đọc mã; có một phiên bản dễ đọc hơn dưới đây.

Chạy với -Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all. -MMath::Bigint=lib,GMPlà không cần thiết (và do đó không được bao gồm trong điểm số), nhưng nếu bạn thêm nó trước các thư viện khác, nó sẽ làm cho chương trình chạy nhanh hơn một chút.

Tính điểm

Thuật toán ở đây có phần dễ ứng dụng, nhưng sẽ khó viết hơn (do Perl không có các thư viện phù hợp). Bởi vì điều này, tôi đã thực hiện một vài sự đánh đổi kích thước / hiệu quả trong mã, trên cơ sở cho rằng các byte có thể được lưu trong mã hóa, không có lý do gì để cố gắng loại bỏ mọi điểm khi chơi golf.

Chương trình bao gồm 600 byte mã, cộng với 78 byte hình phạt cho các tùy chọn dòng lệnh, đưa ra mức phạt 678 điểm. Phần còn lại của điểm được tính bằng cách chạy chương trình trên chuỗi trường hợp tốt nhất và trường hợp xấu nhất (về độ dài đầu ra) cho mọi độ dài từ 0 đến 99 và mọi mức độ bức xạ từ 0 đến 9; trường hợp trung bình là một nơi nào đó ở giữa, và điều này mang lại giới hạn về điểm số. (Không đáng để cố gắng tính giá trị chính xác trừ khi có mục khác có điểm tương tự.)

Do đó, điều này có nghĩa là điểm số từ hiệu quả mã hóa nằm trong phạm vi bao gồm 91100 đến 92141, do đó, điểm số cuối cùng là:

91100 + 600 + 78 = 91778 ≤ điểm 92819 = 92141 + 600 + 78

Phiên bản ít chơi hơn, với ý kiến ​​và mã kiểm tra

Đây là chương trình ban đầu + dòng mới, thụt lề và bình luận. (Trên thực tế, phiên bản chơi gôn được sản xuất bằng cách xóa dòng mới / thụt lề / nhận xét khỏi phiên bản này.)

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;

Thuật toán

Đơn giản hóa vấn đề

Ý tưởng cơ bản là giảm vấn đề "mã hóa xóa" này (không phải là vấn đề được khám phá rộng rãi) thành một vấn đề mã hóa xóa (một lĩnh vực toán học được khám phá toàn diện). Ý tưởng đằng sau mã hóa xóa là bạn đang chuẩn bị dữ liệu được gửi qua "kênh xóa", một kênh đôi khi thay thế các ký tự mà nó gửi bằng ký tự "bị cắt xén" chỉ ra vị trí đã biết của lỗi. (Nói cách khác, nó luôn luôn rõ ràng nơi tham nhũng đã xảy ra, mặc dù nhân vật ban đầu vẫn chưa được biết.) Ý tưởng đằng sau khá đơn giản: chúng tôi chia đầu vào thành các khối dài ( bức xạ+ 1) và sử dụng bảy trong số tám bit trong mỗi khối cho dữ liệu, trong khi bit còn lại (trong cấu trúc này, MSB) xen kẽ giữa việc được đặt cho toàn bộ khối, xóa cho toàn bộ khối tiếp theo, được đặt cho khối sau đó, vân vân Bởi vì các khối dài hơn tham số bức xạ, ít nhất một ký tự từ mỗi khối tồn tại trong đầu ra; vì vậy bằng cách chạy các ký tự có cùng MSB, chúng ta có thể tìm ra khối nào mỗi nhân vật thuộc về. Số lượng khối cũng luôn lớn hơn tham số bức xạ, vì vậy chúng ta luôn có ít nhất một khối không bị hư hại trong encdng; do đó chúng tôi biết rằng tất cả các khối dài nhất hoặc gắn dài nhất đều không bị hư hại, cho phép chúng tôi coi bất kỳ khối ngắn nào là hư hỏng (do đó bị cắt xén). Chúng ta cũng có thể suy ra tham số bức xạ như thế này (nó '

Mã hóa xóa

Đối với phần mã hóa xóa của vấn đề, điều này sử dụng một trường hợp đặc biệt đơn giản của việc xây dựng Reed-Solomon. Đây là một cấu trúc có hệ thống: đầu ra (của thuật toán mã hóa xóa) bằng với đầu vào cộng với một số khối bổ sung, bằng với tham số bức xạ. Chúng ta có thể tính toán các giá trị cần thiết cho các khối này theo cách đơn giản (và golf!), Thông qua việc coi chúng là các khối, sau đó chạy thuật toán giải mã trên chúng để "tái tạo" giá trị của chúng.

Ý tưởng thực tế đằng sau việc xây dựng cũng rất đơn giản: chúng tôi phù hợp với một đa thức, ở mức độ tối thiểu có thể, cho tất cả các khối trong mã hóa (với các lần cắt được nội suy từ các phần tử khác); nếu đa thức là f , khối thứ nhất là f (0), khối thứ hai là f (1), v.v. Rõ ràng là mức độ của đa thức sẽ bằng số khối đầu vào trừ đi 1 (vì chúng ta khớp một đa thức với các khối đó trước, sau đó sử dụng nó để xây dựng các khối "kiểm tra" bổ sung); và bởi vì d +1 điểm xác định duy nhất một đa thức bậc d, cắt xén bất kỳ số khối nào (tối đa tham số bức xạ) sẽ để lại một số khối không bị hư hại bằng với đầu vào ban đầu, đó là thông tin đủ để tái tạo lại đa thức. (Sau đó chúng ta chỉ cần đánh giá đa thức để giải quyết một khối.)

Chuyển đổi cơ sở

Việc xem xét cuối cùng còn lại ở đây là thực hiện với các giá trị thực tế được thực hiện bởi các khối; nếu chúng ta thực hiện phép nội suy đa thức trên các số nguyên, kết quả có thể là số hữu tỷ (chứ không phải số nguyên), lớn hơn nhiều so với các giá trị đầu vào hoặc không mong muốn. Như vậy, thay vì sử dụng các số nguyên, chúng tôi sử dụng trường hữu hạn; trong chương trình này, trường hữu hạn được sử dụng là trường số nguyên modulo p , trong đó p là số nguyên tố lớn nhất nhỏ hơn 128 bức xạ +1(tức là số nguyên tố lớn nhất mà chúng ta có thể khớp với một số giá trị riêng biệt bằng số nguyên tố đó vào phần dữ liệu của một khối). Ưu điểm lớn của các trường hữu hạn là phép chia (trừ 0) được xác định duy nhất và sẽ luôn tạo ra một giá trị trong trường đó; do đó, các giá trị nội suy của đa thức sẽ khớp với một khối giống như cách các giá trị đầu vào thực hiện.

Sau đó, để chuyển đổi đầu vào thành một chuỗi dữ liệu khối, chúng ta cần thực hiện chuyển đổi cơ sở: chuyển đổi đầu vào từ cơ sở 256 thành một số, sau đó chuyển đổi thành cơ sở p (ví dụ: tham số bức xạ là 1, chúng ta có p= 16381). Điều này chủ yếu được duy trì do thiếu các thói quen chuyển đổi cơ sở của Perl (Math :: Prime :: Util có một số, nhưng chúng không hoạt động cho các cơ sở bignum, và một số số nguyên tố chúng tôi làm việc ở đây rất lớn). Vì chúng tôi đã sử dụng Math :: Polynomial cho phép nội suy đa thức, tôi có thể sử dụng lại nó như là một hàm "chuyển đổi từ chuỗi số" (thông qua việc xem các chữ số là hệ số của đa thức và đánh giá nó), và điều này hoạt động cho bignums bình thường. Đi theo con đường khác, mặc dù, tôi đã phải tự viết chức năng. May mắn thay, nó không quá khó (hoặc dài dòng) để viết. Thật không may, chuyển đổi cơ sở này có nghĩa là đầu vào thường được hiển thị không thể đọc được. Cũng có một vấn đề với các số 0 hàng đầu;

Cần lưu ý rằng chúng ta không thể có nhiều hơn khối p trong đầu ra (nếu không các chỉ số của hai khối sẽ trở nên bằng nhau, và có thể cần phải tạo ra các đầu ra khác nhau tạo thành đa thức). Điều này chỉ xảy ra khi đầu vào cực kỳ lớn. Chương trình này giải quyết vấn đề theo một cách rất đơn giản: tăng bức xạ (làm cho các khối lớn hơn và p lớn hơn nhiều, có nghĩa là chúng ta có thể chứa nhiều dữ liệu hơn và dẫn đến kết quả chính xác).

Một điểm đáng làm khác là chúng ta mã hóa chuỗi null thành chính nó, bởi vì chương trình như được viết sẽ bị sập trên nó. Đây rõ ràng cũng là mã hóa tốt nhất có thể, và hoạt động bất kể thông số bức xạ là gì.

Cải tiến tiềm năng

Sự không hiệu quả tiệm cận chính trong chương trình này là để sử dụng modulo-Prime làm các trường hữu hạn trong câu hỏi. Các trường hữu hạn có kích thước 2 n tồn tại (đó chính xác là những gì chúng ta muốn ở đây, bởi vì kích thước tải trọng của các khối tự nhiên là sức mạnh của 128). Thật không may, chúng khá phức tạp hơn so với cách xây dựng modulo đơn giản, có nghĩa là Math :: ModInt sẽ không cắt nó (và tôi không thể tìm thấy bất kỳ thư viện nào trên CPAN để xử lý các trường hữu hạn có kích thước không chính); Tôi phải viết cả một lớp với số học quá tải cho Math :: Polynomial để có thể xử lý nó, và tại thời điểm đó, chi phí byte có thể có khả năng vượt quá tổn thất (rất nhỏ) khi sử dụng, ví dụ, 16381 thay vì 16384.

Một ưu điểm khác của việc sử dụng kích thước power-of-2 là việc chuyển đổi cơ sở sẽ trở nên dễ dàng hơn nhiều. Tuy nhiên, trong cả hai trường hợp, một phương pháp tốt hơn để biểu thị độ dài của đầu vào sẽ hữu ích; phương pháp "trả trước 1 trong các trường hợp mơ hồ" là đơn giản nhưng lãng phí. Chuyển đổi cơ sở sinh học là một cách tiếp cận hợp lý ở đây (ý tưởng là bạn có cơ sở là một chữ số và 0 không phải là một chữ số, sao cho mỗi số tương ứng với một chuỗi).

Mặc dù hiệu suất tiệm cận của mã hóa này rất tốt (ví dụ: đầu vào có độ dài 99 và tham số bức xạ là 3, mã hóa luôn dài 128 byte, thay vì ~ 400 byte mà các phương pháp dựa trên lặp lại sẽ có được), hiệu suất của nó là kém tốt về đầu vào ngắn; độ dài của mã hóa luôn luôn ít nhất là bình phương của (tham số bức xạ + 1). Vì vậy, đối với các đầu vào rất ngắn (chiều dài 1 đến 8) ở bức xạ 9, tuy nhiên độ dài của đầu ra là 100. (Ở độ dài 9, độ dài của đầu ra đôi khi là 100 và đôi khi là 110.) Các cách tiếp cận dựa trên sự lặp lại rõ ràng đã đánh bại việc xóa này phương pháp tiếp cận dựa trên mã hóa trên các đầu vào rất nhỏ; nó có thể có giá trị thay đổi giữa nhiều thuật toán dựa trên kích thước của đầu vào.

Cuối cùng, nó không thực sự xuất hiện trong việc tính điểm, nhưng với các thông số bức xạ rất cao, sử dụng một bit của mỗi byte (kích thước đầu ra) để phân định các khối là lãng phí; Thay vào đó, sẽ rẻ hơn khi sử dụng các dấu phân cách giữa các khối. Tái tạo các khối từ các dấu phân cách khá khó hơn so với cách tiếp cận MSB xen kẽ, nhưng tôi tin rằng điều đó là có thể, ít nhất là nếu dữ liệu đủ dài (với dữ liệu ngắn, khó có thể suy ra tham số bức xạ từ đầu ra) . Đó sẽ là một cái gì đó để xem xét nếu nhắm đến một cách tiếp cận lý tưởng không có triệu chứng bất kể các tham số.

(Và tất nhiên, có thể có một thuật toán hoàn toàn khác tạo ra kết quả tốt hơn thuật toán này!)

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.